diff options
12 files changed, 708 insertions, 68 deletions
diff --git a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.classpath b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.classpath index cc4f891eda6..dbc46ec74ec 100644 --- a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.classpath +++ b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.classpath @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> <classpathentry kind="src" path="src-gen"/> <classpathentry kind="src" path="src/"/> diff --git a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.settings/org.eclipse.jdt.core.prefs b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.settings/org.eclipse.jdt.core.prefs index 94d61f00da6..b3aa6d60f94 100644 --- a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.settings/org.eclipse.jdt.core.prefs +++ b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/.settings/org.eclipse.jdt.core.prefs @@ -1,10 +1,10 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff --git a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/META-INF/MANIFEST.MF b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/META-INF/MANIFEST.MF index fb514cc358c..80641f36eb4 100644 --- a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/META-INF/MANIFEST.MF +++ b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/META-INF/MANIFEST.MF @@ -67,4 +67,4 @@ Bundle-Activator: org.eclipse.papyrus.cdo.internal.core.Activator Bundle-ManifestVersion: 2
Bundle-Description: %pluginDescription
Bundle-SymbolicName: org.eclipse.papyrus.cdo.core;singleton:=true
-Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
diff --git a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/src/org/eclipse/papyrus/cdo/internal/core/services/localizer/CDOAwareObjectLocalizer.java b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/src/org/eclipse/papyrus/cdo/internal/core/services/localizer/CDOAwareObjectLocalizer.java index cd6753439c5..d0a5e5fd501 100644 --- a/extraplugins/cdo/org.eclipse.papyrus.cdo.core/src/org/eclipse/papyrus/cdo/internal/core/services/localizer/CDOAwareObjectLocalizer.java +++ b/extraplugins/cdo/org.eclipse.papyrus.cdo.core/src/org/eclipse/papyrus/cdo/internal/core/services/localizer/CDOAwareObjectLocalizer.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2013 CEA LIST. + * Copyright (c) 2013, 2016 CEA LIST, Christian W. Damus, and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * CEA LIST - Initial API and implementation + * Christian W. Damus - bug 488558 *****************************************************************************/ package org.eclipse.papyrus.cdo.internal.core.services.localizer; @@ -18,9 +19,7 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.papyrus.cdo.internal.core.CDOUtils; -import org.eclipse.papyrus.infra.core.services.IServiceFactory; -import org.eclipse.papyrus.infra.core.services.ServiceException; -import org.eclipse.papyrus.infra.core.services.ServicesRegistry; +import org.eclipse.papyrus.infra.core.services.SharedServiceFactory; import org.eclipse.papyrus.infra.services.localizer.DefaultObjectLocalizer; import org.eclipse.papyrus.infra.services.localizer.IObjectLocalizer; @@ -87,32 +86,10 @@ public class CDOAwareObjectLocalizer extends DefaultObjectLocalizer { // Nested types // - public static class Factory implements IServiceFactory { - - private static final IObjectLocalizer LOCALIZER = new CDOAwareObjectLocalizer(); + public static class Factory extends SharedServiceFactory<IObjectLocalizer> { public Factory() { - super(); - } - - @Override - public void init(ServicesRegistry servicesRegistry) throws ServiceException { - // pass. The localizer is stateless and requires no initialization - } - - @Override - public void startService() throws ServiceException { - // pass. The localizer is stateless and requires no starting - } - - @Override - public void disposeService() throws ServiceException { - // pass. The localizer is stateless and requires no disposal - } - - @Override - public Object createServiceInstance() throws ServiceException { - return LOCALIZER; + super(IObjectLocalizer.class, CDOAwareObjectLocalizer::new); } } diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/SharedServiceFactory.java b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/SharedServiceFactory.java new file mode 100644 index 00000000000..7066747473d --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.core/src/org/eclipse/papyrus/infra/core/services/SharedServiceFactory.java @@ -0,0 +1,283 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.core.services; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.eclipse.emf.common.util.WrappedException; +import org.eclipse.papyrus.infra.core.Activator; +import org.eclipse.papyrus.infra.tools.util.ReferenceCounted; + +/** + * A service factory that creates a reference-counted service that is shared + * amongst all {@linkplain ServicesRegistry service registries}. If the + * service interface {@code S} does not conform to the {@link IService} protocol + * then it is recommended to override the following operations: + * <ul> + * <li>{@link #start(Object)}</li> + * <li>{@link #dispose(Object)}</li> + * </ul> + * In any case, if the service is an {@link IService}, it will never be + * {@linkplain IService#init(ServicesRegistry) initialized} with a service + * registry because it is shared by all service registries. + * + * @param <S> + * the shared service interface + * + * @since 2.0 + */ +public class SharedServiceFactory<S> implements IServiceFactory { + + private final Class<? extends S> serviceInterface; + + private final Supplier<? extends S> serviceCreator; + + private final Function<? super ServicesRegistry, Scope> scopeProvider; + + private Scope scope; + + private S serviceInstance; + + /** + * Creates a new shared service factory for services of the given type. Subclasses that + * use this constructor must override the {@link #createSharedInstance()} method to + * create the shared service instance. The {@link Scope} of the shared service instance + * is the {@linkplain Scope#GLOBAL_SCOPE global scope}. + * + * @param serviceInterface + * the service type that I create + */ + protected SharedServiceFactory(Class<? extends S> serviceInterface) { + this(serviceInterface, null, null); + } + + /** + * Creates a new shared service factory for services of the given type with a supplier + * that provides the shared service instance on demand. Subclasses that + * use this constructor need not override the {@link #createSharedInstance()} method to + * create the shared service instance, unless the {@code serviceCreator} is {@code null}. + * The {@link Scope} of the shared service instance is the + * {@linkplain Scope#GLOBAL_SCOPE global scope}. + * + * @param serviceInterface + * the service type that I create + * @param serviceCreator + * a supplier that creates a new service instance on demand. This commonly + * would be a zero-argument constructor of the service implementation class + */ + protected SharedServiceFactory(Class<? extends S> serviceInterface, Supplier<? extends S> serviceCreator) { + this(serviceInterface, serviceCreator, null); + } + + /** + * Creates a new shared service factory for services of the given type in a defined + * scope, with a supplier that provides the shared service instance on demand. + * Subclasses that use this constructor need not override the {@link #createSharedInstance()} + * method to create the shared service instance, unless the {@code serviceCreator} is + * {@code null}. + * + * @param serviceInterface + * the service type that I create + * @param serviceCreator + * a supplier that creates a new service instance on demand. This commonly + * would be a zero-argument constructor of the service implementation class + * @param scopeProvider + * a function that computes the appropriate {@link Scope} in which + * a registry should find the shared instance of my service. A {@code null} scope + * is equivalent to the {@linkplain Scope#GLOBAL_SCOPE global scope} + */ + protected SharedServiceFactory(Class<? extends S> serviceInterface, Supplier<? extends S> serviceCreator, Function<? super ServicesRegistry, Scope> scopeProvider) { + super(); + + this.serviceInterface = serviceInterface; + this.serviceCreator = (serviceCreator != null) ? serviceCreator : this::createSharedInstance; + this.scopeProvider = (scopeProvider != null) ? scopeProvider : __ -> Scope.GLOBAL_SCOPE; + } + + @Override + public final void init(ServicesRegistry servicesRegistry) throws ServiceException { + // A shared service doesn't need to know any particular registry, but I + // need to create my scope + scope = scopeProvider.apply(servicesRegistry); + } + + @Override + public final void startService() throws ServiceException { + // It was already started on creation + } + + /** + * Releases my service instance, thereby reducing its reference count and potentially + * actually disposing of it (if there are no other service registries using it). + */ + @Override + public final void disposeService() { + if (serviceInstance != null) { + serviceInstance = null; + scope.maybeGetService(serviceInterface).ifPresent(ReferenceCounted::release); + scope = null; + } + } + + @Override + public final Object createServiceInstance() throws ServiceException { + if (serviceInstance == null) { + try { + serviceInstance = serviceInterface.cast(getCountedService().retain()); + } catch (ClassCastException e) { + getCountedService().release(); // It's of no use to us + throw new ServiceException("Incompatible service type", e); //$NON-NLS-1$ + } + } + + return serviceInstance; + } + + private ReferenceCounted<?> getCountedService() throws ServiceException { + return scope.getService(serviceInterface, this); + } + + /** + * If the factory is not {@linkplain #SharedServiceFactory(Class, Supplier) initialized} + * with a service creator, then this method must be overridden to create the shared + * service instance. The default implementation throws {@link UnsupportedOperationException}.. + * + * @return a new instance of my service, to be shared by all registries + */ + protected S createSharedInstance() { + throw new UnsupportedOperationException("createSharedInstance"); //$NON-NLS-1$ + } + + /** + * Starts the shared instance of my service. This is only called once in the lifetime + * of the instance. The default implementation forwards to the {@link IService} protocol + * if the {@code sharedInstance} conforms to it. Otherwise, subclasses should override + * to start the service instance if necessary. + * + * @param sharedInstance + * the shared instance of my service + * + * @throws ServiceException + * on failure to start the instance + * + * @see IService#startService() + */ + protected void start(S sharedInstance) throws ServiceException { + if (sharedInstance instanceof IService) { + ((IService) sharedInstance).startService(); + } + } + + /** + * Disposes of the shared instance of my service. This is only called once in the lifetime + * of the instance. The default implementation forwards to the {@link IService} protocol + * if the {@code sharedInstance} conforms to it. Otherwise, subclasses should override + * to dispose of the service instance if necessary. + * + * @param sharedInstance + * the shared instance of my service + * + * @throws ServiceException + * on failure to dispose of the instance + * + * @see IService#disposeService() + */ + protected void dispose(S sharedInstance) throws ServiceException { + if (sharedInstance instanceof IService) { + ((IService) sharedInstance).disposeService(); + } + } + + // + // Nested types + // + + /** + * A scope in which a single instance of my service is to be shared. Any number of + * service registries can share the same service within a scope if they have factories + * that provide that scope. It is up to each factory to determine the scope appropriate + * for its registry. The default scope is the {@link #GLOBAL_SCOPE}. Clients may + * create any scopes that they may need, in addition to using the + * {@linkplain #GLOBAL_SCOPE global scope}. + * + * @noextend This class is not intended to be subclassed by clients. + */ + public static class Scope { + /** + * The default scope in which registries share service instances, being a global scope + * available to all registries. + */ + public static final Scope GLOBAL_SCOPE = new Scope(); + + /** + * Initialize me. + */ + public Scope() { + super(); + } + + private final Map<Class<?>, ReferenceCounted<?>> serviceInstances = new ConcurrentHashMap<>(); + + /** + * Obtains the shared instance of the specified service interface if it currently + * exists in the scope. + * + * @param serviceInterface + * a service interface + * + * @return the current shared instance, or {@code null} if it does not exist + */ + public <S> S getService(Class<S> serviceInterface) { + return maybeGetService(serviceInterface) + .map(serviceInterface::cast) + .orElse(null); + } + + Optional<ReferenceCounted<?>> maybeGetService(Class<?> serviceInterface) { + return Optional.ofNullable(serviceInstances.get(serviceInterface)); + } + + <S> ReferenceCounted<?> getService(Class<? extends S> serviceInterface, SharedServiceFactory<S> factory) throws ServiceException { + try { + return serviceInstances.computeIfAbsent(serviceInterface, type -> { + S sharedInstance = factory.serviceCreator.get(); + + try { + factory.start(sharedInstance); + } catch (ServiceException e) { + throw new WrappedException(e); + } + + return new ReferenceCounted<>(sharedInstance, () -> { + // Remove the mapping for this shared instance + serviceInstances.remove(type); + + // And dispose of it + try { + factory.dispose(sharedInstance); + } catch (Exception e) { + Activator.log.error("Shared service instance not successfully disposed", e); //$NON-NLS-1$ + } + }); + }); + } catch (WrappedException e) { + throw (ServiceException) e.exception(); + } + } + } +} diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.classpath b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.classpath index ad32c83a788..eca7bdba8f0 100644 --- a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.classpath +++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.classpath @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> <classpathentry kind="src" path="src"/> <classpathentry kind="output" path="bin"/> diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.settings/org.eclipse.jdt.core.prefs b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.settings/org.eclipse.jdt.core.prefs index 94d61f00da6..b3aa6d60f94 100644 --- a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.settings/org.eclipse.jdt.core.prefs +++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/.settings/org.eclipse.jdt.core.prefs @@ -1,10 +1,10 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/META-INF/MANIFEST.MF b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/META-INF/MANIFEST.MF index e13640ff285..db1a0451351 100644 --- a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/META-INF/MANIFEST.MF +++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/META-INF/MANIFEST.MF @@ -12,4 +12,4 @@ Bundle-Localization: plugin Bundle-ManifestVersion: 2 Bundle-Activator: org.eclipse.papyrus.infra.services.localizer.internal.Activator Bundle-SymbolicName: org.eclipse.papyrus.infra.services.localizer;singleton:=true -Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 diff --git a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/src/org/eclipse/papyrus/infra/services/localizer/util/DefaultObjectLocalizerFactory.java b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/src/org/eclipse/papyrus/infra/services/localizer/util/DefaultObjectLocalizerFactory.java index 79ffad08e9f..35ca3a6ecf6 100644 --- a/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/src/org/eclipse/papyrus/infra/services/localizer/util/DefaultObjectLocalizerFactory.java +++ b/plugins/infra/services/org.eclipse.papyrus.infra.services.localizer/src/org/eclipse/papyrus/infra/services/localizer/util/DefaultObjectLocalizerFactory.java @@ -1,5 +1,5 @@ /***************************************************************************** - * Copyright (c) 2013 CEA LIST and others. + * Copyright (c) 2013, 2016 CEA LIST, Christian W. Damus, and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,42 +8,22 @@ * * Contributors: * CEA LIST - Initial API and implementation + * Christian W. Damus - bug 488558 *****************************************************************************/ package org.eclipse.papyrus.infra.services.localizer.util; -import org.eclipse.papyrus.infra.core.services.IServiceFactory; -import org.eclipse.papyrus.infra.core.services.ServiceException; -import org.eclipse.papyrus.infra.core.services.ServicesRegistry; +import org.eclipse.papyrus.infra.core.services.SharedServiceFactory; import org.eclipse.papyrus.infra.services.localizer.DefaultObjectLocalizer; +import org.eclipse.papyrus.infra.services.localizer.IObjectLocalizer; /** * Service factory for the default object localizer. */ -public class DefaultObjectLocalizerFactory implements IServiceFactory { +public class DefaultObjectLocalizerFactory extends SharedServiceFactory<IObjectLocalizer> { public DefaultObjectLocalizerFactory() { - super(); - } - - @Override - public void init(ServicesRegistry servicesRegistry) throws ServiceException { - // pass. The localizer is stateless and requires no initialization - } - - @Override - public void startService() throws ServiceException { - // pass. The localizer is stateless and requires no starting - } - - @Override - public void disposeService() throws ServiceException { - // pass. The localizer is stateless and requires no disposal - } - - @Override - public Object createServiceInstance() throws ServiceException { - return DefaultObjectLocalizer.INSTANCE; + super(IObjectLocalizer.class, DefaultObjectLocalizer::new); } } diff --git a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/services/ServicesRegistryTest.java b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/services/ServicesRegistryTest.java index 25c620d9ed8..0b8e1ce035e 100644 --- a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/services/ServicesRegistryTest.java +++ b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/services/ServicesRegistryTest.java @@ -8,7 +8,7 @@ * * Contributors: * LIFL - Initial API and implementation - * Christian W. Damus - bug 488791 + * Christian W. Damus - bugs 488791, 488558 * *****************************************************************************/ package org.eclipse.papyrus.infra.core.services; @@ -739,7 +739,7 @@ public class ServicesRegistryTest extends AbstractPapyrusTest { /** * General purpose descriptor. */ - public class ServiceFactoryDesc extends TestServiceDescriptor { + public static class ServiceFactoryDesc extends TestServiceDescriptor { public ServiceFactoryDesc(String key, ServiceStartKind startKind) { diff --git a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/services/SharedServiceFactoryTest.java b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/services/SharedServiceFactoryTest.java new file mode 100644 index 00000000000..78424bb56b4 --- /dev/null +++ b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/services/SharedServiceFactoryTest.java @@ -0,0 +1,399 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Christian W. Damus - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.core.services; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.eclipse.papyrus.junit.utils.rules.AnnotationRule; +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test cases for the {@link SharedServiceFactory} class. Tests annotated with + * {@link Parallel @Parallel} operate on multiple service registries in parallel + * and the initialization and finalization of services in parallel tests have + * random short delays to try to mix things up for each execution. + */ +public class SharedServiceFactoryTest { + private static final int DEFAULT_REGISTRY_COUNT = 3; + + private static Synchronizer sync; + + @Rule + public final AnnotationRule<Integer> registryCount = AnnotationRule.create(Registries.class, DEFAULT_REGISTRY_COUNT); + + @Rule + public final AnnotationRule<Boolean> parallelRegistries = AnnotationRule.createExists(Parallel.class); + + private List<ServicesRegistry> registries; + + public SharedServiceFactoryTest() { + super(); + } + + @Test + public void uniqueServiceInstances() { + registries().forEach(this::start); + + assertUniqueServiceInstance(Service1.class, Service1Impl.class); + assertUniqueServiceInstance(Service2.class, Service2Impl.class); + } + + @Test + public void serviceNotInitialized() { + registries().forEach(this::start); + + Service1Impl s1 = assumeUniqueServiceInstance(Service1.class, Service1Impl.class); + assertThat(s1.initializedCount.get(), is(0)); + } + + @Test + public void serviceStartedOnce() { + registries().forEach(this::start); + + Service1Impl s1 = assumeUniqueServiceInstance(Service1.class, Service1Impl.class); + assertThat(s1.startedCount.get(), is(1)); + + Service2Impl s2 = assumeUniqueServiceInstance(Service2.class, Service2Impl.class); + assertThat(s2.startedCount.get(), is(1)); + } + + @Test + public void serviceDisposedOnce() { + registries().forEach(this::start); + + Service1Impl s1 = assumeUniqueServiceInstance(Service1.class, Service1Impl.class); + Service2Impl s2 = assumeUniqueServiceInstance(Service2.class, Service2Impl.class); + + destroyFixture(); + + assertThat(s1.disposedCount.get(), is(1)); + assertThat(s2.disposedCount.get(), is(1)); + } + + @Test + public void servicesRecreated() { + registries().forEach(this::start); + + Service1Impl s1 = assumeUniqueServiceInstance(Service1.class, Service1Impl.class); + Service2Impl s2 = assumeUniqueServiceInstance(Service2.class, Service2Impl.class); + + destroyFixture(); + createFixture(); + registries().forEach(this::start); + + Service1Impl s1_ = assumeUniqueServiceInstance(Service1.class, Service1Impl.class); + Service2Impl s2_ = assumeUniqueServiceInstance(Service2.class, Service2Impl.class); + + assertThat(s1, not(sameInstance(s1_))); + assertThat(s2, not(sameInstance(s2_))); + } + + @Registries(64) + @Parallel + @Test + public void parallelAccess() { + // Do a few iterations of this test + for (int i = 0; i < 5; i++) { + if (i > 0) { + // We destroyed the fixture the last time around + createFixture(); + } + + registries().forEach(this::start); + + Service1Impl s1 = assumeUniqueServiceInstance(Service1.class, Service1Impl.class); + Service2Impl s2 = assumeUniqueServiceInstance(Service2.class, Service2Impl.class); + Service3Impl s3 = assumeUniqueServiceInstance(Service3.class, Service3Impl.class); + + destroyFixture(); + + assertThat(s1.startedCount.get(), is(1)); + assertThat(s1.disposedCount.get(), is(1)); + + assertThat(s2.startedCount.get(), is(1)); + assertThat(s2.disposedCount.get(), is(1)); + + assertThat(s3.startedCount.get(), is(1)); + assertThat(s3.disposedCount.get(), is(1)); + } + } + + // + // Test framework + // + + @Before + public void createFixture() { + if (parallelRegistries.get()) { + sync = new ParallelSynchronizer(); + } else { + // No-op synchronizer + sync = () -> { + }; + } + + registries = createServiceRegistries(registryCount.get()); + } + + @After + public void destroyFixture() { + if (registries != null) { + try { + registries().forEach(this::dispose); + } finally { + registries = null; + } + } + + sync = null; + } + + Stream<ServicesRegistry> registries() { + return parallelRegistries.get() ? registries.parallelStream() : registries.stream(); + } + + <S, T extends S> T assertUniqueServiceInstance(Class<S> serviceInterface, Class<T> serviceImpl) { + List<S> result = registries() + .map(r -> getService(r, serviceInterface)) + .distinct() + .collect(Collectors.toList()); + assertThat(result.size(), is(1)); + return serviceImpl.cast(result.get(0)); + } + + <S, T extends S> T assumeUniqueServiceInstance(Class<S> serviceInterface, Class<T> serviceImpl) { + List<S> result = registries() + .map(r -> getService(r, serviceInterface)) + .distinct() + .collect(Collectors.toList()); + Assume.assumeThat(result.size(), is(1)); + return serviceImpl.cast(result.get(0)); + } + + <S> S getService(ServicesRegistry registry, Class<S> serviceInterface) { + try { + return registry.getService(serviceInterface); + } catch (ServiceException e) { + throw new RuntimeException(e); + } + } + + List<ServicesRegistry> createServiceRegistries(int count) { + return IntStream.range(0, count).mapToObj(__ -> createServicesRegistry()).collect(Collectors.toList()); + } + + ServicesRegistry createServicesRegistry() { + ServicesRegistry result = new ServicesRegistry(); + result.add(new ServicesRegistryTest.ServiceFactoryDesc(Service1.class, Service1Factory.class.getName(), ServiceStartKind.LAZY)); + result.add(new ServicesRegistryTest.ServiceFactoryDesc(Service2.class, Service2Factory.class.getName(), ServiceStartKind.LAZY)); + result.add(new ServicesRegistryTest.ServiceFactoryDesc(Service3.class, Service3Factory.class.getName(), ServiceStartKind.LAZY)); + return result; + } + + void start(ServicesRegistry registry) { + try { + registry.startRegistry(); + } catch (ServiceException e) { + throw new RuntimeException(e); + } + } + + void dispose(ServicesRegistry registry) { + try { + registry.disposeRegistry(); + } catch (ServiceException e) { + throw new RuntimeException(e); + } + } + + public interface Service1 extends IService { + // Nothing more + } + + static class Service1Impl implements Service1 { + AtomicInteger initializedCount = new AtomicInteger(); + AtomicInteger startedCount = new AtomicInteger(); + AtomicInteger disposedCount = new AtomicInteger(); + + { + sync.sync(); + } + + @Override + public void init(ServicesRegistry servicesRegistry) throws ServiceException { + initializedCount.incrementAndGet(); + } + + @Override + public void startService() throws ServiceException { + sync.sync(); + startedCount.incrementAndGet(); + } + + @Override + public void disposeService() throws ServiceException { + sync.sync(); + disposedCount.incrementAndGet(); + } + + } + + public static class Service1Factory extends SharedServiceFactory<Service1> { + + public Service1Factory() { + super(Service1.class, Service1Impl::new); + } + + } + + public interface Service2 { + // Empty + } + + static class Service2Impl implements Service2 { + AtomicInteger startedCount = new AtomicInteger(); + AtomicInteger disposedCount = new AtomicInteger(); + + { + sync.sync(); + } + + void start() throws ServiceException { + sync.sync(); + startedCount.incrementAndGet(); + } + + void dispose() throws ServiceException { + sync.sync(); + disposedCount.incrementAndGet(); + } + + } + + public static class Service2Factory extends SharedServiceFactory<Service2> { + + public Service2Factory() { + super(Service2.class); + } + + @Override + protected Service2 createSharedInstance() { + return new Service2Impl(); + } + + @Override + protected void start(Service2 sharedInstance) throws ServiceException { + ((Service2Impl) sharedInstance).start(); + } + + @Override + protected void dispose(Service2 sharedInstance) throws ServiceException { + ((Service2Impl) sharedInstance).dispose(); + } + } + + public interface Service3 { + // Empty + } + + static class Service3Impl implements Service3 { + AtomicInteger startedCount = new AtomicInteger(); + AtomicInteger disposedCount = new AtomicInteger(); + + { + sync.sync(); + } + + void start() throws ServiceException { + sync.sync(); + startedCount.incrementAndGet(); + } + + void dispose() throws ServiceException { + sync.sync(); + disposedCount.incrementAndGet(); + } + + } + + public static class Service3Factory extends SharedServiceFactory<Service3> { + + public Service3Factory() { + super(Service3.class, Service3Impl::new); + } + + @Override + protected void start(Service3 sharedInstance) throws ServiceException { + ((Service3Impl) sharedInstance).start(); + } + + @Override + protected void dispose(Service3 sharedInstance) throws ServiceException { + ((Service3Impl) sharedInstance).dispose(); + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface Registries { + int value() default DEFAULT_REGISTRY_COUNT; + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface Parallel { + // Empty + } + + @FunctionalInterface + interface Synchronizer { + void sync(); + } + + /** + * A synchronizer that introduces short random delays into the + * execution of a parallel test case. + */ + static final class ParallelSynchronizer implements Synchronizer { + final Random random = new Random(System.currentTimeMillis()); + + @Override + public void sync() { + try { + Thread.sleep(random.nextInt(100) + 20L); + } catch (Exception e) { + e.printStackTrace(); + // Never mind. Just proceed + } + } + } +} diff --git a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java index c8e600342f9..063762cff96 100644 --- a/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java +++ b/tests/junit/plugins/core/org.eclipse.papyrus.infra.core.tests/test/org/eclipse/papyrus/infra/core/tests/AllTests.java @@ -10,7 +10,7 @@ * Remi Schnekenburger (CEA LIST) remi.schnekenburger@cea.fr - Initial API and implementation
* Christian W. Damus (CEA LIST) - add test for AdapterUtils
* Christian W. Damus (CEA) - bugs 402525, 422257, 399859
- * Christian W. Damus - bugs 456934, 468030, 482949, 485220
+ * Christian W. Damus - bugs 456934, 468030, 482949, 485220, 488558
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.tests;
@@ -23,6 +23,7 @@ import org.eclipse.papyrus.infra.core.resource.ResourceAdapterTest; import org.eclipse.papyrus.infra.core.resource.ResourceAdapterTransactionalTest;
import org.eclipse.papyrus.infra.core.services.ComposedServiceTest;
import org.eclipse.papyrus.infra.core.services.ServicesRegistryTest;
+import org.eclipse.papyrus.infra.core.services.SharedServiceFactoryTest;
import org.eclipse.papyrus.infra.core.utils.AdapterUtilsTest;
import org.eclipse.papyrus.infra.core.utils.JobBasedFutureTest;
import org.eclipse.papyrus.infra.core.utils.JobExecutorServiceTest;
@@ -42,7 +43,7 @@ import org.junit.runners.Suite.SuiteClasses; // {oep}.core.language
LanguageServiceTest.class,
// {oep}.core.services
- ComposedServiceTest.class, ServicesRegistryTest.class,
+ ComposedServiceTest.class, ServicesRegistryTest.class, SharedServiceFactoryTest.class,
// {oep}.core.utils
AdapterUtilsTest.class, JobBasedFutureTest.class, JobExecutorServiceTest.class
})
|