Skip to content

Commit

Permalink
Allow to have different implementations of extensions
Browse files Browse the repository at this point in the history
Signed-off-by: Jon Harper <jon.harper87@gmail.com>
  • Loading branch information
jonenst committed Feb 13, 2020
1 parent fafde36 commit c4c5b97
Show file tree
Hide file tree
Showing 6 changed files with 605 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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 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 Extendable#createExtension} that must be overriden by
* subclasses to create the extension.
*
* @author Jon Harper <jon.harper at rte-france.com>
*/
//TODO can we use T extends Extendable<T> here ? Then we can remove the cast in add()
public abstract class AbstractExtensionAdder<T, E extends Extension<T>> implements ExtensionAdder<T, E> {

protected T extendable;

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

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

@Override
public T add() {
((Extendable<T>) extendable).addExtension(getExtensionInterface(), createExtension());
return extendable;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,39 @@ 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";
}

/**
* 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.
*
* This is not meant to be overriden.
*
* @param type The interface of the ExtensionAdder
* @return the adder
*/
// 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 O as "this".
@SuppressWarnings({ "rawtypes", "unchecked" })
default <E extends Extension<O>, B extends ExtensionAdder<O, 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;

/**
* 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.
*
* @return the interface of the extension
*/
// Class<? super E> to allow builders 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> getExtensionInterface();

/**
* 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,49 @@
/**
* 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
*/
//TODO can we use T extends Extendable<T> here ? It would make calling newAdder safer.
public interface ExtensionAdderProvider<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,94 @@
/**
* 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.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")
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;

//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 static final ConcurrentMap<Pair<String, Class>, ExtensionAdderProvider> CACHE = new ConcurrentHashMap<>();

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 platform 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>
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));
}

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

0 comments on commit c4c5b97

Please sign in to comment.