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

com.gluonhq.attach.util.Services refactored #20

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
109 changes: 81 additions & 28 deletions modules/util/src/main/java/com/gluonhq/attach/util/Services.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,64 +32,117 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Logger;

public class Services<T> {

private static final Logger LOGGER = Logger.getLogger(Services.class.getName());

private static final Map<Class, ServiceFactory> FACTORY_MAP = new HashMap<>();
private static final Map<Class, Object> SERVICE_MAP = new HashMap<>();
private static final Map<Class<?>, ServiceFactory<?>> FACTORY_MAP = new HashMap<>();
private static final Map<Class<?>, Optional <?>> SERVICE_MAP = new HashMap<>();

private static final Optional <?> SERVICE_NULL_VALUE = Optional.of(new Object());

// not public API
private Services() { }

/**
* Required call to register a ServiceFactory for a given service of type T.
* ServiceFactory instance is cached.
* In case a service is called without its ServiceFactory being registered first,
* a Runtime Exception will be thrown
* In case a service is requested without its ServiceFactory being registered first
* and no Default Service could be obtained,
* a Warning will be logged.
*
* @param <T> The type of service
* @param factory The ServiceFactory instance
* @throws NullPointerException if {@code factory.getServiceType()} is {@code null}
*/
public static <T> void registerServiceFactory(ServiceFactory<T> factory) {
LOGGER.fine("Register " + factory);
FACTORY_MAP.put(factory.getServiceType(), factory);
FACTORY_MAP.put(Objects.requireNonNull(factory.getServiceType()), factory);
}

/**
* Returns an optional with a service, if previously a ServiceFactory was registered.
* Otherwise, it will try to find a service factory in the same package as the service, and
* otherwise a Runtime Exception will be thrown.
* otherwise a Warning will be logged.
* Both serviceFactory and service instances are cached, so only one service
* is created for the given factory
*
* @param <T> the type of service
* @param service the class of service
* @return An optional with the service
* @param serviceClass the class of service
* @return An optional with the service (which may be {@code Optional.empty()})
*/
public static <T> Optional<T> get(Class<T> service) {
LOGGER.fine("Get Service " + service.getName());
if (!FACTORY_MAP.containsKey(service)) {
final ServiceFactory<T> factory = getFactory(service);
if (factory != null) {
registerServiceFactory(factory);
} else {
throw new RuntimeException("The service " + service.getSimpleName() + " can't be registered. "
+ "Call Services.registerServiceFactory() with a valid ServiceFactory");
}
}
if (!SERVICE_MAP.containsKey(service)) {
FACTORY_MAP.get(service).getInstance()
.ifPresent(t -> SERVICE_MAP.put(service, t));
}
LOGGER.fine("Return service: " + SERVICE_MAP.get(service));
return Optional.ofNullable((T) SERVICE_MAP.get(service));
}
public static <T> Optional<T> get(final Class<T> serviceClass) {
LOGGER.fine("Get Service " + serviceClass.getName());
/*
* Note: SERVICE_MAP is used & updated SOLELY within this Method.
*/
if (SERVICE_MAP.containsKey(serviceClass)) {
/*
* Service already Mapped & instantiated, so we don't need the (albeit known) Factory.
* Just return the Optional containing the (NOT null) Service Singleton instance...
*/
@SuppressWarnings("unchecked")
final Optional<T> uncheckedResult = (Optional<T>) SERVICE_MAP.get(serviceClass);

if (SERVICE_NULL_VALUE != uncheckedResult) {
return uncheckedResult;
} else {
return Optional.empty();
}
}
/*
* Note.: for any particular Service, the following logic will be executed EXACTLY once
* except for the case where serviceFactory.getInstance() returns Optional.empty().
* (not all Services are available on all Platforms for example)
*
* TODO This behaviour is EXACTLY THE SAME as the previous implementation of this Method! (remove this TODO when merging to Repo)
*
* To avoid this, SERVICE_NULL_VALUE is used as a Placeholder
* & ensures that next time this Service is requested, a result will be found very quickly.
*
* TODO the disadvantage of this is that if a Service requires explicit registration
* in advance (via registerServiceFactory) & should the Service be requested
* BEFORE its ServiceFactory has been registered, the Service will never be found.
* If that is not desired, the SERVICE_NULL_VALUE logic will have to be removed & a Performance-hit taken)
*/

/*
* Service not yet Mapped: first try to find ServiceFactory...
*/
final ServiceFactory<?> serviceFactory;

if (FACTORY_MAP.containsKey(serviceClass)) {
/*
* This may be a SPECIFIC ServiceFactory as registered
* by a previous EXTERNAL-TO-THIS-CLASS invocation of registerServiceFactory(serviceFactory)
* or it may be the DefaultServiceFactory as registered in the "else" below...
*/
serviceFactory = FACTORY_MAP.get(serviceClass); // serviceFactory in Map can NEVER be null
} else {
serviceFactory = new DefaultServiceFactory<>(serviceClass);

registerServiceFactory(serviceFactory);
}
/*
* Use ServiceFactory (NEVER null here) to get Service Singleton...
*/
@SuppressWarnings("unchecked")
final Optional<T> optionalServiceInstance = (Optional<T>) serviceFactory.getInstance();

if (optionalServiceInstance.isPresent()) {
SERVICE_MAP.put(serviceClass, optionalServiceInstance);
} else {
SERVICE_MAP.put(serviceClass, SERVICE_NULL_VALUE);

LOGGER.warning("NULL Instance created by " + serviceFactory.getClass().getSimpleName()
+ " on current Platform (" + Platform.getCurrent() + ")");
}

private static <T> ServiceFactory<T> getFactory(Class<T> service) {
return new DefaultServiceFactory<>(service);
LOGGER.fine("Return service: " + optionalServiceInstance);
return optionalServiceInstance; // (may be Optional.empty)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,18 @@ public Optional<T> getInstance() {
return Optional.ofNullable(instance);
}

private T createInstance(Platform platform) {
String fqn = serviceType.getPackageName() + ".impl." + className(platform);
private T createInstance(final Platform platform) {
final String fqn = serviceType.getPackageName() + ".impl." + className(platform);
try {
Class<T> clazz = (Class<T>) Class.forName(fqn);
if (clazz != null) {
LOGGER.fine("Service class for: " + clazz.getName());
return clazz.getDeclaredConstructor().newInstance();
}
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) {
@SuppressWarnings("unchecked")
final Class<T> clazz = (Class<T>) Class.forName(fqn); // clazz return is NEVER null

LOGGER.fine("Service class for: " + clazz.getName());
return clazz.getDeclaredConstructor().newInstance();

} catch (final InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) {
LOGGER.log(Level.SEVERE, null, ex);
} catch (ClassNotFoundException ex) {
} catch (final ClassNotFoundException ex) {
// no-op
LOGGER.log(Level.WARNING, "No new instance for " + serviceType);
}
Expand Down