Add template for assignment 2
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package dst.ass2.ioc.di;
|
||||
|
||||
import dst.ass2.ioc.di.annotation.Property;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public interface IObjectContainer {
|
||||
|
||||
/**
|
||||
* Returns the (mutable) data structure holding property values that can be injected via the
|
||||
* {@link Property} annotation.
|
||||
*
|
||||
* @return A mutable Properties object
|
||||
*/
|
||||
Properties getProperties();
|
||||
|
||||
/**
|
||||
* Returns a container-managed object of the given type.
|
||||
*
|
||||
* @param type the type of object
|
||||
* @param <T> the class type
|
||||
* @return the object
|
||||
* @throws InjectionException throw the concrete InjectionException as specified in the assignment
|
||||
*/
|
||||
<T> T getObject(Class<T> type) throws InjectionException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package dst.ass2.ioc.di;
|
||||
|
||||
import dst.ass2.ioc.di.annotation.Property;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public interface IObjectContainerFactory {
|
||||
|
||||
/**
|
||||
* Creates a new IObjectContainer instance that uses the given properties for injecting
|
||||
* {@link Property} instances.
|
||||
*
|
||||
* @param properties a Properties object
|
||||
* @return a new IObjectContainer instance
|
||||
*/
|
||||
IObjectContainer newObjectContainer(Properties properties);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package dst.ass2.ioc.di;
|
||||
|
||||
public class InjectionException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InjectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InjectionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InjectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dst.ass2.ioc.di;
|
||||
|
||||
public class InvalidDeclarationException extends InjectionException {
|
||||
public InvalidDeclarationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidDeclarationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InvalidDeclarationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dst.ass2.ioc.di;
|
||||
|
||||
public class ObjectCreationException extends InjectionException {
|
||||
public ObjectCreationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ObjectCreationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ObjectCreationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dst.ass2.ioc.di;
|
||||
|
||||
public class TypeConversionException extends InjectionException {
|
||||
public TypeConversionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TypeConversionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public TypeConversionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package dst.ass2.ioc.di.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a class to be container managed.
|
||||
*/
|
||||
// TODO: add correct retention policy and target type
|
||||
public @interface Component {
|
||||
|
||||
/**
|
||||
* The component can either be marked to be a singleton or a prototype via the {@link Scope} enum. The default scope
|
||||
* is singleton.
|
||||
*
|
||||
* @return the scope
|
||||
*/
|
||||
Scope scope() default Scope.SINGLETON;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package dst.ass2.ioc.di.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Initialize marks a method to be invoked by the container after it has been constructed and all dependencies and
|
||||
* properties have been injected.
|
||||
*/
|
||||
// TODO: add correct retention policy and target type
|
||||
public @interface Initialize {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package dst.ass2.ioc.di.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Inject marks a field in a class to be autowired by the container.
|
||||
*/
|
||||
// TODO: add correct retention policy and target type
|
||||
public @interface Inject {
|
||||
|
||||
/**
|
||||
* Marks whether the dependency is required or not.
|
||||
*
|
||||
* @return a boolean value
|
||||
*/
|
||||
boolean optional() default false;
|
||||
|
||||
/**
|
||||
* Explicitly states the class that should be injected by the container into this field. If the target type is Void,
|
||||
* then we assume the targetType is not set, and the container uses the declared field type instead.
|
||||
*
|
||||
* @return a class
|
||||
*/
|
||||
Class<?> targetType() default Void.class;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package dst.ass2.ioc.di.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A property injects the value of a {@link java.util.Properties} object into a field based on a key.
|
||||
*/
|
||||
// TODO: add correct retention policy and target type
|
||||
public @interface Property {
|
||||
|
||||
/**
|
||||
* The key to look up in the container's Properties object.
|
||||
*
|
||||
* @return a key
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dst.ass2.ioc.di.annotation;
|
||||
|
||||
/**
|
||||
* The scope of a {@link Component}.
|
||||
*/
|
||||
public enum Scope {
|
||||
SINGLETON,
|
||||
PROTOTYPE
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package dst.ass2.ioc.di.impl;
|
||||
|
||||
import dst.ass2.ioc.di.IObjectContainer;
|
||||
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
public final class ObjectContainerFactory implements IObjectContainerFactory {
|
||||
|
||||
@Override
|
||||
public IObjectContainer newObjectContainer(Properties properties) {
|
||||
// TODO: implement
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package dst.ass2.ioc.lock;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a method to be executed by acquiring a named lock.
|
||||
*/
|
||||
// TODO: add correct retention policy and target type
|
||||
public @interface Lock {
|
||||
|
||||
/**
|
||||
* The name of the container managed lock.
|
||||
*
|
||||
* @return a name
|
||||
*/
|
||||
String value();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package dst.ass2.ioc.lock;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
public class LockingInjector implements ClassFileTransformer {
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className,
|
||||
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
|
||||
byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
|
||||
// TODO transform all @Lock annotated methods of classes with a @Component annotation
|
||||
|
||||
return classfileBuffer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package dst.ass2.ioc.lock;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
public class LockingInjectorAgent {
|
||||
|
||||
public static void premain(String args, Instrumentation inst) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package dst.ass2.ioc.tests;
|
||||
|
||||
import dst.ass2.ioc.tests.di.DependencyInjectionTest;
|
||||
import dst.ass2.ioc.tests.di.HierarchyTest;
|
||||
import dst.ass2.ioc.tests.di.InitializeTest;
|
||||
import dst.ass2.ioc.tests.di.PropertyInjectionTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
DependencyInjectionTest.class,
|
||||
PropertyInjectionTest.class,
|
||||
HierarchyTest.class,
|
||||
InitializeTest.class
|
||||
})
|
||||
public class Ass2_2_1_Suite {
|
||||
// suite
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package dst.ass2.ioc.tests;
|
||||
|
||||
import dst.ass2.ioc.tests.lock.LockingTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
LockingTest.class
|
||||
})
|
||||
public class Ass2_2_2_Suite {
|
||||
// suite
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package dst.ass2.ioc.tests.di;
|
||||
|
||||
public class CustomInitializeTest {
|
||||
|
||||
/*
|
||||
* TODO: In cases where the object hierarchy has multiple methods annotated with @Initialize or overwrites
|
||||
* previously annotated methods, you should come up with your own behavior. Document the behavior with at least two
|
||||
* unit tests.
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package dst.ass2.ioc.tests.di;
|
||||
|
||||
|
||||
import dst.ass2.ioc.di.IObjectContainer;
|
||||
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||
import dst.ass2.ioc.di.InjectionException;
|
||||
import dst.ass2.ioc.di.InvalidDeclarationException;
|
||||
import dst.ass2.ioc.di.annotation.Component;
|
||||
import dst.ass2.ioc.di.annotation.Inject;
|
||||
import dst.ass2.ioc.di.annotation.Scope;
|
||||
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
|
||||
public class DependencyInjectionTest {
|
||||
|
||||
private IObjectContainerFactory factory;
|
||||
private IObjectContainer container;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
factory = new ObjectContainerFactory();
|
||||
container = factory.newObjectContainer(new Properties());
|
||||
|
||||
if (container == null) {
|
||||
throw new NullPointerException("ObjectContainerFactory did not return an ObjectContainer instance");
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class SimpleSingleton {
|
||||
public String someValue;
|
||||
|
||||
public SimpleSingleton() {
|
||||
// just to verify that the constructor has been called correctly
|
||||
this.someValue = "42";
|
||||
}
|
||||
}
|
||||
|
||||
public static class NotAComponentClass {
|
||||
// a plain class shouldn't be autowired
|
||||
|
||||
@Inject
|
||||
SimpleSingleton singleton;
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDeclarationException.class)
|
||||
public void getObject_onNotAnnotatedClass_throwsException() {
|
||||
container.getObject(NotAComponentClass.class);
|
||||
}
|
||||
|
||||
|
||||
@Component
|
||||
public static abstract class AbstractComponentClass {
|
||||
// abstract classes can't be instantiated
|
||||
}
|
||||
|
||||
@Test(expected = InjectionException.class)
|
||||
public void getObject_onAbstractClass_throwsException() throws Exception {
|
||||
container.getObject(AbstractComponentClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_onSimpleSingleton_createsObjectCorrectly() {
|
||||
var object = container.getObject(SimpleSingleton.class);
|
||||
assertNotNull(object);
|
||||
assertThat(object, instanceOf(SimpleSingleton.class));
|
||||
assertEquals("constructor was not called correctly", "42", object.someValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_onSimpleSingleton_returnsSameObject() {
|
||||
var object1 = container.getObject(SimpleSingleton.class);
|
||||
var object2 = container.getObject(SimpleSingleton.class);
|
||||
|
||||
assertNotNull(object1);
|
||||
assertNotNull(object2);
|
||||
assertSame(object1, object2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_onDifferentContainer_returnsDifferentObject() {
|
||||
var container1 = factory.newObjectContainer(new Properties());
|
||||
var object1 = container1.getObject(SimpleSingleton.class);
|
||||
|
||||
var container2 = factory.newObjectContainer(new Properties());
|
||||
var object2 = container2.getObject(SimpleSingleton.class);
|
||||
|
||||
assertNotNull(object1);
|
||||
assertNotNull(object2);
|
||||
assertNotSame(object1, object2);
|
||||
}
|
||||
|
||||
@Component(scope = Scope.PROTOTYPE)
|
||||
public static class SimplePrototype {
|
||||
|
||||
static final AtomicInteger COUNTER = new AtomicInteger(0);
|
||||
|
||||
public int cnt;
|
||||
|
||||
public SimplePrototype() {
|
||||
// just to verify that the constructor has been called correctly
|
||||
cnt = SimplePrototype.COUNTER.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_onSimplePrototype_returnsDifferentObjects() {
|
||||
SimplePrototype.COUNTER.set(0);
|
||||
var object1 = container.getObject(SimplePrototype.class);
|
||||
var object2 = container.getObject(SimplePrototype.class);
|
||||
|
||||
assertNotNull(object1);
|
||||
assertNotNull(object2);
|
||||
assertNotSame(object1, object2);
|
||||
|
||||
assertEquals(1, object1.cnt);
|
||||
assertEquals(2, object2.cnt);
|
||||
}
|
||||
|
||||
@Component(scope = Scope.PROTOTYPE)
|
||||
public static class CompositePrototype {
|
||||
|
||||
@Inject
|
||||
SimpleSingleton simpleSingleton;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_onCompositePrototype_createsObjectCorrectly() {
|
||||
var object = container.getObject(CompositePrototype.class);
|
||||
assertNotNull(object.simpleSingleton);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_onCompositePrototype_injectsPreviouslyCreatedSingleton() {
|
||||
var singleton = container.getObject(SimpleSingleton.class);
|
||||
|
||||
var object = container.getObject(CompositePrototype.class);
|
||||
assertNotNull(object.simpleSingleton);
|
||||
|
||||
assertSame("autowire should use already container managed singletons", singleton, object.simpleSingleton);
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class CompositeSingleton {
|
||||
@Inject
|
||||
SimpleSingleton simpleSingleton;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_onCompositeSingleton_createsObjectGraphCorrectly() {
|
||||
var object = container.getObject(CompositeSingleton.class);
|
||||
|
||||
assertNotNull(object);
|
||||
assertThat(object, instanceOf(CompositeSingleton.class));
|
||||
|
||||
assertNotNull("dependency of CompositeSingleton not initialized", object.simpleSingleton);
|
||||
assertNotNull("dependency of CompositeSingleton not initialized correctly", object.simpleSingleton.someValue);
|
||||
}
|
||||
|
||||
public interface IService {
|
||||
int getSomeValue();
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ServiceImpl implements IService {
|
||||
private int someValue;
|
||||
|
||||
public ServiceImpl() {
|
||||
someValue = 42;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSomeValue() {
|
||||
return someValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ServiceUser {
|
||||
@Inject(targetType = ServiceImpl.class)
|
||||
IService service;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_usingTargetType_createsGraphCorrectly() throws Exception {
|
||||
var object = container.getObject(ServiceUser.class);
|
||||
|
||||
assertNotNull(object);
|
||||
assertNotNull("dependency of ServiceUser not initialized", object.service);
|
||||
assertThat("dependency of ServiceUser not initialized correctly", object.service, instanceOf(ServiceImpl.class));
|
||||
assertEquals("dependency of ServiceUser not initialized correctly", 42, object.service.getSomeValue());
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class InvalidServiceUser {
|
||||
// trying to inject an existing but invalid type (a SimplePrototype is not an IService)
|
||||
@Inject(targetType = SimplePrototype.class)
|
||||
IService service;
|
||||
}
|
||||
|
||||
@Test(expected = InvalidDeclarationException.class)
|
||||
public void getObject_onInvalidTargetType_throwsException() {
|
||||
container.getObject(InvalidServiceUser.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package dst.ass2.ioc.tests.di;
|
||||
|
||||
import dst.ass2.ioc.di.IObjectContainer;
|
||||
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||
import dst.ass2.ioc.di.annotation.Component;
|
||||
import dst.ass2.ioc.di.annotation.Inject;
|
||||
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
|
||||
public class HierarchyTest {
|
||||
|
||||
private IObjectContainerFactory factory;
|
||||
private IObjectContainer container;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
factory = new ObjectContainerFactory();
|
||||
container = factory.newObjectContainer(new Properties());
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class SimpleSingleton {
|
||||
public String someValue;
|
||||
|
||||
public SimpleSingleton() {
|
||||
// just to verify that the constructor has been called correctly
|
||||
this.someValue = "42";
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class AbstractComponent {
|
||||
@Inject
|
||||
protected SimpleSingleton superSingleton;
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ConcreteComponent extends AbstractComponent {
|
||||
@Inject
|
||||
private SimpleSingleton privateSingleton;
|
||||
|
||||
public SimpleSingleton getSuperSingleton() {
|
||||
return superSingleton;
|
||||
}
|
||||
|
||||
public SimpleSingleton getPrivateSingleton() {
|
||||
return privateSingleton;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_injectsDependencyOfSuperClassCorrectly() throws Exception {
|
||||
ConcreteComponent component = container.getObject(ConcreteComponent.class);
|
||||
|
||||
assertNotNull("getObject returned null", component);
|
||||
assertNotNull("Dependency of superclass was not injected", component.getSuperSingleton());
|
||||
assertNotNull("Private dependency was not injected", component.getPrivateSingleton());
|
||||
assertSame("Multiple singleton instances", component.getSuperSingleton(), component.getPrivateSingleton());
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ClassA {
|
||||
@Inject
|
||||
SimpleSingleton singletonA;
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ClassB extends ClassA {
|
||||
|
||||
@Inject
|
||||
SimpleSingleton singletonB;
|
||||
|
||||
@Inject
|
||||
ClassD classD;
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ClassC {
|
||||
|
||||
@Inject
|
||||
SimpleSingleton singletonC;
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ClassD extends ClassC {
|
||||
|
||||
@Inject
|
||||
SimpleSingleton singletonD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the following hierarchy. Getting first D then B should should inject the previously created D into B.
|
||||
*
|
||||
* +-------+ +-------+
|
||||
* | A | | C |
|
||||
* +-------+ +-------+
|
||||
* ^ is a ^ is a
|
||||
* | |
|
||||
* +-------+ uses +-------+
|
||||
* | B | ---> | D |
|
||||
* +-------+ +-------+
|
||||
*/
|
||||
@Test
|
||||
public void getObject_initializesHierarchyCorrectly() throws Exception {
|
||||
ClassD d = container.getObject(ClassD.class);
|
||||
|
||||
assertNotNull("getObject returned null for ClassD", d);
|
||||
assertNotNull("ClassD dependency was not injected", d.singletonD);
|
||||
assertNotNull("ClassC dependency was not injected when instantiating ClassD", d.singletonC);
|
||||
|
||||
ClassB b = container.getObject(ClassB.class);
|
||||
assertNotNull("getObject returned null for ClassB", b);
|
||||
assertNotNull("ClassB dependency was not injected", b.singletonB);
|
||||
assertNotNull("ClassA dependency was not injected when instantiating ClassB", b.singletonA);
|
||||
|
||||
assertNotNull("ClassD dependency was not injected into ClassB", b.classD);
|
||||
assertSame("Container did not re-use already initialized ClassD instance", b.classD, d);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package dst.ass2.ioc.tests.di;
|
||||
|
||||
import dst.ass2.ioc.di.IObjectContainer;
|
||||
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||
import dst.ass2.ioc.di.annotation.Component;
|
||||
import dst.ass2.ioc.di.annotation.Initialize;
|
||||
import dst.ass2.ioc.di.annotation.Inject;
|
||||
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class InitializeTest {
|
||||
|
||||
private IObjectContainerFactory factory;
|
||||
private IObjectContainer container;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
factory = new ObjectContainerFactory();
|
||||
container = factory.newObjectContainer(new Properties());
|
||||
|
||||
if (container == null) {
|
||||
throw new NullPointerException("ObjectContainerFactory did not return an ObjectContainer instance");
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ComponentClass {
|
||||
|
||||
private AtomicInteger initializeCalls = new AtomicInteger(0);
|
||||
|
||||
@Initialize
|
||||
public void myInitMethod() {
|
||||
initializeCalls.incrementAndGet();
|
||||
}
|
||||
|
||||
public int getInitializeCalls() {
|
||||
return initializeCalls.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_runsInitializeMethodCorrectly() throws Exception {
|
||||
ComponentClass component = container.getObject(ComponentClass.class);
|
||||
assertEquals("expected exactly one call to myInitMethod", 1, component.getInitializeCalls());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_runsInitializeMethodOnlyOnce() throws Exception {
|
||||
ComponentClass component = container.getObject(ComponentClass.class);
|
||||
assertEquals("expected exactly one call to myInitMethod", 1, component.getInitializeCalls());
|
||||
|
||||
component = container.getObject(ComponentClass.class);
|
||||
assertEquals("expected exactly one call to myInitMethod", 1, component.getInitializeCalls());
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class AnotherComponentClass {
|
||||
|
||||
@Inject
|
||||
private ComponentClass dependency;
|
||||
|
||||
boolean wasNull;
|
||||
|
||||
@Initialize
|
||||
public void myOtherInitMethod() {
|
||||
this.wasNull = dependency == null;
|
||||
}
|
||||
|
||||
public ComponentClass getDependency() {
|
||||
return dependency;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_runsInitializeMethodAfterInjection() throws Exception {
|
||||
AnotherComponentClass component = container.getObject(AnotherComponentClass.class);
|
||||
assertFalse("Expected dependency to be injected before calls to @Initialize", component.wasNull);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_runsInitializeMethodOfDependencyCorrectly() throws Exception {
|
||||
AnotherComponentClass component = container.getObject(AnotherComponentClass.class);
|
||||
assertEquals("expected exactly one call to myInitMethod", 1, component.getDependency().getInitializeCalls());
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class ComponentWithPrivateInitMethod {
|
||||
|
||||
private AtomicInteger initializeCalls = new AtomicInteger(0);
|
||||
|
||||
@Initialize
|
||||
private void myPrivateInitMethod() {
|
||||
initializeCalls.incrementAndGet();
|
||||
}
|
||||
|
||||
public int getInitializeCalls() {
|
||||
return initializeCalls.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getObject_runsPrivateInitializeMethodCorrectly() throws Exception {
|
||||
ComponentWithPrivateInitMethod component = container.getObject(ComponentWithPrivateInitMethod.class);
|
||||
assertEquals("expected exactly one call to myPrivateInitMethod", 1, component.getInitializeCalls());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package dst.ass2.ioc.tests.di;
|
||||
|
||||
import dst.ass2.ioc.di.IObjectContainerFactory;
|
||||
import dst.ass2.ioc.di.ObjectCreationException;
|
||||
import dst.ass2.ioc.di.TypeConversionException;
|
||||
import dst.ass2.ioc.di.annotation.Component;
|
||||
import dst.ass2.ioc.di.annotation.Property;
|
||||
import dst.ass2.ioc.di.annotation.Scope;
|
||||
import dst.ass2.ioc.di.impl.ObjectContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.allOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class PropertyInjectionTest {
|
||||
|
||||
private IObjectContainerFactory factory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
factory = new ObjectContainerFactory();
|
||||
}
|
||||
|
||||
@Component
|
||||
static class StringPropertyClass {
|
||||
@Property("my_string")
|
||||
String value;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_string_value_injection() {
|
||||
var properties = new Properties();
|
||||
properties.setProperty("my_string", "my string value");
|
||||
var container = factory.newObjectContainer(properties);
|
||||
|
||||
StringPropertyClass obj = container.getObject(StringPropertyClass.class);
|
||||
assertNotNull("getObject(StringPropertyClass.class) returned null", obj);
|
||||
assertEquals("my string value", obj.value);
|
||||
}
|
||||
|
||||
@Test(expected = ObjectCreationException.class)
|
||||
public void wireMissingProperty_throwsException() {
|
||||
var container = factory.newObjectContainer(new Properties());
|
||||
container.getObject(StringPropertyClass.class);
|
||||
}
|
||||
|
||||
@Component
|
||||
static class IntegerPropertyClass {
|
||||
@Property("my_integer")
|
||||
Integer value;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_integer_value_injection() {
|
||||
var properties = new Properties();
|
||||
properties.setProperty("my_integer", "42");
|
||||
var container = factory.newObjectContainer(properties);
|
||||
|
||||
IntegerPropertyClass obj = container.getObject(IntegerPropertyClass.class);
|
||||
assertNotNull("getObject(IntegerPropertyClass.class) returned null", obj);
|
||||
assertEquals(Integer.valueOf(42), obj.value);
|
||||
}
|
||||
|
||||
@Test(expected = TypeConversionException.class)
|
||||
public void wireInvalidPropertyType_throwsException() {
|
||||
var properties = new Properties();
|
||||
properties.setProperty("my_integer", "fourtytwo");
|
||||
var container = factory.newObjectContainer(properties);
|
||||
|
||||
IntegerPropertyClass obj = container.getObject(IntegerPropertyClass.class);
|
||||
assertNotNull("getObject(IntegerPropertyClass.class) returned null", obj);
|
||||
assertEquals(Integer.valueOf(42), obj.value);
|
||||
}
|
||||
|
||||
@Component
|
||||
static class IntegerPrimitivePropertyClass {
|
||||
@Property("my_integer")
|
||||
int value;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_integer_primitive_value_injection() {
|
||||
var properties = new Properties();
|
||||
properties.setProperty("my_integer", "42");
|
||||
var container = factory.newObjectContainer(properties);
|
||||
|
||||
IntegerPrimitivePropertyClass obj = container.getObject(IntegerPrimitivePropertyClass.class);
|
||||
assertNotNull("getObject(IntegerPrimitivePropertyClass.class) returned null", obj);
|
||||
assertEquals(42, obj.value);
|
||||
}
|
||||
|
||||
@Component
|
||||
static class MultiplePropertyClass {
|
||||
@Property("my_string")
|
||||
String stringValue;
|
||||
|
||||
@Property("my_integer")
|
||||
int intValue;
|
||||
|
||||
@Property("my_integer")
|
||||
Integer integerValue;
|
||||
|
||||
@Property("my_float")
|
||||
Float floatValue;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simple_multiple_property_injection() {
|
||||
var properties = new Properties();
|
||||
properties.setProperty("my_string", "my string value");
|
||||
properties.setProperty("my_integer", "42");
|
||||
properties.setProperty("my_float", "3.14");
|
||||
var container = factory.newObjectContainer(properties);
|
||||
|
||||
MultiplePropertyClass obj = container.getObject(MultiplePropertyClass.class);
|
||||
assertNotNull("getObject(MultiplePropertyClass.class) returned null", obj);
|
||||
|
||||
assertEquals("my string value", obj.stringValue);
|
||||
assertEquals(42, obj.intValue);
|
||||
assertEquals(Integer.valueOf(42), obj.integerValue);
|
||||
assertEquals(Float.valueOf(3.14f), obj.floatValue);
|
||||
}
|
||||
|
||||
@Component
|
||||
static class InheritedMultiplePropertyClass extends MultiplePropertyClass {
|
||||
@Property("my_float")
|
||||
Float theSameFloatValue;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multiple_property_injection_with_inheritance() {
|
||||
var properties = new Properties();
|
||||
properties.setProperty("my_string", "my string value");
|
||||
properties.setProperty("my_integer", "42");
|
||||
properties.setProperty("my_float", "3.14");
|
||||
var container = factory.newObjectContainer(properties);
|
||||
|
||||
InheritedMultiplePropertyClass obj = container.getObject(InheritedMultiplePropertyClass.class);
|
||||
assertNotNull("getObject(InheritedMultiplePropertyClass.class) returned null", obj);
|
||||
|
||||
assertEquals(Float.valueOf(3.14f), obj.theSameFloatValue);
|
||||
|
||||
// inherited values
|
||||
assertEquals("Property not set correctly through inheritance", "my string value", obj.stringValue);
|
||||
assertEquals("Property not set correctly through inheritance", 42, obj.intValue);
|
||||
assertEquals("Property not set correctly through inheritance", Integer.valueOf(42), obj.integerValue);
|
||||
assertEquals("Property not set correctly through inheritance", Float.valueOf(3.14f), obj.floatValue);
|
||||
}
|
||||
|
||||
@Component(scope = Scope.PROTOTYPE)
|
||||
static class PrototypePropertyClass {
|
||||
|
||||
@Property("my_string")
|
||||
String stringValue;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPrototypeObject_usesLatestPropertyValue() {
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("my_string", "initial value");
|
||||
var container = factory.newObjectContainer(properties);
|
||||
|
||||
var object1 = container.getObject(PrototypePropertyClass.class);
|
||||
assertEquals("initial value", object1.stringValue);
|
||||
|
||||
// Properties returned by IObjectContainerFactory should be mutable
|
||||
container.getProperties().setProperty("my_string", "changed value");
|
||||
|
||||
var object2 = container.getObject(PrototypePropertyClass.class);
|
||||
assertEquals("properties should be mutable", "changed value", object2.stringValue);
|
||||
assertEquals("initial value", object1.stringValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Component
|
||||
static class ManyValuesClass {
|
||||
@Property("my_string")
|
||||
String stringValue01;
|
||||
@Property("my_integer")
|
||||
Integer value01;
|
||||
@Property("my_integer")
|
||||
Integer value02;
|
||||
@Property("my_integer")
|
||||
Integer value03;
|
||||
@Property("my_integer")
|
||||
Integer value04;
|
||||
@Property("my_integer")
|
||||
Integer value05;
|
||||
@Property("my_string")
|
||||
String stringValue02;
|
||||
@Property("my_integer")
|
||||
Integer value06;
|
||||
@Property("my_integer")
|
||||
Integer value07;
|
||||
@Property("my_integer")
|
||||
Integer value08;
|
||||
@Property("my_integer")
|
||||
Integer value09;
|
||||
@Property("my_integer")
|
||||
Integer value10;
|
||||
@Property("my_string")
|
||||
String stringValue03;
|
||||
@Property("my_integer")
|
||||
Integer value11;
|
||||
@Property("my_integer")
|
||||
Integer value12;
|
||||
@Property("my_integer")
|
||||
Integer value13;
|
||||
@Property("my_integer")
|
||||
Integer value14;
|
||||
@Property("my_integer")
|
||||
Integer value15;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void mutatePropertiesConcurrently_verifyThreadSafety() throws Exception {
|
||||
var container = factory.newObjectContainer(new Properties());
|
||||
|
||||
Properties properties = container.getProperties();
|
||||
properties.setProperty("my_integer", "1");
|
||||
properties.setProperty("my_string", "some string value");
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
CountDownLatch latch = new CountDownLatch(10);
|
||||
|
||||
executor.execute(() -> {
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
latch.countDown();
|
||||
properties.setProperty("my_integer", Integer.toString(i));
|
||||
}
|
||||
});
|
||||
Future<ManyValuesClass> getObject = executor.submit(() -> {
|
||||
latch.await(); // wait until the property set loop has started and warmed up
|
||||
return container.getObject(ManyValuesClass.class);
|
||||
});
|
||||
|
||||
ManyValuesClass object = getObject.get();
|
||||
executor.shutdown();
|
||||
executor.awaitTermination(2, TimeUnit.SECONDS);
|
||||
executor.shutdownNow();
|
||||
|
||||
assertNotEquals("updated property value was not used", Integer.valueOf(1), object.value01);
|
||||
assertThat("property values are inconsistent indicating non-thread-safe property injection",
|
||||
object.value01, allOf(
|
||||
is(object.value01), is(object.value02), is(object.value03), is(object.value04),
|
||||
is(object.value05), is(object.value06), is(object.value07), is(object.value08),
|
||||
is(object.value09), is(object.value10), is(object.value11), is(object.value12),
|
||||
is(object.value13), is(object.value14), is(object.value15)
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
package dst.ass2.ioc.tests.lock;
|
||||
|
||||
import dst.ass2.ioc.di.annotation.Component;
|
||||
import dst.ass2.ioc.lock.Lock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
|
||||
public class LockingTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LockingTest.class);
|
||||
|
||||
@Rule
|
||||
public ThreadPoolResource executor = new ThreadPoolResource(2);
|
||||
|
||||
/**
|
||||
* This class simulates the use of a shared resource (the semaphore). If the resource is already acquired
|
||||
* by some other thread, then a {@link ConcurrentModificationException} is thrown. We use this behavior to test
|
||||
* various scenarios (whether mutex access is guaranteed when the @Lock value refers to the same lock).
|
||||
*/
|
||||
@Component
|
||||
public static class SimpleLockManaged {
|
||||
|
||||
public Semaphore semaphore;
|
||||
|
||||
public SimpleLockManaged(Semaphore semaphore) {
|
||||
this.semaphore = semaphore;
|
||||
}
|
||||
|
||||
@Lock("my_lock")
|
||||
public void useResource() {
|
||||
useResourceImpl();
|
||||
}
|
||||
|
||||
@Lock("my_other_lock")
|
||||
public void useResourceSomeMore() {
|
||||
useResourceImpl();
|
||||
}
|
||||
|
||||
private void useResourceImpl() {
|
||||
LOG.info("{} trying to acquire semaphore", this);
|
||||
boolean acquired = semaphore.tryAcquire();
|
||||
|
||||
if (!acquired) {
|
||||
LOG.info("{} failed to acquire semaphore", this);
|
||||
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||
}
|
||||
|
||||
// hold the permit for a few ms
|
||||
try {
|
||||
LOG.info("{} holding permit for 500ms", this);
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
LOG.info("{} releasing semaphore", this);
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mutexTest_onSameClassAndObject_behavesCorrectly() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
SimpleLockManaged lockManaged = new SimpleLockManaged(semaphore);
|
||||
|
||||
Future<?> r1 = executor.submit(lockManaged::useResource);
|
||||
Future<?> r2 = executor.submit(lockManaged::useResource);
|
||||
|
||||
try {
|
||||
r1.get();
|
||||
r2.get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mutexTest_onSameClassButDifferentObject_behavesCorrectly() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
SimpleLockManaged lockManaged1 = new SimpleLockManaged(semaphore);
|
||||
SimpleLockManaged lockManaged2 = new SimpleLockManaged(semaphore);
|
||||
|
||||
Future<?> r1 = executor.submit(lockManaged1::useResource);
|
||||
Future<?> r2 = executor.submit(lockManaged2::useResource);
|
||||
|
||||
try {
|
||||
r1.get();
|
||||
r2.get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonMutexTest_onSameClassAndObject_behavesCorrectly() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
SimpleLockManaged lockManaged = new SimpleLockManaged(semaphore);
|
||||
|
||||
Future<?> r1 = executor.submit(lockManaged::useResource);
|
||||
Future<?> r2 = executor.submit(lockManaged::useResourceSomeMore);
|
||||
|
||||
try {
|
||||
r1.get();
|
||||
r2.get();
|
||||
throw new AssertionError("Different locks should allow concurrent access to resources");
|
||||
} catch (ExecutionException e) {
|
||||
if (!(e.getCause() instanceof ConcurrentModificationException)) {
|
||||
throw e;
|
||||
}
|
||||
// we expected a concurrent modification exception here
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonMutexTest_onSameClassButDifferentObject_behavesCorrectly() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
SimpleLockManaged lockManaged1 = new SimpleLockManaged(semaphore);
|
||||
SimpleLockManaged lockManaged2 = new SimpleLockManaged(semaphore);
|
||||
|
||||
Future<?> r1 = executor.submit(lockManaged1::useResource);
|
||||
Future<?> r2 = executor.submit(lockManaged2::useResourceSomeMore);
|
||||
|
||||
try {
|
||||
r1.get();
|
||||
r2.get();
|
||||
throw new AssertionError("Different locks should allow concurrent access to resources");
|
||||
} catch (ExecutionException e) {
|
||||
if (!(e.getCause() instanceof ConcurrentModificationException)) {
|
||||
throw e;
|
||||
}
|
||||
// we expected a concurrent modification exception here
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class AnotherSimpleLockManaged {
|
||||
|
||||
private Semaphore semaphore;
|
||||
|
||||
public AnotherSimpleLockManaged(Semaphore semaphore) {
|
||||
this.semaphore = semaphore;
|
||||
}
|
||||
|
||||
@Lock("my_lock")
|
||||
protected void useResourceAgain() {
|
||||
LOG.info("{} trying to acquire semaphore", this);
|
||||
boolean acquired = semaphore.tryAcquire();
|
||||
|
||||
if (!acquired) {
|
||||
LOG.info("{} failed to acquire semaphore", this);
|
||||
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||
}
|
||||
|
||||
// hold the permit for a few ms
|
||||
try {
|
||||
LOG.info("{} holding permit for 500ms", this);
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
LOG.info("{} releasing semaphore", this);
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mutexTest_onDifferentClass_behavesCorrectly() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
SimpleLockManaged lockManaged1 = new SimpleLockManaged(semaphore);
|
||||
AnotherSimpleLockManaged lockManaged2 = new AnotherSimpleLockManaged(semaphore);
|
||||
|
||||
Future<?> r1 = executor.submit(lockManaged1::useResource);
|
||||
Future<?> r2 = executor.submit(lockManaged2::useResourceAgain);
|
||||
|
||||
try {
|
||||
r1.get();
|
||||
r2.get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is annotated with @Lock, but does not have a @Component annotation, so it should not be instrumented.
|
||||
*/
|
||||
public static class NotLockManaged {
|
||||
|
||||
public Semaphore semaphore;
|
||||
|
||||
public NotLockManaged(Semaphore semaphore) {
|
||||
this.semaphore = semaphore;
|
||||
}
|
||||
|
||||
@Lock("my_lock_0")
|
||||
public void useResource() {
|
||||
LOG.info("{} trying to acquire semaphore", this);
|
||||
boolean acquired = semaphore.tryAcquire();
|
||||
|
||||
if (!acquired) {
|
||||
LOG.info("{} failed to acquire semaphore", this);
|
||||
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||
}
|
||||
|
||||
// hold the permit for a few ms
|
||||
try {
|
||||
LOG.info("{} holding permit for 500ms", this);
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
LOG.info("{} releasing semaphore", this);
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnNonComponent_shouldNotBeInstrumented() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
NotLockManaged notManaged = new NotLockManaged(semaphore);
|
||||
|
||||
Future<?> r1 = executor.submit(notManaged::useResource);
|
||||
Future<?> r2 = executor.submit(notManaged::useResource);
|
||||
|
||||
try {
|
||||
r1.get();
|
||||
r2.get();
|
||||
throw new AssertionError("Only classes with the @Component annotation should be instrumented");
|
||||
} catch (ExecutionException e) {
|
||||
if (!(e.getCause() instanceof ConcurrentModificationException)) {
|
||||
throw e;
|
||||
}
|
||||
// we expected a concurrent modification exception here
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class LockManagedWithParameter {
|
||||
|
||||
@Lock("my_lock_1")
|
||||
protected void useResource(Semaphore semaphore) {
|
||||
LOG.info("{} trying to acquire semaphore", this);
|
||||
boolean acquired = semaphore.tryAcquire();
|
||||
|
||||
if (!acquired) {
|
||||
LOG.info("{} failed to acquire semaphore", this);
|
||||
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||
}
|
||||
|
||||
// hold the permit for a few ms
|
||||
try {
|
||||
LOG.info("{} holding permit for 500ms", this);
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
LOG.info("{} releasing semaphore", this);
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mutexTest_withParameter_behavesCorrectly() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
LockManagedWithParameter lockManaged = new LockManagedWithParameter();
|
||||
|
||||
Future<?> r1 = executor.submit(() -> lockManaged.useResource(semaphore));
|
||||
Future<?> r2 = executor.submit(() -> lockManaged.useResource(semaphore));
|
||||
|
||||
try {
|
||||
r1.get();
|
||||
r2.get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component
|
||||
public static class LockManagedWithReturnValue {
|
||||
|
||||
public Semaphore semaphore;
|
||||
|
||||
public LockManagedWithReturnValue(Semaphore semaphore) {
|
||||
this.semaphore = semaphore;
|
||||
}
|
||||
|
||||
@Lock("my_lock")
|
||||
public Integer useResource() {
|
||||
useResourceImpl();
|
||||
return 42;
|
||||
}
|
||||
|
||||
private void useResourceImpl() {
|
||||
LOG.info("{} trying to acquire semaphore", this);
|
||||
boolean acquired = semaphore.tryAcquire();
|
||||
|
||||
if (!acquired) {
|
||||
LOG.info("{} failed to acquire semaphore", this);
|
||||
throw new ConcurrentModificationException("Semaphore was acquired concurrently");
|
||||
}
|
||||
|
||||
// hold the permit for a few ms
|
||||
try {
|
||||
LOG.info("{} holding permit for 500ms", this);
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
LOG.info("{} releasing semaphore", this);
|
||||
semaphore.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mutexTest_withReturnValue_behavesCorrectly() throws Exception {
|
||||
Semaphore semaphore = new Semaphore(1);
|
||||
|
||||
LockManagedWithReturnValue lockManaged = new LockManagedWithReturnValue(semaphore);
|
||||
|
||||
Future<Integer> r1 = executor.submit(lockManaged::useResource);
|
||||
Future<Integer> r2 = executor.submit(lockManaged::useResource);
|
||||
|
||||
try {
|
||||
Integer i1 = r1.get();
|
||||
Integer i2 = r2.get();
|
||||
|
||||
Assert.assertEquals(Integer.valueOf(42), i1);
|
||||
Assert.assertEquals(Integer.valueOf(42), i2);
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component
|
||||
public static class LockManagedWithParamAndReturnValue {
|
||||
|
||||
private Integer i = 0;
|
||||
|
||||
@Lock("my_lock_2")
|
||||
public Integer checkAndIncrement(Integer test) throws RuntimeException {
|
||||
System.out.println(Thread.currentThread().getName() + " getting value " + i);
|
||||
int x = i;
|
||||
|
||||
if (i < test) {
|
||||
// this condition will always be true in the tests, just to add some logic
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
x = x + 1;
|
||||
i = x;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This test verifies that the instrumentation also works when the function has input parameters and return values.
|
||||
*/
|
||||
@Test
|
||||
public void mutexTest_withParamAndReturnValue_behavesCorrectly() throws Exception {
|
||||
LockManagedWithParamAndReturnValue managed = new LockManagedWithParamAndReturnValue();
|
||||
|
||||
Future<Integer> r1 = executor.submit(() -> managed.checkAndIncrement(10));
|
||||
Future<Integer> r2 = executor.submit(() -> managed.checkAndIncrement(10));
|
||||
|
||||
try {
|
||||
Integer i1 = r1.get();
|
||||
Integer i2 = r2.get();
|
||||
|
||||
Assert.assertNotEquals("Result of concurrent increment is equal, indicating a race condition", i1, i2);
|
||||
if (i1 < i2) {
|
||||
Assert.assertEquals(Integer.valueOf(1), i1);
|
||||
Assert.assertEquals(Integer.valueOf(2), i2);
|
||||
} else {
|
||||
Assert.assertEquals(Integer.valueOf(2), i1);
|
||||
Assert.assertEquals(Integer.valueOf(1), i2);
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof ConcurrentModificationException) {
|
||||
throw new AssertionError("Access to shared resource was not mutually exclusive as required", e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package dst.ass2.ioc.tests.lock;
|
||||
|
||||
import org.junit.rules.ExternalResource;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Simple JUnit Rule wrapper of a fixed thread pool {@link ExecutorService} that shuts down after each test execution.
|
||||
*/
|
||||
public class ThreadPoolResource extends ExternalResource {
|
||||
|
||||
private ExecutorService executor;
|
||||
private int nThreads;
|
||||
|
||||
public ThreadPoolResource(int nThreads) {
|
||||
this.nThreads = nThreads;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void before() {
|
||||
executor = Executors.newFixedThreadPool(nThreads);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
shutdownAndAwaitTermination(executor);
|
||||
}
|
||||
|
||||
public Future<?> submit(Runnable task) {
|
||||
return executor.submit(task);
|
||||
}
|
||||
|
||||
public <T> Future<T> submit(Callable<T> task) {
|
||||
return executor.submit(task);
|
||||
}
|
||||
|
||||
public ExecutorService getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
private void shutdownAndAwaitTermination(ExecutorService pool) {
|
||||
System.out.println("shutting down pool");
|
||||
pool.shutdown(); // Disable new tasks from being submitted
|
||||
try {
|
||||
// Wait a while for existing tasks to terminate
|
||||
System.out.println("awaiting pool termination");
|
||||
if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
pool.shutdownNow(); // Cancel currently executing tasks
|
||||
// Wait a while for tasks to respond to being cancelled
|
||||
if (!pool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
System.err.println("Pool did not terminate");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
// (Re-)Cancel if current thread also interrupted
|
||||
pool.shutdownNow();
|
||||
// Preserve interrupt status
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user