Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to have different implementations of extensions #1152

Merged
merged 2 commits into from
Feb 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,14 @@ public <E extends Extension<T>> boolean removeExtension(Class<E> type) {
public Collection<Extension<T>> getExtensions() {
return extensionsByName.values();
}

// Don't bother all the way with generics because the this is a runtime system
// that doesn't know generics. We do check that the builder is from the the same
// extendable class T as "this".
@Override
public <E extends Extension<T>, B extends ExtensionAdder<T, E>> B newExtension(Class<B> type) {
ExtensionAdderProvider provider = ExtensionAdderProviders.findCachedProvider(getImplementationName(), type);
return (B) provider.newAdder(this);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2020, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.commons.extensions;

import java.util.Objects;

/**
* A base class for implementations of {@link ExtensionAdder} that holds the
* extendable to be able build and then add the extension to the extendable.
* This class calls {@link #createExtension} that must be overriden by
* subclasses to create the extension.
*
* @author Jon Harper <jon.harper at rte-france.com>
*/
public abstract class AbstractExtensionAdder<T extends Extendable<T>, E extends Extension<T>>
implements ExtensionAdder<T, E> {

private final T extendable;

protected AbstractExtensionAdder(T extendable) {
this.extendable = Objects.requireNonNull(extendable);
}

/**
* Creates the extension.
*
* @return the extension
*/
protected abstract E createExtension(T extendable);

@Override
public T add() {
extendable.addExtension(getExtensionClass(), createExtension(extendable));
return extendable;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,30 @@ public interface Extendable<O> {
* @return all extensions associated to this extendable object.
*/
<E extends Extension<O>> Collection<E> getExtensions();

/**
* Returns a name that is used to find matching {@link ExtensionAdderProvider}s
* when selecting implementations of extensions in {@link #newExtension}. This
* is meant to be overriden by extendables when multiple implementations exist.
*
* @return the name
*/
default String getImplementationName() {
return "Default";
}
mathbagu marked this conversation as resolved.
Show resolved Hide resolved

/**
* Returns an extensionAdder to build and add an extension for this extendable.
*
* The extension implementation is selected at runtime based on matching the
* {@link #getImplementationName} of this extendable to the
* {@link ExtensionAdderProvider#getImplementationName} of a provider.
* Implementations are loaded with java's {@link java.util.ServiceLoader} using
* the ExtensionAdderProvider interface.
*
* @param type The interface of the ExtensionAdder
* @return the adder
*/
<E extends Extension<O>, B extends ExtensionAdder<O, E>> B newExtension(Class<B> type);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2020, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.commons.extensions;

/**
* An ExtensionAdder is a builder for an extension that is built and then added
* to an extendable.
*
* @author Jon Harper <jon.harper at rte-france.com>
*/
// Can't use "T extends Extendable<T>" here because T is used in Extendable::newExtensionAdder
// to ensure that ExtensionAdder::T is the same as Extendable::O and Extendable doesn't declare
// Extendable<O extends Extendable>.
// TODO: If we can, we should change Extendable to declare Extendable<O extends Extendable> ?
public interface ExtensionAdder<T, E extends Extension<T>> {

/**
* Returns the class of the extension. This is expected to be an interface so
* that multiple implementors can implement the same extensions. This will be
* the key at which the extension is added on the extendable. This is meant to
* be implemented by adder interfaces but not by adder implementations.
*
* @return the interface of the extension
*/
// Class<? super E> to allow adders of generic extensions to return the class
// of the raw type. This has the bad effect of allowing the super classes but we
// consider the trade-off acceptable.
Class<? super E> getExtensionClass();

/**
* Builds and adds the extension to the extendable which was used to get this
* extensionAdder. The extendable is returned to allow a fluent style adding of
* multiple extensions.
*
* @return the extendable
*/
T add();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) 2020, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.commons.extensions;

/**
* A provider used through java's {@link java.util.ServiceLoader}. It will
* provide an {@link ExtensionAdder} to add an extension to an extendable.
* {@link #getImplementationName} is used to find providers corresponding to the
* implementation of the {@link Extendable}. {@link #getAdderClass} is used to
* specify the adder class.
*
* @author Jon Harper <jon.harper at rte-france.com>
*
* @param <T> The extendable
* @param <E> The extension
* @param <B> The extensionBuilder
*/
public interface ExtensionAdderProvider<T extends Extendable<T>, E extends Extension<T>, B extends ExtensionAdder<T, E>> {

/**
* Returns a name that is used to select this provider when searching for
* implementations of extension builders in {@link Extendable#newExtension}.
*
* @return the name
*/
String getImplementationName();

/**
* Returns the builder class provided by this Provider.
*
* @return the class
*/
// Class<? super B> to allow providers of generic builders to return the class
// of the raw type. This has the bad effect of allowing the super classes but we
// consider the trade-off acceptable.
Class<? super B> getAdderClass();

/**
* returns an new empty ExtensionAdder for this extendable.
*
* @param extendable the extendable on which the adder will add the extension
*/
B newAdder(T extendable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Copyright (c) 2020, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.powsybl.commons.extensions;
mathbagu marked this conversation as resolved.
Show resolved Hide resolved

import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Suppliers;
import com.powsybl.commons.PowsyblException;

/**
* A utility class to help finding providers using ServiceLoader.
*
* @author Jon Harper <jon.harper at rte-france.com>
*
*/
//Don't bother with generics because serviceloader doesn't return them
//and we put them in a cache where we can't propagate the generic types.
@SuppressWarnings("rawtypes")
public final class ExtensionAdderProviders {

private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionAdderProviders.class);

private static final Supplier<ConcurrentMap<String, List<ExtensionAdderProvider>>> ADDER_PROVIDERS = Suppliers
.memoize(() -> groupProvidersByName(ServiceLoader.load(ExtensionAdderProvider.class)))::get;

private static final ConcurrentMap<Pair<String, Class>, ExtensionAdderProvider> CACHE = new ConcurrentHashMap<>();

//package private for tests
static ConcurrentMap<String, List<ExtensionAdderProvider>> groupProvidersByName(Iterable<ExtensionAdderProvider> i) {
return StreamSupport.stream(i.spliterator(), false).collect(Collectors
.groupingByConcurrent(ExtensionAdderProvider::getImplementationName));
}

private ExtensionAdderProviders() {
}

private static <O, E extends Extension<O>, B extends ExtensionAdder<O, E>> ExtensionAdderProvider findProvider(
String name, Class<B> type,
ConcurrentMap<String, List<ExtensionAdderProvider>> providersMap) {

List<ExtensionAdderProvider> providersForName = providersMap.get(name);
if (providersForName == null) {
providersForName = Collections.emptyList();
}
List<ExtensionAdderProvider> providers = providersForName.stream()
.filter(s -> type.isAssignableFrom(s.getAdderClass()))
.collect(Collectors.toList());

if (providers.isEmpty()) {
LOGGER.error(
"ExtensionAdderProvider not found for ExtensionAdder {} for implementation {}",
type.getSimpleName(), name);
throw new PowsyblException("ExtensionAdderProvider not found");
}

if (providers.size() > 1) {
LOGGER.error(
"Multiple ExtensionAdderProviders found for ExtensionAdder {} for implementation {} : {}",
type.getSimpleName(), name, providers);
throw new PowsyblException(
"Multiple ExtensionAdderProviders configuration providers found");
}
return providers.get(0);
}

// TODO use O extends Extendable<O> here ? For now we can't because Extendable
// doesn't declare Extendable<O extends Extendable>
// package private for tests
static <O, E extends Extension<O>, B extends ExtensionAdder<O, E>> ExtensionAdderProvider findCachedProvider(
String name, Class<B> type,
ConcurrentMap<String, List<ExtensionAdderProvider>> providersMap,
ConcurrentMap<Pair<String, Class>, ExtensionAdderProvider> cache) {
return cache.computeIfAbsent(Pair.of(name, type), k -> findProvider(name, type, providersMap));
}

public static <O, E extends Extension<O>, B extends ExtensionAdder<O, E>> ExtensionAdderProvider findCachedProvider(
String name, Class<B> type) {
return findCachedProvider(name, type, ADDER_PROVIDERS.get(), CACHE);
}
}
Loading