diff --git a/src/main/java/org/fest/reflect/simple/SimpleInjector.java b/src/main/java/org/fest/reflect/simple/SimpleInjector.java new file mode 100644 index 0000000..cfa9263 --- /dev/null +++ b/src/main/java/org/fest/reflect/simple/SimpleInjector.java @@ -0,0 +1,79 @@ +package org.fest.reflect.simple; + +import static java.text.MessageFormat.format; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides simple injection capabilities when needed in tests. To be used by {@link SimpleReflection}. + * + * @author Marek Dominiak + */ +public class SimpleInjector { + private final Object valueToSet; + + static SimpleInjector set(Object value) { + return new SimpleInjector(value); + } + + private SimpleInjector(Object value) { + this.valueToSet = value; + } + + /** + * Sets {@link #valueToSet} into target object if it is possible. + * Rules for setting: + * + * @param target object into which we try to inject + * @throws IllegalStateException when there are none or more than one fields of "source" object in "target" type. + */ + public void in(Object target) { + Class targetClass = target.getClass(); + Field[] fields = targetClass.getDeclaredFields(); + List matchingFields = getAllFieldsAssignableFrom(fields); + checkThatNumberOfFieldsOfValueTypeIsValid(targetClass, matchingFields); + Field fieldToInjectTo = matchingFields.get(0); + setValueOnField(fieldToInjectTo, target); + } + + private List getAllFieldsAssignableFrom(Field[] fields) { + List result = new ArrayList(); + for (Field field : fields) { + if (field.getType().isAssignableFrom(valueToSet.getClass())) { + result.add(field); + } + } + return result; + } + + private void checkThatNumberOfFieldsOfValueTypeIsValid(Class targetClass, + List matchingFields) { + if (matchingFields.isEmpty()) { + String errorMessage = format("There must be exactly ONE field of type {0} (or assignable from {0}) in target class {1}!\nBut found none.", valueToSet.getClass().getName(), targetClass.getName()); + throw new IllegalStateException(errorMessage); + } + + if (matchingFields.size() > 1) { + String errorMessageListOfFields = "" + matchingFields.size() + ":\n"; + for (Field field : matchingFields) { + errorMessageListOfFields += field.getName() + "\n"; + } + String errorMessage = format("There must be exactly ONE field of type {0} (or assignable from {0}) in target class {1}!\nBut found {2}", valueToSet.getClass().getName(), targetClass.getName(), errorMessageListOfFields); + throw new IllegalStateException(errorMessage); + } + } + + private void setValueOnField(Field found, Object target) { + found.setAccessible(true); + try { + found.set(target, valueToSet); + } catch (final IllegalAccessException ex) { + throw new IllegalStateException("Unexpected reflection exception - " + ex.getClass().getName() + ": " + + ex.getMessage()); + } + } +} diff --git a/src/main/java/org/fest/reflect/simple/SimpleReflection.java b/src/main/java/org/fest/reflect/simple/SimpleReflection.java new file mode 100644 index 0000000..8b5e76a --- /dev/null +++ b/src/main/java/org/fest/reflect/simple/SimpleReflection.java @@ -0,0 +1,58 @@ +/* + * Created on Oct 13, 2012 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" + * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * Copyright @2006-2012 the original author or authors. + */ +package org.fest.reflect.simple; + +import org.fest.reflect.core.Reflection; + +/** + * Provides a "fluent" API that makes usage of the Java Reflection API + * to help achieve some common case scenarios when setting/retrieving state of + * the objects in tests. If some more advanced scenarios are needed use {@link Reflection} instead. + *

+ * Here are some examples: + *

+ *    // import static {@link org.fest.reflect.simple.SimpleReflection org.fest.reflect.simple.SimpleReflection}.*;
+ * 
+ *    // Retrieves the value of the field "logger" from object userService
+ *    UserService userService = ...
+ *    Logger logger = SimpleReflection.get(Logger.class).from(userService);
+ *    
+ *    // Sets the value of the field 'logger' to "Yoda"
+ *    UserService userService = ...
+ *    Logger logger = ...
+ *    Logger logger = SimpleReflection.set(logger).to(userService);
+ * 
+ * + * @author Marek Dominiak + */ +public class SimpleReflection { + /** + * Begins the setting/injecting of the valueToSet to some object. + * @param valueToSet + * @return fluent API interface to set/inject valueToSet to some object. + */ + public static SimpleInjector set(Object valueToSet) { + return SimpleInjector.set(valueToSet); + } + + /** + * Begins the getting/retrieving of the field value of the provided type from some object. + * @param fieldValueType + * @return fluent API interface to get/retrieve field value of the provided type from some object. + */ + public static SimpleRetriever get(Class fieldValueType) { + return new SimpleRetriever(fieldValueType); + } +} diff --git a/src/main/java/org/fest/reflect/simple/SimpleRetriever.java b/src/main/java/org/fest/reflect/simple/SimpleRetriever.java new file mode 100644 index 0000000..597ce24 --- /dev/null +++ b/src/main/java/org/fest/reflect/simple/SimpleRetriever.java @@ -0,0 +1,71 @@ +package org.fest.reflect.simple; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides simple retrieving capabilities when needed in tests. To be used by {@link SimpleReflection}. + * + * @author Marek Dominiak + */ +public class SimpleRetriever { + + private final Class typeToRetrieve; + + public SimpleRetriever(Class typeToRetrieve) { + this.typeToRetrieve = typeToRetrieve; + } + + /** + * Retrieves the value of field of type {@link #typeToRetrieve} from the "source" object. + * + * Rules for retrieving: + *
    + *
  • there must be EXACTLY ONE field in "source" object which is of type which is assignable from {@link #typeToRetrieve} type
  • + *
+ * @param source object from which we are retrieving the value + * @throws IllegalStateException when there are none or more than one fields of {@link #typeToRetrieve} type in "source" object type. + */ + public T from(Object source) { + Field[] declaredFields = source.getClass().getDeclaredFields(); + List allPossibleFields = getAllFieldsAssignableFrom(declaredFields); + checkThatThereIsOnlyOneFieldWithCompatibleType(source, allPossibleFields); + return retrieveFieldValueFromBean(source, allPossibleFields.get(0)); + } + + private void checkThatThereIsOnlyOneFieldWithCompatibleType(Object source, List allPossibleFields) { + if (allPossibleFields.size() > 1) { + throw new IllegalStateException("Can't retrieve value of type: " + typeToRetrieve.getName() + + " because there are more fields of this type in source bean (" + source.getClass().getName() + + ")"); + } + if (allPossibleFields.size() == 0) { + throw new IllegalStateException("Can't retrieve value of type: " + typeToRetrieve.getName() + + " because there none fields of this type (or compatible type) in source bean (" + + source.getClass().getName() + ")"); + } + } + + @SuppressWarnings("unchecked") + private T retrieveFieldValueFromBean(Object source, Field field) { + field.setAccessible(true); + Object object = null; + try { + object = field.get(source); + } catch (Exception e) { + throw new IllegalStateException("Cannot retrieve value " + e.getMessage()); + } + return (T) object; + } + + private List getAllFieldsAssignableFrom(Field[] fields) { + List result = new ArrayList(); + for (Field field : fields) { + if (field.getType().isAssignableFrom(typeToRetrieve)) { + result.add(field); + } + } + return result; + } +} diff --git a/src/main/java/org/fest/reflect/simple/package.html b/src/main/java/org/fest/reflect/simple/package.html new file mode 100644 index 0000000..b3214e0 --- /dev/null +++ b/src/main/java/org/fest/reflect/simple/package.html @@ -0,0 +1,40 @@ + + + + + + + +

+Provides a "fluent" API that +makes usage of the Java Reflection API to help achieve some common case scenarios when setting/retrieving state of the objects in tests. +

+

+Here are some examples: +

+    // import static org.fest.reflect.simple.SimpleReflection.*;
+ 
+    // Retrieves the value of the field "logger" from object userService
+    UserService userService = ...
+    Logger logger = SimpleReflection.get(Logger.class).from(userService);
+    
+    // Sets the value of the field 'logger' to "Yoda"
+    UserService userService = ...
+    Logger logger = ...
+    Logger logger = SimpleReflection.set(logger).to(userService);
+
+

+ + \ No newline at end of file diff --git a/src/test/java/org/fest/reflect/simple/SimpleReflectionTest.java b/src/test/java/org/fest/reflect/simple/SimpleReflectionTest.java new file mode 100644 index 0000000..2600295 --- /dev/null +++ b/src/test/java/org/fest/reflect/simple/SimpleReflectionTest.java @@ -0,0 +1,167 @@ +package org.fest.reflect.simple; + +import junit.framework.Assert; + +import org.fest.test.CodeToTest; +import org.fest.test.ExpectedFailure; +import org.junit.Test; + +public class SimpleReflectionTest { + + @SuppressWarnings("unused") + class ClassWithTwoFieldsOfTheSameType { + private Crowdy crowdy; + private Crowdy reallyCrowdy; + public ClassWithTwoFieldsOfTheSameType(Crowdy crowdy, Crowdy reallyCrowdy) { + super(); + this.crowdy = crowdy; + this.reallyCrowdy = reallyCrowdy; + } + public ClassWithTwoFieldsOfTheSameType() { + super(); + } + } + + class ClassWithExactlyOneField { + private Logger logger; + + public Logger getLogger() { + return logger; + } + } + + class ClassWithExactlyOneCompatibleField { + private SomeInterface someInterface; + + public SomeInterface getSomeInterface() { + return someInterface; + } + } + + class CompletelyEmpty { + } + + class Logger { + } + + interface SomeInterface { + } + + class ClassWithInterface implements SomeInterface { + } + + class Crowdy { + } + + @Test + public void shouldInjectFieldByTypeInAnObject() throws Exception { + // given + ClassWithExactlyOneField exactlyOneField = new ClassWithExactlyOneField(); + Logger logger = new Logger(); + + // when + SimpleReflection.set(logger).in(exactlyOneField); + + // then + Assert.assertEquals(logger, exactlyOneField.getLogger()); + } + + @Test + public void shouldInjectFieldByTypeInAnObjectWhenTheInjectedValueIfOfTypeExtendingTypeOfTargetField() + throws Exception { + // given + ClassWithExactlyOneCompatibleField exactlyOneCompatibleField = new ClassWithExactlyOneCompatibleField(); + ClassWithInterface classWithInterface = new ClassWithInterface(); + + // when + SimpleReflection.set(classWithInterface).in(exactlyOneCompatibleField); + + // then + Assert.assertEquals(classWithInterface, exactlyOneCompatibleField.getSomeInterface()); + } + + @Test + public void shouldThrowISEWhenInjectingValueOfTypeWhenThereAreNoneFieldsOfThisTypeInTargetObject() throws Exception { + final CompletelyEmpty completelyEmpty = new CompletelyEmpty(); + final Logger logger = new Logger(); + ExpectedFailure + .expect(IllegalStateException.class) + .withMessage( + "There must be exactly ONE field of type " + Logger.class.getName() + " (or assignable from "+ Logger.class.getName() + ") in target class " + + CompletelyEmpty.class.getName() + "!\nBut found none.").on(new CodeToTest() { + public void run() throws Throwable { + SimpleReflection.set(logger).in(completelyEmpty); + } + }); + } + + @Test + public void shouldThrowISEWhenInjectingValueOfTypeWhenThereAreMoreFieldsOfThisTypeInTargetObject() throws Exception { + final Crowdy crowdy = new Crowdy(); + final ClassWithTwoFieldsOfTheSameType classWithTwoFieldsOfTheSameType = new ClassWithTwoFieldsOfTheSameType(); + ExpectedFailure + .expect(IllegalStateException.class) + .withMessage( + "There must be exactly ONE field of type " + Crowdy.class.getName() + " (or assignable from " + Crowdy.class.getName() + ") in target class " + + ClassWithTwoFieldsOfTheSameType.class.getName() + "!\nBut found 2:\ncrowdy\nreallyCrowdy\n") + .on(new CodeToTest() { + public void run() throws Throwable { + SimpleReflection.set(crowdy).in(classWithTwoFieldsOfTheSameType); + } + }); + } + + @Test + public void shouldRetrieveFieldByTypeInAnObjectWhenOnlyOneFieldOfThisTypeExist() throws Exception { + // given + ClassWithExactlyOneField exactlyOneField = new ClassWithExactlyOneField(); + Logger logger = new Logger(); + SimpleReflection.set(logger).in(exactlyOneField); + + // when + Logger result = SimpleReflection.get(Logger.class).from(exactlyOneField); + // then + Assert.assertEquals(logger, result); + } + + @Test + public void shouldRetrieveFieldValueFromSourceObjectByTypeWhenSourceClassHasCompatibleField() throws Exception { + // given + // compatible = from the inheritance hierarchy + ClassWithExactlyOneCompatibleField classWithExactlyOneCompatibleField = new ClassWithExactlyOneCompatibleField(); + ClassWithInterface value = new ClassWithInterface(); + SimpleReflection.set(value).in(classWithExactlyOneCompatibleField); + // when + SomeInterface result = SimpleReflection.get(SomeInterface.class).from(classWithExactlyOneCompatibleField); + // then + Assert.assertEquals(value, result); + } + + @Test + public void shouldThrowISEWhenTryingToGetFieldByTypeInAnObjectWhenThereAreMoreFieldsOfThisTypeInSourceObject() throws Exception { + // given + Crowdy crowdy1 = new Crowdy(); + Crowdy crowdy2 = new Crowdy(); + final ClassWithTwoFieldsOfTheSameType source = new ClassWithTwoFieldsOfTheSameType(crowdy1, crowdy2); + ExpectedFailure + .expect(IllegalStateException.class) + .withMessage("Can't retrieve value of type: " + Crowdy.class.getName() + " because there are more fields of this type in source bean (" + ClassWithTwoFieldsOfTheSameType.class.getName() + ")").on(new CodeToTest() { + public void run() throws Throwable { + SimpleReflection.get(Crowdy.class).from(source); + } + }); + } + + @Test + public void shouldThrowISEWhenTryingToGetFieldByTypeInAnObjectWhichDoesNotDefineFieldOfThisType() throws Exception { + // given + final CompletelyEmpty source = new CompletelyEmpty(); + ExpectedFailure + .expect(IllegalStateException.class) + .withMessage("Can't retrieve value of type: " + Crowdy.class.getName() + " because there none fields of this type (or compatible type) in source bean (" + CompletelyEmpty.class.getName() + ")").on(new CodeToTest() { + public void run() throws Throwable { + SimpleReflection.get(Crowdy.class).from(source); + } + }); + } +}