diff options
author | Paul Duffin <paulduffin@google.com> | 2017-03-03 10:59:02 +0000 |
---|---|---|
committer | Paul Duffin <paulduffin@google.com> | 2017-03-07 13:42:56 +0000 |
commit | d174d376bccaf52c3273473bcc34bdb11b50bd67 (patch) | |
tree | 1fc35d29b265d03aec96fc5c74f050365a2695b5 /src/main/java/org/mockito/internal/util | |
parent | 134e857e0377f553c0be5f0344238ade69c5de53 (diff) | |
download | mockito-d174d376bccaf52c3273473bcc34bdb11b50bd67.tar.gz |
Restructure files to match upstream mockito
Makes the file structure match upstream mockito so that it is
simpler to update in future. Moving the files as a separate
step to upgrading makes it easier for git to track renames and
simplifies future diffs and reviews.
Bug: 32912773
Test: make checkbuild
Change-Id: I884ba2cc2856fd19d31bdb658fa058c47d078982
Diffstat (limited to 'src/main/java/org/mockito/internal/util')
39 files changed, 2634 insertions, 0 deletions
diff --git a/src/main/java/org/mockito/internal/util/Checks.java b/src/main/java/org/mockito/internal/util/Checks.java new file mode 100644 index 0000000..00ed8a8 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/Checks.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockito.internal.util; + +/** + * Pre-made preconditions + */ +public class Checks { + + public static <T> T checkNotNull(T value, String checkedValue) { + if(value == null) { + throw new NullPointerException(checkedValue + " should not be null"); + } + return value; + } + + public static <T extends Iterable> T checkItemsNotNull(T iterable, String checkedIterable) { + checkNotNull(iterable, checkedIterable); + for (Object item : iterable) { + checkNotNull(item, "item in " + checkedIterable); + } + return iterable; + } +} diff --git a/src/main/java/org/mockito/internal/util/ConsoleMockitoLogger.java b/src/main/java/org/mockito/internal/util/ConsoleMockitoLogger.java new file mode 100644 index 0000000..80cac5c --- /dev/null +++ b/src/main/java/org/mockito/internal/util/ConsoleMockitoLogger.java @@ -0,0 +1,15 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util;
+
+public class ConsoleMockitoLogger implements MockitoLogger {
+
+ /* (non-Javadoc)
+ * @see org.mockito.internal.util.Logger#print(java.lang.Object)
+ */
+ public void log(Object what) {
+ System.out.println(what);
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/Decamelizer.java b/src/main/java/org/mockito/internal/util/Decamelizer.java new file mode 100644 index 0000000..9bc34be --- /dev/null +++ b/src/main/java/org/mockito/internal/util/Decamelizer.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockito.internal.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Decamelizer { + +private static final Pattern CAPS = Pattern.compile("([A-Z\\d][^A-Z\\d]*)"); + + public static String decamelizeMatcher(String className) { + if (className.length() == 0) { + return "<custom argument matcher>"; + } + + String decamelized = decamelizeClassName(className); + + if (decamelized.length() == 0) { + return "<" + className + ">"; + } + + return "<" + decamelized + ">"; + } + + private static String decamelizeClassName(String className) { + Matcher match = CAPS.matcher(className); + StringBuilder deCameled = new StringBuilder(); + while(match.find()) { + if (deCameled.length() == 0) { + deCameled.append(match.group()); + } else { + deCameled.append(" "); + deCameled.append(match.group().toLowerCase()); + } + } + return deCameled.toString(); + } +} diff --git a/src/main/java/org/mockito/internal/util/DefaultMockingDetails.java b/src/main/java/org/mockito/internal/util/DefaultMockingDetails.java new file mode 100644 index 0000000..e361c6e --- /dev/null +++ b/src/main/java/org/mockito/internal/util/DefaultMockingDetails.java @@ -0,0 +1,45 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util;
+
+import org.mockito.MockingDetails;
+import org.mockito.invocation.Invocation;
+
+import java.util.Collection;
+
+/**
+ * Class to inspect any object, and identify whether a particular object is either a mock or a spy. This is
+ * a wrapper for {@link org.mockito.internal.util.MockUtil}.
+ */
+public class DefaultMockingDetails implements MockingDetails {
+
+ private final Object toInspect;
+ private final MockUtil delegate;
+
+ public DefaultMockingDetails(Object toInspect, MockUtil delegate){
+ this.toInspect = toInspect;
+ this.delegate = delegate;
+ }
+ /**
+ * Find out whether the object is a mock.
+ * @return true if the object is a mock or a spy.
+ */
+ public boolean isMock(){
+ return delegate.isMock( toInspect );
+ }
+
+ /**
+ * Find out whether the object is a spy.
+ * @return true if the object is a spy.
+ */
+ public boolean isSpy(){
+ return delegate.isSpy( toInspect );
+ }
+
+ public Collection<Invocation> getInvocations() {
+ return delegate.getMockHandler(toInspect).getInvocationContainer().getInvocations();
+ }
+}
+
diff --git a/src/main/java/org/mockito/internal/util/MockCreationValidator.java b/src/main/java/org/mockito/internal/util/MockCreationValidator.java new file mode 100644 index 0000000..35d99d9 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/MockCreationValidator.java @@ -0,0 +1,73 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util;
+
+import org.mockito.exceptions.Reporter;
+import org.mockito.internal.util.reflection.Constructors;
+import org.mockito.mock.SerializableMode;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+@SuppressWarnings("unchecked")
+public class MockCreationValidator {
+
+ private final MockUtil mockUtil = new MockUtil();
+
+ public void validateType(Class classToMock) {
+ if (!mockUtil.isTypeMockable(classToMock)) {
+ new Reporter().cannotMockFinalClass(classToMock);
+ }
+ }
+
+ public void validateExtraInterfaces(Class classToMock, Collection<Class> extraInterfaces) {
+ if (extraInterfaces == null) {
+ return;
+ }
+
+ for (Class i : extraInterfaces) {
+ if (classToMock == i) {
+ new Reporter().extraInterfacesCannotContainMockedType(classToMock);
+ }
+ }
+ }
+
+ public void validateMockedType(Class classToMock, Object spiedInstance) {
+ if (classToMock == null || spiedInstance == null) {
+ return;
+ }
+ if (!classToMock.equals(spiedInstance.getClass())) {
+ new Reporter().mockedTypeIsInconsistentWithSpiedInstanceType(classToMock, spiedInstance);
+ }
+ }
+
+ public void validateDelegatedInstance(Class classToMock, Object delegatedInstance) {
+ if (classToMock == null || delegatedInstance == null) {
+ return;
+ }
+ if (delegatedInstance.getClass().isAssignableFrom(classToMock)) {
+ new Reporter().mockedTypeIsInconsistentWithDelegatedInstanceType(classToMock, delegatedInstance);
+ }
+ }
+
+ public void validateSerializable(Class classToMock, boolean serializable) {
+ // We can't catch all the errors with this piece of code
+ // Having a **superclass that do not implements Serializable** might fail as well when serialized
+ // Though it might prevent issues when mockito is mocking a class without superclass.
+ if(serializable
+ && !classToMock.isInterface()
+ && !(Serializable.class.isAssignableFrom(classToMock))
+ && Constructors.noArgConstructorOf(classToMock) == null
+ ) {
+ new Reporter().serializableWontWorkForObjectsThatDontImplementSerializable(classToMock);
+ }
+ }
+
+ public void validateConstructorUse(boolean usingConstructor, SerializableMode mode) {
+ if (usingConstructor && mode == SerializableMode.ACROSS_CLASSLOADERS) {
+ new Reporter().usingConstructorWithFancySerializable(mode);
+ }
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/MockNameImpl.java b/src/main/java/org/mockito/internal/util/MockNameImpl.java new file mode 100644 index 0000000..d8654f3 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/MockNameImpl.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util;
+
+import org.mockito.mock.MockName;
+
+import java.io.Serializable;
+
+public class MockNameImpl implements MockName, Serializable {
+
+ private static final long serialVersionUID = 8014974700844306925L;
+ private final String mockName;
+ private boolean defaultName;
+
+ @SuppressWarnings("unchecked")
+ public MockNameImpl(String mockName, Class classToMock) {
+ if (mockName == null) {
+ this.mockName = toInstanceName(classToMock);
+ this.defaultName = true;
+ } else {
+ this.mockName = mockName;
+ }
+ }
+
+ public MockNameImpl(String mockName) {
+ this.mockName = mockName;
+ }
+
+ private static String toInstanceName(Class<?> clazz) {
+ String className = clazz.getSimpleName();
+ if (className.length() == 0) {
+ //it's an anonymous class, let's get name from the parent
+ className = clazz.getSuperclass().getSimpleName();
+ }
+ //lower case first letter
+ return className.substring(0, 1).toLowerCase() + className.substring(1);
+ }
+
+ public boolean isDefault() {
+ return defaultName;
+ }
+
+ @Override
+ public String toString() {
+ return mockName;
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/MockUtil.java b/src/main/java/org/mockito/internal/util/MockUtil.java new file mode 100644 index 0000000..433be78 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/MockUtil.java @@ -0,0 +1,92 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util;
+
+import org.mockito.Mockito;
+import org.mockito.exceptions.misusing.NotAMockException;
+import org.mockito.internal.InternalMockHandler;
+import org.mockito.internal.configuration.plugins.Plugins;
+import org.mockito.internal.creation.settings.CreationSettings;
+import org.mockito.internal.handler.MockHandlerFactory;
+import org.mockito.internal.util.reflection.LenientCopyTool;
+import org.mockito.invocation.MockHandler;
+import org.mockito.mock.MockCreationSettings;
+import org.mockito.mock.MockName;
+import org.mockito.plugins.MockMaker;
+
+import java.lang.reflect.Modifier;
+
+@SuppressWarnings("unchecked")
+public class MockUtil {
+
+ private static final MockMaker mockMaker = Plugins.getMockMaker();
+
+ public boolean isTypeMockable(Class<?> type) {
+ return !type.isPrimitive() && !Modifier.isFinal(type.getModifiers());
+ }
+
+ public <T> T createMock(MockCreationSettings<T> settings) {
+ MockHandler mockHandler = new MockHandlerFactory().create(settings);
+
+ T mock = mockMaker.createMock(settings, mockHandler);
+
+ Object spiedInstance = settings.getSpiedInstance();
+ if (spiedInstance != null) {
+ new LenientCopyTool().copyToMock(spiedInstance, mock);
+ }
+
+ return mock;
+ }
+
+ public <T> void resetMock(T mock) {
+ InternalMockHandler oldHandler = (InternalMockHandler) getMockHandler(mock);
+ MockCreationSettings settings = oldHandler.getMockSettings();
+ MockHandler newHandler = new MockHandlerFactory().create(settings);
+
+ mockMaker.resetMock(mock, newHandler, settings);
+ }
+
+ public <T> InternalMockHandler<T> getMockHandler(T mock) {
+ if (mock == null) {
+ throw new NotAMockException("Argument should be a mock, but is null!");
+ }
+
+ if (isMockitoMock(mock)) {
+ MockHandler handler = mockMaker.getHandler(mock);
+ return (InternalMockHandler) handler;
+ } else {
+ throw new NotAMockException("Argument should be a mock, but is: " + mock.getClass());
+ }
+ }
+
+ public boolean isMock(Object mock) {
+ // double check to avoid classes that have the same interfaces, could be great to have a custom mockito field in the proxy instead of relying on instance fields
+ return isMockitoMock(mock);
+ }
+
+ public boolean isSpy(Object mock) {
+ return isMockitoMock(mock) && getMockSettings(mock).getDefaultAnswer() == Mockito.CALLS_REAL_METHODS;
+ }
+
+ private <T> boolean isMockitoMock(T mock) {
+ return mockMaker.getHandler(mock) != null;
+ }
+
+ public MockName getMockName(Object mock) {
+ return getMockHandler(mock).getMockSettings().getMockName();
+ }
+
+ public void maybeRedefineMockName(Object mock, String newName) {
+ MockName mockName = getMockName(mock);
+ //TODO SF hacky...
+ if (mockName.isDefault() && getMockHandler(mock).getMockSettings() instanceof CreationSettings) {
+ ((CreationSettings) getMockHandler(mock).getMockSettings()).setMockName(new MockNameImpl(newName));
+ }
+ }
+
+ public MockCreationSettings getMockSettings(Object mock) {
+ return getMockHandler(mock).getMockSettings();
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/MockitoLogger.java b/src/main/java/org/mockito/internal/util/MockitoLogger.java new file mode 100644 index 0000000..31117d8 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/MockitoLogger.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util;
+
+public interface MockitoLogger {
+
+ void log(Object what);
+
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/ObjectMethodsGuru.java b/src/main/java/org/mockito/internal/util/ObjectMethodsGuru.java new file mode 100644 index 0000000..4e78249 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/ObjectMethodsGuru.java @@ -0,0 +1,44 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util;
+
+import org.mockito.internal.creation.DelegatingMethod;
+import org.mockito.internal.invocation.MockitoMethod;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+
+public class ObjectMethodsGuru implements Serializable {
+
+ private static final long serialVersionUID = -1286718569065470494L;
+
+ public boolean isToString(Method method) {
+ return isToString(new DelegatingMethod(method));
+ }
+
+ public boolean isToString(MockitoMethod method) {
+ return method.getReturnType() == String.class
+ && method.getParameterTypes().length == 0
+ && method.getName().equals("toString");
+ }
+
+ public boolean isEqualsMethod(Method method) {
+ return method.getName().equals("equals")
+ && method.getParameterTypes().length == 1
+ && method.getParameterTypes()[0] == Object.class;
+ }
+
+ public boolean isHashCodeMethod(Method method) {
+ return method.getName().equals("hashCode")
+ && method.getParameterTypes().length == 0;
+ }
+
+ public boolean isCompareToMethod(Method method) {
+ return Comparable.class.isAssignableFrom(method.getDeclaringClass())
+ && method.getName().equals("compareTo")
+ && method.getParameterTypes().length == 1
+ && method.getParameterTypes()[0] == method.getDeclaringClass();
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/Primitives.java b/src/main/java/org/mockito/internal/util/Primitives.java new file mode 100644 index 0000000..4dcc07e --- /dev/null +++ b/src/main/java/org/mockito/internal/util/Primitives.java @@ -0,0 +1,87 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SuppressWarnings("unchecked")
+public class Primitives {
+
+ private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPES = new HashMap<Class<?>, Class<?>>();
+ private static final Map<Class<?>, Object> PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES = new HashMap<Class<?>, Object>();
+
+
+ /**
+ * Returns the primitive type of the given class.
+ * <p/>
+ * The passed class can be any class : <code>boolean.class</code>, <code>Integer.class</code>
+ * in witch case this method will return <code>boolean.class</code>, even <code>SomeObject.class</code>
+ * in which case <code>null</code> will be returned.
+ *
+ * @param clazz The class from which primitive type has to be retrieved
+ * @param <T> The type
+ * @return The primitive type if relevant, otherwise <code>null</code>
+ */
+ public static <T> Class<T> primitiveTypeOf(Class<T> clazz) {
+ if (clazz.isPrimitive()) {
+ return clazz;
+ }
+ return (Class<T>) PRIMITIVE_TYPES.get(clazz);
+ }
+
+ /**
+ * Indicates if the given class is primitive type or a primitive wrapper.
+ *
+ * @param type The type to check
+ * @return <code>true</code> if primitive or wrapper, <code>false</code> otherwise.
+ */
+ public static boolean isPrimitiveOrWrapper(Class<?> type) {
+ return PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.containsKey(type);
+ }
+
+ /**
+ * Returns the boxed default value for a primitive or a primitive wrapper.
+ *
+ * @param primitiveOrWrapperType The type to lookup the default value
+ * @return The boxed default values as defined in Java Language Specification,
+ * <code>null</code> if the type is neither a primitive nor a wrapper
+ */
+ public static <T> T defaultValueForPrimitiveOrWrapper(Class<T> primitiveOrWrapperType) {
+ return (T) PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.get(primitiveOrWrapperType);
+ }
+
+
+ static {
+ PRIMITIVE_TYPES.put(Boolean.class, Boolean.TYPE);
+ PRIMITIVE_TYPES.put(Character.class, Character.TYPE);
+ PRIMITIVE_TYPES.put(Byte.class, Byte.TYPE);
+ PRIMITIVE_TYPES.put(Short.class, Short.TYPE);
+ PRIMITIVE_TYPES.put(Integer.class, Integer.TYPE);
+ PRIMITIVE_TYPES.put(Long.class, Long.TYPE);
+ PRIMITIVE_TYPES.put(Float.class, Float.TYPE);
+ PRIMITIVE_TYPES.put(Double.class, Double.TYPE);
+ }
+
+ static {
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Boolean.class, false);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Character.class, '\u0000');
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Byte.class, (byte) 0);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Short.class, (short) 0);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Integer.class, 0);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Long.class, 0L);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Float.class, 0F);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Double.class, 0D);
+
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(boolean.class, false);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(char.class, '\u0000');
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(byte.class, (byte) 0);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(short.class, (short) 0);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(int.class, 0);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(long.class, 0L);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(float.class, 0F);
+ PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(double.class, 0D);
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/RemoveFirstLine.java b/src/main/java/org/mockito/internal/util/RemoveFirstLine.java new file mode 100644 index 0000000..517f88f --- /dev/null +++ b/src/main/java/org/mockito/internal/util/RemoveFirstLine.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util;
+
+public class RemoveFirstLine {
+
+ /**
+ * @param text to have the first line removed
+ * @return less first line
+ */
+ public String of(String text) {
+ return text.replaceFirst(".*?\n", "");
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/SimpleMockitoLogger.java b/src/main/java/org/mockito/internal/util/SimpleMockitoLogger.java new file mode 100644 index 0000000..6ee40ea --- /dev/null +++ b/src/main/java/org/mockito/internal/util/SimpleMockitoLogger.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util;
+
+public class SimpleMockitoLogger extends ConsoleMockitoLogger {
+
+ StringBuilder loggedInfo = new StringBuilder();
+
+ public void log(Object what) {
+// can be uncommented when debugging this test
+// super.log(what);
+ loggedInfo.append(what);
+ }
+
+ public String getLoggedInfo() {
+ return loggedInfo.toString();
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/StringJoiner.java b/src/main/java/org/mockito/internal/util/StringJoiner.java new file mode 100644 index 0000000..2cbab0c --- /dev/null +++ b/src/main/java/org/mockito/internal/util/StringJoiner.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockito.internal.util; + +public class StringJoiner { + + public static String join(Object ... linesToBreak) { + StringBuilder out = new StringBuilder("\n"); + return join(out, linesToBreak); + } + + private static String join(StringBuilder out, Object[] linesToBreak) { + for (Object line : linesToBreak) { + out.append(line.toString()).append("\n"); + } + int lastBreak = out.lastIndexOf("\n"); + return out.replace(lastBreak, lastBreak+1, "").toString(); + } +} diff --git a/src/main/java/org/mockito/internal/util/Timer.java b/src/main/java/org/mockito/internal/util/Timer.java new file mode 100644 index 0000000..b71e4fd --- /dev/null +++ b/src/main/java/org/mockito/internal/util/Timer.java @@ -0,0 +1,26 @@ +package org.mockito.internal.util; + +public class Timer { + + private final long durationMillis; + private long startTime = -1; + + public Timer(long durationMillis) { + this.durationMillis = durationMillis; + } + + /** + * Informs whether the timer is still counting down. + */ + public boolean isCounting() { + assert startTime != -1; + return System.currentTimeMillis() - startTime <= durationMillis; + } + + /** + * Starts the timer count down. + */ + public void start() { + startTime = System.currentTimeMillis(); + } +} diff --git a/src/main/java/org/mockito/internal/util/collections/ArrayUtils.java b/src/main/java/org/mockito/internal/util/collections/ArrayUtils.java new file mode 100644 index 0000000..1b3859c --- /dev/null +++ b/src/main/java/org/mockito/internal/util/collections/ArrayUtils.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.collections;
+
+@SuppressWarnings("unchecked")
+public class ArrayUtils {
+
+ public <T> boolean isEmpty(T[] array) {
+ return array == null || array.length == 0;
+ }
+
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/collections/HashCodeAndEqualsMockWrapper.java b/src/main/java/org/mockito/internal/util/collections/HashCodeAndEqualsMockWrapper.java new file mode 100644 index 0000000..22c9d1b --- /dev/null +++ b/src/main/java/org/mockito/internal/util/collections/HashCodeAndEqualsMockWrapper.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.collections; + +import org.mockito.internal.util.MockUtil; + +/** + * hashCode and equals safe mock wrapper. + * + * <p> + * It doesn't use the actual mock {@link Object#hashCode} and {@link Object#equals} method as they might + * throw an NPE if those method cannot be stubbed <em>even internally</em>. + * </p> + * + * <p> + * Instead the strategy is : + * <ul> + * <li>For hashCode : <strong>use {@link System#identityHashCode}</strong></li> + * <li>For equals : <strong>use the object reference equality</strong></li> + * </ul> + * </p> + * + * @see HashCodeAndEqualsSafeSet + */ +public class HashCodeAndEqualsMockWrapper { + + private final Object mockInstance; + + public HashCodeAndEqualsMockWrapper(Object mockInstance) { + this.mockInstance = mockInstance; + } + + public Object get() { + return mockInstance; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof HashCodeAndEqualsMockWrapper)) return false; + + HashCodeAndEqualsMockWrapper that = (HashCodeAndEqualsMockWrapper) o; + + return mockInstance == that.mockInstance; + } + + @Override + public int hashCode() { + return System.identityHashCode(mockInstance); + } + + public static HashCodeAndEqualsMockWrapper of(Object mock) { + return new HashCodeAndEqualsMockWrapper(mock); + } + + @Override public String toString() { + MockUtil mockUtil = new MockUtil(); + return "HashCodeAndEqualsMockWrapper{" + + "mockInstance=" + (mockUtil.isMock(mockInstance) ? mockUtil.getMockName(mockInstance) : typeInstanceString()) + + '}'; + } + + private String typeInstanceString() { + return mockInstance.getClass().getSimpleName() + "(" + System.identityHashCode(mockInstance) + ")"; + } +} diff --git a/src/main/java/org/mockito/internal/util/collections/HashCodeAndEqualsSafeSet.java b/src/main/java/org/mockito/internal/util/collections/HashCodeAndEqualsSafeSet.java new file mode 100644 index 0000000..466c11a --- /dev/null +++ b/src/main/java/org/mockito/internal/util/collections/HashCodeAndEqualsSafeSet.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.collections; + +import org.mockito.internal.util.Checks; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import static java.lang.reflect.Array.*; + +/** + * hashCode and equals safe hash based set. + * + * <p> + * Useful for holding mocks that have un-stubbable hashCode or equals method, + * meaning that in this scenario the real code is always called and will most probably + * cause an {@link NullPointerException}. + * </p> + * <p> + * This collection wraps the mock in an augmented type {@link HashCodeAndEqualsMockWrapper} + * that have his own implementation. + * </p> + * + * @see HashCodeAndEqualsMockWrapper + */ +public class HashCodeAndEqualsSafeSet implements Set<Object> { + + private final HashSet<HashCodeAndEqualsMockWrapper> backingHashSet = new HashSet<HashCodeAndEqualsMockWrapper>(); + + public Iterator<Object> iterator() { + return new Iterator<Object>() { + private final Iterator<HashCodeAndEqualsMockWrapper> iterator = backingHashSet.iterator(); + + public boolean hasNext() { + return iterator.hasNext(); + } + + public Object next() { + return iterator.next().get(); + } + + public void remove() { + iterator.remove(); + } + }; + } + + public int size() { + return backingHashSet.size(); + } + + public boolean isEmpty() { + return backingHashSet.isEmpty(); + } + + public boolean contains(Object mock) { + return backingHashSet.contains(HashCodeAndEqualsMockWrapper.of(mock)); + } + + public boolean add(Object mock) { + return backingHashSet.add(HashCodeAndEqualsMockWrapper.of(mock)); + } + + public boolean remove(Object mock) { + return backingHashSet.remove(HashCodeAndEqualsMockWrapper.of(mock)); + } + + public void clear() { + backingHashSet.clear(); + } + + @Override public Object clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + @Override public boolean equals(Object o) { + if (!(o instanceof HashCodeAndEqualsSafeSet)) { + return false; + } + HashCodeAndEqualsSafeSet that = (HashCodeAndEqualsSafeSet) o; + return backingHashSet.equals(that.backingHashSet); + } + + @Override public int hashCode() { + return backingHashSet.hashCode(); + } + + public Object[] toArray() { + return unwrapTo(new Object[size()]); + } + + private <T> T[] unwrapTo(T[] array) { + Iterator<Object> iterator = iterator(); + for (int i = 0, objectsLength = array.length; i < objectsLength; i++) { + if (iterator.hasNext()) { + array[i] = (T) iterator.next(); + } + } + return array; + } + + + public <T> T[] toArray(T[] typedArray) { + T[] array = typedArray.length >= size() ? typedArray : + (T[]) newInstance(typedArray.getClass().getComponentType(), size()); + return unwrapTo(array); + } + + public boolean removeAll(Collection<?> mocks) { + return backingHashSet.removeAll(asWrappedMocks(mocks)); + } + + public boolean containsAll(Collection<?> mocks) { + return backingHashSet.containsAll(asWrappedMocks(mocks)); + } + + public boolean addAll(Collection<?> mocks) { + return backingHashSet.addAll(asWrappedMocks(mocks)); + } + + public boolean retainAll(Collection<?> mocks) { + return backingHashSet.retainAll(asWrappedMocks(mocks)); + } + + private HashSet<HashCodeAndEqualsMockWrapper> asWrappedMocks(Collection<?> mocks) { + Checks.checkNotNull(mocks, "Passed collection should notify() be null"); + HashSet<HashCodeAndEqualsMockWrapper> hashSet = new HashSet<HashCodeAndEqualsMockWrapper>(); + for (Object mock : mocks) { + assert ! (mock instanceof HashCodeAndEqualsMockWrapper) : "WRONG"; + hashSet.add(HashCodeAndEqualsMockWrapper.of(mock)); + } + return hashSet; + } + + @Override public String toString() { + return backingHashSet.toString(); + } + + public static HashCodeAndEqualsSafeSet of(Object... mocks) { + return of(Arrays.asList(mocks)); + } + + public static HashCodeAndEqualsSafeSet of(Iterable<Object> objects) { + HashCodeAndEqualsSafeSet hashCodeAndEqualsSafeSet = new HashCodeAndEqualsSafeSet(); + if (objects != null) { + for (Object mock : objects) { + hashCodeAndEqualsSafeSet.add(mock); + } + } + return hashCodeAndEqualsSafeSet; + } +} diff --git a/src/main/java/org/mockito/internal/util/collections/IdentitySet.java b/src/main/java/org/mockito/internal/util/collections/IdentitySet.java new file mode 100644 index 0000000..661d609 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/collections/IdentitySet.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.collections;
+
+import java.util.LinkedList;
+
+@SuppressWarnings("unchecked")
+public class IdentitySet {
+
+ LinkedList list = new LinkedList();
+
+ public boolean contains(Object o) {
+ for(Object existing:list) {
+ if (existing == o) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void add(Object o) {
+ list.add(o);
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/collections/Iterables.java b/src/main/java/org/mockito/internal/util/collections/Iterables.java new file mode 100644 index 0000000..9aba368 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/collections/Iterables.java @@ -0,0 +1,22 @@ +package org.mockito.internal.util.collections; + +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; + +/** + * Utilities for Iterables + */ +public class Iterables { + + /** + * Converts enumeration into iterable + */ + public static <T> Iterable<T> toIterable(Enumeration<T> in) { + List<T> out = new LinkedList<T>(); + while(in.hasMoreElements()) { + out.add(in.nextElement()); + } + return out; + } +} diff --git a/src/main/java/org/mockito/internal/util/collections/ListUtil.java b/src/main/java/org/mockito/internal/util/collections/ListUtil.java new file mode 100644 index 0000000..b426bb2 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/collections/ListUtil.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockito.internal.util.collections; + +import java.util.Collection; +import java.util.LinkedList; + +public class ListUtil { + + public static <T> LinkedList<T> filter(Collection<T> collection, Filter<T> filter) { + LinkedList<T> filtered = new LinkedList<T>(); + for (T t : collection) { + if (!filter.isOut(t)) { + filtered.add(t); + } + } + return filtered; + } + + public interface Filter<T> { + boolean isOut(T object); + } +} diff --git a/src/main/java/org/mockito/internal/util/collections/Sets.java b/src/main/java/org/mockito/internal/util/collections/Sets.java new file mode 100644 index 0000000..6095549 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/collections/Sets.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.collections; + + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; + +import static java.util.Arrays.asList; + +public abstract class Sets { + public static Set<Object> newMockSafeHashSet(Iterable<Object> mocks) { + return HashCodeAndEqualsSafeSet.of(mocks); + } + + public static Set<Object> newMockSafeHashSet(Object... mocks) { + return HashCodeAndEqualsSafeSet.of(mocks); + } + + public static IdentitySet newIdentitySet() { + return new IdentitySet(); + } + + public static <T> Set<T> newSet(T ... elements) { + if (elements == null) { + throw new IllegalArgumentException("Expected an array of elements (or empty array) but received a null."); + } + return new LinkedHashSet<T>(asList(elements)); + } +} diff --git a/src/main/java/org/mockito/internal/util/io/IOUtil.java b/src/main/java/org/mockito/internal/util/io/IOUtil.java new file mode 100644 index 0000000..2b90c2c --- /dev/null +++ b/src/main/java/org/mockito/internal/util/io/IOUtil.java @@ -0,0 +1,71 @@ +package org.mockito.internal.util.io; + +import org.mockito.exceptions.base.MockitoException; + +import java.io.*; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +/** + * IO utils. A bit of reinventing the wheel but we don't want extra dependencies at this stage and we want to be java. + */ +public class IOUtil { + + /** + * Writes text to file + */ + public static void writeText(String text, File output) { + PrintWriter pw = null; + try { + pw = new PrintWriter(new FileWriter(output)); + pw.write(text); + } catch (Exception e) { + throw new MockitoException("Problems writing text to file: " + output, e); + } finally { + close(pw); + } + } + + public static Collection<String> readLines(InputStream is) { + List<String> out = new LinkedList<String>(); + BufferedReader r = new BufferedReader(new InputStreamReader(is)); + String line; + try { + while((line = r.readLine()) != null) { + out.add(line); + } + } catch (IOException e) { + throw new MockitoException("Problems reading from: " + is, e); + } + return out; + } + + /** + * Closes the target. Does nothing when target is null. Is silent. + * + * @param closeable the target, may be null + */ + public static void closeQuietly(Closeable closeable) { + try { + close(closeable); + } catch (MockitoException ignored) { + //ignore, for backwards compatibility + } + } + + /** + * Closes the target. Does nothing when target is null. Is not silent and exceptions are rethrown. + * + * @param closeable the target, may be null + */ + public static void close(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + throw new MockitoException("Problems closing stream: " + closeable, e); + } + } + } +} diff --git a/src/main/java/org/mockito/internal/util/junit/JUnitFailureHacker.java b/src/main/java/org/mockito/internal/util/junit/JUnitFailureHacker.java new file mode 100644 index 0000000..debc871 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/junit/JUnitFailureHacker.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.junit;
+
+import org.junit.runner.notification.Failure;
+import org.mockito.internal.exceptions.ExceptionIncludingMockitoWarnings;
+import org.mockito.internal.util.reflection.Whitebox;
+
+public class JUnitFailureHacker {
+
+ public void appendWarnings(Failure failure, String warnings) {
+ if (isEmpty(warnings)) {
+ return;
+ }
+ //TODO: this has to protect the use in case jUnit changes and this internal state logic fails
+ Throwable throwable = (Throwable) Whitebox.getInternalState(failure, "fThrownException");
+
+ String newMessage = "contains both: actual test failure *and* Mockito warnings.\n" +
+ warnings + "\n *** The actual failure is because of: ***\n";
+
+ ExceptionIncludingMockitoWarnings e = new ExceptionIncludingMockitoWarnings(newMessage, throwable);
+ e.setStackTrace(throwable.getStackTrace());
+ Whitebox.setInternalState(failure, "fThrownException", e);
+ }
+
+ private boolean isEmpty(String warnings) {
+ return warnings == null || "".equals(warnings); // isEmpty() is in JDK 6+
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/package.html b/src/main/java/org/mockito/internal/util/package.html new file mode 100644 index 0000000..b86ca1c --- /dev/null +++ b/src/main/java/org/mockito/internal/util/package.html @@ -0,0 +1,8 @@ +<!-- + ~ Copyright (c) 2007 Mockito contributors + ~ This program is made available under the terms of the MIT License. + --> + +<body> +Static utils +</body>
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/reflection/AccessibilityChanger.java b/src/main/java/org/mockito/internal/util/reflection/AccessibilityChanger.java new file mode 100644 index 0000000..a1d59c3 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/AccessibilityChanger.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection;
+
+import java.lang.reflect.AccessibleObject;
+
+public class AccessibilityChanger {
+
+ private Boolean wasAccessible = null;
+
+ /**
+ * safely disables access
+ */
+ public void safelyDisableAccess(AccessibleObject accessibleObject) {
+ assert wasAccessible != null : "accessibility info shall not be null";
+ try {
+ accessibleObject.setAccessible(wasAccessible);
+ } catch (Throwable t) {
+ //ignore
+ }
+ }
+
+ /**
+ * changes the accessibleObject accessibility and returns true if accessibility was changed
+ */
+ public void enableAccess(AccessibleObject accessibleObject) {
+ wasAccessible = accessibleObject.isAccessible();
+ accessibleObject.setAccessible(true);
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/BeanPropertySetter.java b/src/main/java/org/mockito/internal/util/reflection/BeanPropertySetter.java new file mode 100644 index 0000000..082231d --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/BeanPropertySetter.java @@ -0,0 +1,98 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+/**
+ * This utility class will call the setter of the property to inject a new value.
+ */
+public class BeanPropertySetter {
+
+ private static final String SET_PREFIX = "set";
+
+ private final Object target;
+ private final boolean reportNoSetterFound;
+ private final Field field;
+
+ /**
+ * New BeanPropertySetter
+ * @param target The target on which the setter must be invoked
+ * @param propertyField The field that should be accessed with the setter
+ * @param reportNoSetterFound Allow the set method to raise an Exception if the setter cannot be found
+ */
+ public BeanPropertySetter(final Object target, final Field propertyField, boolean reportNoSetterFound) {
+ this.field = propertyField;
+ this.target = target;
+ this.reportNoSetterFound = reportNoSetterFound;
+ }
+
+ /**
+ * New BeanPropertySetter that don't report failure
+ * @param target The target on which the setter must be invoked
+ * @param propertyField The propertyField that must be accessed through a setter
+ */
+ public BeanPropertySetter(final Object target, final Field propertyField) {
+ this(target, propertyField, false);
+ }
+
+ /**
+ * Set the value to the property represented by this {@link BeanPropertySetter}
+ * @param value the new value to pass to the property setter
+ * @return <code>true</code> if the value has been injected, <code>false</code> otherwise
+ * @throws RuntimeException Can be thrown if the setter threw an exception, if the setter is not accessible
+ * or, if <code>reportNoSetterFound</code> and setter could not be found.
+ */
+ public boolean set(final Object value) {
+
+ AccessibilityChanger changer = new AccessibilityChanger();
+ Method writeMethod = null;
+ try {
+ writeMethod = target.getClass().getMethod(setterName(field.getName()), field.getType());
+
+ changer.enableAccess(writeMethod);
+ writeMethod.invoke(target, value);
+ return true;
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException("Setter '" + writeMethod + "' of '" + target + "' with value '" + value + "' threw exception : '" + e.getTargetException() + "'", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Access not authorized on field '" + field + "' of object '" + target + "' with value: '" + value + "'", e);
+ } catch (NoSuchMethodException e) {
+ reportNoSetterFound();
+ } finally {
+ if(writeMethod != null) {
+ changer.safelyDisableAccess(writeMethod);
+ }
+ }
+
+ reportNoSetterFound();
+ return false;
+ }
+
+ /**
+ * Retrieve the setter name from the field name.
+ *
+ * <p>Implementation is based on the code of {@link java.beans.Introspector}.</p>
+ *
+ * @param fieldName the Field name
+ * @return Setter name.
+ */
+ private String setterName(String fieldName) {
+ return new StringBuilder(SET_PREFIX)
+ .append(fieldName.substring(0, 1).toUpperCase(Locale.ENGLISH))
+ .append(fieldName.substring(1))
+ .toString();
+ }
+
+ private void reportNoSetterFound() {
+ if(reportNoSetterFound) {
+ throw new RuntimeException("Problems setting value on object: [" + target + "] for property : [" + field.getName() + "], setter not found");
+ }
+ }
+
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/Constructors.java b/src/main/java/org/mockito/internal/util/reflection/Constructors.java new file mode 100644 index 0000000..909de8c --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/Constructors.java @@ -0,0 +1,20 @@ +package org.mockito.internal.util.reflection; + +import java.lang.reflect.Constructor; + +public abstract class Constructors { + + /** + * Returns the no arg constructor of the type if any. + * + * @param classToMock The type to look for a no-arg constructor + * @return The no-arg constructor or null if none is declared. + */ + public static Constructor<?> noArgConstructorOf(Class<?> classToMock) { + try { + return classToMock.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + return null; + } + } +} diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldCopier.java b/src/main/java/org/mockito/internal/util/reflection/FieldCopier.java new file mode 100644 index 0000000..bc6bcff --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/FieldCopier.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection;
+
+import java.lang.reflect.Field;
+
+public class FieldCopier {
+
+ public <T> void copyValue(T from, T to, Field field) throws IllegalAccessException {
+ Object value = field.get(from);
+ field.set(to, value);
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldInitializationReport.java b/src/main/java/org/mockito/internal/util/reflection/FieldInitializationReport.java new file mode 100644 index 0000000..9a0b778 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/FieldInitializationReport.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +package org.mockito.internal.util.reflection; + +/** + * Report on field initialization + */ +public class FieldInitializationReport { + private final Object fieldInstance; + private final boolean wasInitialized; + private final boolean wasInitializedUsingConstructorArgs; + + public FieldInitializationReport(Object fieldInstance, boolean wasInitialized, boolean wasInitializedUsingConstructorArgs) { + this.fieldInstance = fieldInstance; + this.wasInitialized = wasInitialized; + this.wasInitializedUsingConstructorArgs = wasInitializedUsingConstructorArgs; + } + + /** + * Returns the actual field instance. + * + * @return the actual instance + */ + public Object fieldInstance() { + return fieldInstance; + } + + /** + * Indicate wether the field was created during the process or not. + * + * @return <code>true</code> if created, <code>false</code> if the field did already hold an instance. + */ + public boolean fieldWasInitialized() { + return wasInitialized; + } + + /** + * Indicate wether the field was created using constructor args. + * + * @return <code>true</code> if field was created using constructor parameters. + */ + public boolean fieldWasInitializedUsingContructorArgs() { + return wasInitializedUsingConstructorArgs; + } + + /** + * Returns the class of the actual instance in the field. + * + * @return Class of the instance + */ + public Class<?> fieldClass() { + return fieldInstance != null ? fieldInstance.getClass() : null; + } +} + diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java new file mode 100644 index 0000000..ec4b68e --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/FieldInitializer.java @@ -0,0 +1,291 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.util.MockUtil;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Initialize a field with type instance if a default constructor can be found.
+ *
+ * <p>
+ * If the given field is already initialized, then <strong>the actual instance is returned</strong>.
+ * This initializer doesn't work with inner classes, local classes, interfaces or abstract types.
+ * </p>
+ *
+ */
+public class FieldInitializer {
+
+ private final Object fieldOwner;
+ private final Field field;
+ private final ConstructorInstantiator instantiator;
+
+
+ /**
+ * Prepare initializer with the given field on the given instance.
+ *
+ * <p>
+ * This constructor fail fast if the field type cannot be handled.
+ * </p>
+ *
+ * @param fieldOwner Instance of the test.
+ * @param field Field to be initialize.
+ */
+ public FieldInitializer(Object fieldOwner, Field field) {
+ this(fieldOwner, field, new NoArgConstructorInstantiator(fieldOwner, field));
+ }
+
+ /**
+ * Prepare initializer with the given field on the given instance.
+ *
+ * <p>
+ * This constructor fail fast if the field type cannot be handled.
+ * </p>
+ *
+ * @param fieldOwner Instance of the test.
+ * @param field Field to be initialize.
+ * @param argResolver Constructor parameters resolver
+ */
+ public FieldInitializer(Object fieldOwner, Field field, ConstructorArgumentResolver argResolver) {
+ this(fieldOwner, field, new ParameterizedConstructorInstantiator(fieldOwner, field, argResolver));
+ }
+
+ private FieldInitializer(Object fieldOwner, Field field, ConstructorInstantiator instantiator) {
+ if(new FieldReader(fieldOwner, field).isNull()) {
+ checkNotLocal(field);
+ checkNotInner(field);
+ checkNotInterface(field);
+ checkNotAbstract(field);
+ }
+ this.fieldOwner = fieldOwner;
+ this.field = field;
+ this.instantiator = instantiator;
+ }
+
+ /**
+ * Initialize field if not initialized and return the actual instance.
+ *
+ * @return Actual field instance.
+ */
+ public FieldInitializationReport initialize() {
+ final AccessibilityChanger changer = new AccessibilityChanger();
+ changer.enableAccess(field);
+
+ try {
+ return acquireFieldInstance();
+ } catch(IllegalAccessException e) {
+ throw new MockitoException("Problems initializing field '" + field.getName() + "' of type '" + field.getType().getSimpleName() + "'", e);
+ } finally {
+ changer.safelyDisableAccess(field);
+ }
+ }
+
+ private void checkNotLocal(Field field) {
+ if(field.getType().isLocalClass()) {
+ throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is a local class.");
+ }
+ }
+
+ private void checkNotInner(Field field) {
+ if(field.getType().isMemberClass() && !Modifier.isStatic(field.getType().getModifiers())) {
+ throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an inner class.");
+ }
+ }
+
+ private void checkNotInterface(Field field) {
+ if(field.getType().isInterface()) {
+ throw new MockitoException("the type '" + field.getType().getSimpleName() + "' is an interface.");
+ }
+ }
+
+ private void checkNotAbstract(Field field) {
+ if(Modifier.isAbstract(field.getType().getModifiers())) {
+ throw new MockitoException("the type '" + field.getType().getSimpleName() + " is an abstract class.");
+ }
+ }
+
+ private FieldInitializationReport acquireFieldInstance() throws IllegalAccessException {
+ Object fieldInstance = field.get(fieldOwner);
+ if(fieldInstance != null) {
+ return new FieldInitializationReport(fieldInstance, false, false);
+ }
+
+ return instantiator.instantiate();
+ }
+
+ /**
+ * Represents the strategy used to resolve actual instances
+ * to be given to a constructor given the argument types.
+ */
+ public interface ConstructorArgumentResolver {
+
+ /**
+ * Try to resolve instances from types.
+ *
+ * <p>
+ * Checks on the real argument type or on the correct argument number
+ * will happen during the field initialization {@link FieldInitializer#initialize()}.
+ * I.e the only responsibility of this method, is to provide instances <strong>if possible</strong>.
+ * </p>
+ *
+ * @param argTypes Constructor argument types, should not be null.
+ * @return The argument instances to be given to the constructor, should not be null.
+ */
+ Object[] resolveTypeInstances(Class<?>... argTypes);
+ }
+
+ private interface ConstructorInstantiator {
+ FieldInitializationReport instantiate();
+ }
+
+ /**
+ * Constructor instantiating strategy for no-arg constructor.
+ *
+ * <p>
+ * If a no-arg constructor can be found then the instance is created using
+ * this constructor.
+ * Otherwise a technical MockitoException is thrown.
+ * </p>
+ */
+ static class NoArgConstructorInstantiator implements ConstructorInstantiator {
+ private final Object testClass;
+ private final Field field;
+
+ /**
+ * Internal, checks are done by FieldInitializer.
+ * Fields are assumed to be accessible.
+ */
+ NoArgConstructorInstantiator(Object testClass, Field field) {
+ this.testClass = testClass;
+ this.field = field;
+ }
+
+ public FieldInitializationReport instantiate() {
+ final AccessibilityChanger changer = new AccessibilityChanger();
+ Constructor<?> constructor = null;
+ try {
+ constructor = field.getType().getDeclaredConstructor();
+ changer.enableAccess(constructor);
+
+ final Object[] noArg = new Object[0];
+ Object newFieldInstance = constructor.newInstance(noArg);
+ new FieldSetter(testClass, field).set(newFieldInstance);
+
+ return new FieldInitializationReport(field.get(testClass), true, false);
+ } catch (NoSuchMethodException e) {
+ throw new MockitoException("the type '" + field.getType().getSimpleName() + "' has no default constructor", e);
+ } catch (InvocationTargetException e) {
+ throw new MockitoException("the default constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e);
+ } catch (InstantiationException e) {
+ throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e);
+ } catch (IllegalAccessException e) {
+ throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e);
+ } finally {
+ if(constructor != null) {
+ changer.safelyDisableAccess(constructor);
+ }
+ }
+ }
+ }
+
+ /**
+ * Constructor instantiating strategy for parameterized constructors.
+ *
+ * <p>
+ * Choose the constructor with the highest number of parameters, then
+ * call the ConstructorArgResolver to get actual argument instances.
+ * If the argResolver fail, then a technical MockitoException is thrown is thrown.
+ * Otherwise the instance is created with the resolved arguments.
+ * </p>
+ */
+ static class ParameterizedConstructorInstantiator implements ConstructorInstantiator {
+ private final Object testClass;
+ private final Field field;
+ private final ConstructorArgumentResolver argResolver;
+ private final MockUtil mockUtil = new MockUtil();
+ private final Comparator<Constructor<?>> byParameterNumber = new Comparator<Constructor<?>>() {
+ public int compare(Constructor<?> constructorA, Constructor<?> constructorB) {
+ int argLengths = constructorB.getParameterTypes().length - constructorA.getParameterTypes().length;
+ if (argLengths == 0) {
+ int constructorAMockableParamsSize = countMockableParams(constructorA);
+ int constructorBMockableParamsSize = countMockableParams(constructorB);
+ return constructorBMockableParamsSize - constructorAMockableParamsSize;
+ }
+ return argLengths;
+ }
+
+ private int countMockableParams(Constructor<?> constructor) {
+ int constructorMockableParamsSize = 0;
+ for (Class<?> aClass : constructor.getParameterTypes()) {
+ if(mockUtil.isTypeMockable(aClass)){
+ constructorMockableParamsSize++;
+ }
+ }
+ return constructorMockableParamsSize;
+ }
+ };
+
+ /**
+ * Internal, checks are done by FieldInitializer.
+ * Fields are assumed to be accessible.
+ */
+ ParameterizedConstructorInstantiator(Object testClass, Field field, ConstructorArgumentResolver argumentResolver) {
+ this.testClass = testClass;
+ this.field = field;
+ this.argResolver = argumentResolver;
+ }
+
+ public FieldInitializationReport instantiate() {
+ final AccessibilityChanger changer = new AccessibilityChanger();
+ Constructor<?> constructor = null;
+ try {
+ constructor = biggestConstructor(field.getType());
+ changer.enableAccess(constructor);
+
+ final Object[] args = argResolver.resolveTypeInstances(constructor.getParameterTypes());
+ Object newFieldInstance = constructor.newInstance(args);
+ new FieldSetter(testClass, field).set(newFieldInstance);
+
+ return new FieldInitializationReport(field.get(testClass), false, true);
+ } catch (IllegalArgumentException e) {
+ throw new MockitoException("internal error : argResolver provided incorrect types for constructor " + constructor + " of type " + field.getType().getSimpleName(), e);
+ } catch (InvocationTargetException e) {
+ throw new MockitoException("the constructor of type '" + field.getType().getSimpleName() + "' has raised an exception (see the stack trace for cause): " + e.getTargetException().toString(), e);
+ } catch (InstantiationException e) {
+ throw new MockitoException("InstantiationException (see the stack trace for cause): " + e.toString(), e);
+ } catch (IllegalAccessException e) {
+ throw new MockitoException("IllegalAccessException (see the stack trace for cause): " + e.toString(), e);
+ } finally {
+ if(constructor != null) {
+ changer.safelyDisableAccess(constructor);
+ }
+ }
+ }
+
+ private void checkParameterized(Constructor<?> constructor, Field field) {
+ if(constructor.getParameterTypes().length == 0) {
+ throw new MockitoException("the field " + field.getName() + " of type " + field.getType() + " has no parameterized constructor");
+ }
+ }
+
+ private Constructor<?> biggestConstructor(Class<?> clazz) {
+ final List<Constructor<?>> constructors = Arrays.asList(clazz.getDeclaredConstructors());
+ Collections.sort(constructors, byParameterNumber);
+
+ Constructor<?> constructor = constructors.get(0);
+ checkParameterized(constructor, field);
+ return constructor;
+ }
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldReader.java b/src/main/java/org/mockito/internal/util/reflection/FieldReader.java new file mode 100644 index 0000000..49a62e7 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/FieldReader.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection;
+
+import org.mockito.exceptions.base.MockitoException;
+
+import java.lang.reflect.Field;
+
+public class FieldReader {
+
+ final Object target;
+ final Field field;
+ final AccessibilityChanger changer = new AccessibilityChanger();
+
+ public FieldReader(Object target, Field field) {
+ this.target = target;
+ this.field = field;
+ changer.enableAccess(field);
+ }
+
+ public boolean isNull() {
+ return read() == null;
+ }
+
+ public Object read() {
+ try {
+ return field.get(target);
+ } catch (Exception e) {
+ throw new MockitoException("Cannot read state from field: " + field + ", on instance: " + target);
+ }
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/FieldSetter.java b/src/main/java/org/mockito/internal/util/reflection/FieldSetter.java new file mode 100644 index 0000000..5972767 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/FieldSetter.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection;
+
+import java.lang.reflect.Field;
+
+public class FieldSetter {
+
+ private final Object target;
+ private final Field field;
+
+ public FieldSetter(Object target, Field field) {
+ this.target = target;
+ this.field = field;
+ }
+
+ public void set(Object value) {
+ AccessibilityChanger changer = new AccessibilityChanger();
+ changer.enableAccess(field);
+ try {
+ field.set(target, value);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Access not authorized on field '" + field + "' of object '" + target + "' with value: '" + value + "'", e);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException("Wrong argument on field '" + field + "' of object '" + target + "' with value: '" + value + "', \n" +
+ "reason : " + e.getMessage(), e);
+ }
+ changer.safelyDisableAccess(field);
+ }
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/Fields.java b/src/main/java/org/mockito/internal/util/reflection/Fields.java new file mode 100644 index 0000000..327dac0 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/Fields.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection; + +import org.mockito.internal.util.Checks; +import org.mockito.internal.util.collections.ListUtil; +import org.mockito.internal.util.collections.ListUtil.Filter; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * Small fluent reflection tools to work with fields. + * + * Code is very new and might need rework. + */ +public abstract class Fields { + + /** + * Instance fields declared in the class and superclasses of the given instance. + * + * @param instance Instance from which declared fields will be retrieved. + * @return InstanceFields of this object instance. + */ + public static InstanceFields allDeclaredFieldsOf(Object instance) { + List<InstanceField> instanceFields = new ArrayList<InstanceField>(); + for (Class<?> clazz = instance.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) { + instanceFields.addAll(instanceFieldsIn(instance, clazz.getDeclaredFields())); + } + return new InstanceFields(instance, instanceFields); + } + + /** + * Instance fields declared in the class of the given instance. + * + * @param instance Instance from which declared fields will be retrieved. + * @return InstanceFields of this object instance. + */ + public static InstanceFields declaredFieldsOf(Object instance) { + List<InstanceField> instanceFields = new ArrayList<InstanceField>(); + instanceFields.addAll(instanceFieldsIn(instance, instance.getClass().getDeclaredFields())); + return new InstanceFields(instance, instanceFields); + } + + private static List<InstanceField> instanceFieldsIn(Object instance, Field[] fields) { + List<InstanceField> instanceDeclaredFields = new ArrayList<InstanceField>(); + for (Field field : fields) { + InstanceField instanceField = new InstanceField(field, instance); + instanceDeclaredFields.add(instanceField); + } + return instanceDeclaredFields; + } + + /** + * Accept fields annotated by the given annotations. + * + * @param annotations Annotation types to check. + * @return The filter. + */ + public static Filter<InstanceField> annotatedBy(final Class<? extends Annotation>... annotations) { + return new Filter<InstanceField>() { + public boolean isOut(InstanceField instanceField) { + Checks.checkNotNull(annotations, "Provide at least one annotation class"); + + for (Class<? extends Annotation> annotation : annotations) { + if(instanceField.isAnnotatedBy(annotation)) { + return false; + } + } + return true; + } + }; + } + + /** + * Accept fields with non null value. + * + * @return The filter. + */ + private static Filter<InstanceField> nullField() { + return new Filter<InstanceField>() { + public boolean isOut(InstanceField instanceField) { + return instanceField.isNull(); + } + }; + } + + public static class InstanceFields { + private final Object instance; + + private final List<InstanceField> instanceFields; + + public InstanceFields(Object instance, List<InstanceField> instanceFields) { + this.instance = instance; + this.instanceFields = instanceFields; + } + + public InstanceFields filter(Filter<InstanceField> withFilter) { + return new InstanceFields(instance, ListUtil.filter(instanceFields, withFilter)); + } + + public InstanceFields notNull() { + return filter(nullField()); + } + + public List<InstanceField> instanceFields() { + return new ArrayList<InstanceField>(instanceFields); + } + + public List<Object> assignedValues() { + List<Object> values = new ArrayList<Object>(instanceFields.size()); + for (InstanceField instanceField : instanceFields) { + values.add(instanceField.read()); + } + return values; + } + + public List<String> names() { + List<String> fieldNames = new ArrayList<String>(instanceFields.size()); + for (InstanceField instanceField : instanceFields) { + fieldNames.add(instanceField.name()); + } + return fieldNames; + } + } +} diff --git a/src/main/java/org/mockito/internal/util/reflection/GenericMaster.java b/src/main/java/org/mockito/internal/util/reflection/GenericMaster.java new file mode 100644 index 0000000..2b07930 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/GenericMaster.java @@ -0,0 +1,34 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+@SuppressWarnings("unchecked")
+public class GenericMaster {
+
+ /**
+ * Finds the generic type (parametrized type) of the field. If the field is not generic it returns Object.class.
+ *
+ * @param field
+ */
+ public Class getGenericType(Field field) {
+ Type generic = field.getGenericType();
+ if (generic instanceof ParameterizedType) {
+ Type actual = ((ParameterizedType) generic).getActualTypeArguments()[0];
+ if (actual instanceof Class) {
+ return (Class) actual;
+ } else if (actual instanceof ParameterizedType) {
+ //in case of nested generics we don't go deep
+ return (Class) ((ParameterizedType) actual).getRawType();
+ }
+ }
+
+ return Object.class;
+ }
+
+}
diff --git a/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java b/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java new file mode 100644 index 0000000..80bd908 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/GenericMetadataSupport.java @@ -0,0 +1,627 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.internal.util.Checks;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+
+/**
+ * This class can retrieve generic meta-data that the compiler stores on classes
+ * and accessible members.
+ *
+ * <p>
+ * The main idea of this code is to create a Map that will help to resolve return types.
+ * In order to actually work with nested generics, this map will have to be passed along new instances
+ * as a type context.
+ * </p>
+ *
+ * <p>
+ * Hence :
+ * <ul>
+ * <li>A new instance representing the metadata is created using the {@link #inferFrom(Type)} method from a real
+ * <code>Class</code> or from a <code>ParameterizedType</code>, other types are not yet supported.</li>
+ *
+ * <li>Then from this metadata, we can extract meta-data for a generic return type of a method, using
+ * {@link #resolveGenericReturnType(Method)}.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * For now this code support the following kind of generic declarations :
+ * <pre class="code"><code class="java">
+ * interface GenericsNest<K extends Comparable<K> & Cloneable> extends Map<K, Set<Number>> {
+ * Set<Number> remove(Object key); // override with fixed ParameterizedType
+ * List<? super Integer> returning_wildcard_with_class_lower_bound();
+ * List<? super K> returning_wildcard_with_typeVar_lower_bound();
+ * List<? extends K> returning_wildcard_with_typeVar_upper_bound();
+ * K returningK();
+ * <O extends K> List<O> paramType_with_type_params();
+ * <S, T extends S> T two_type_params();
+ * <O extends K> O typeVar_with_type_params();
+ * Number returningNonGeneric();
+ * }
+ * </code></pre>
+ *
+ * @see #inferFrom(Type)
+ * @see #resolveGenericReturnType(Method)
+ * @see org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs
+ */
+public abstract class GenericMetadataSupport {
+
+ // public static MockitoLogger logger = new ConsoleMockitoLogger();
+
+ /**
+ * Represents actual type variables resolved for current class.
+ */
+ protected Map<TypeVariable, Type> contextualActualTypeParameters = new HashMap<TypeVariable, Type>();
+
+
+ protected void registerTypeVariablesOn(Type classType) {
+ if (!(classType instanceof ParameterizedType)) {
+ return;
+ }
+ ParameterizedType parameterizedType = (ParameterizedType) classType;
+ TypeVariable[] typeParameters = ((Class<?>) parameterizedType.getRawType()).getTypeParameters();
+ Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+ for (int i = 0; i < actualTypeArguments.length; i++) {
+ TypeVariable typeParameter = typeParameters[i];
+ Type actualTypeArgument = actualTypeArguments[i];
+
+ if (actualTypeArgument instanceof WildcardType) {
+ contextualActualTypeParameters.put(typeParameter, boundsOf((WildcardType) actualTypeArgument));
+ } else if (typeParameter != actualTypeArgument) {
+ contextualActualTypeParameters.put(typeParameter, actualTypeArgument);
+ }
+ // logger.log("For '" + parameterizedType + "' found type variable : { '" + typeParameter + "(" + System.identityHashCode(typeParameter) + ")" + "' : '" + actualTypeArgument + "(" + System.identityHashCode(typeParameter) + ")" + "' }");
+ }
+ }
+
+ protected void registerTypeParametersOn(TypeVariable[] typeParameters) {
+ for (TypeVariable type : typeParameters) {
+ registerTypeVariableIfNotPresent(type);
+ }
+ }
+
+ private void registerTypeVariableIfNotPresent(TypeVariable typeVariable) {
+ if (!contextualActualTypeParameters.containsKey(typeVariable)) {
+ contextualActualTypeParameters.put(typeVariable, boundsOf(typeVariable));
+ // logger.log("For '" + typeVariable.getGenericDeclaration() + "' found type variable : { '" + typeVariable + "(" + System.identityHashCode(typeVariable) + ")" + "' : '" + boundsOf(typeVariable) + "' }");
+ }
+ }
+
+ /**
+ * @param typeParameter The TypeVariable parameter
+ * @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable
+ * then retrieve BoundedType of this TypeVariable
+ */
+ private BoundedType boundsOf(TypeVariable typeParameter) {
+ if (typeParameter.getBounds()[0] instanceof TypeVariable) {
+ return boundsOf((TypeVariable) typeParameter.getBounds()[0]);
+ }
+ return new TypeVarBoundedType(typeParameter);
+ }
+
+ /**
+ * @param wildCard The WildCard type
+ * @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable
+ * then retrieve BoundedType of this TypeVariable
+ */
+ private BoundedType boundsOf(WildcardType wildCard) {
+ /*
+ * According to JLS(http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1):
+ * - Lower and upper can't coexist: (for instance, this is not allowed: <? extends List<String> & super MyInterface>)
+ * - Multiple bounds are not supported (for instance, this is not allowed: <? extends List<String> & MyInterface>)
+ */
+
+ WildCardBoundedType wildCardBoundedType = new WildCardBoundedType(wildCard);
+ if (wildCardBoundedType.firstBound() instanceof TypeVariable) {
+ return boundsOf((TypeVariable) wildCardBoundedType.firstBound());
+ }
+
+ return wildCardBoundedType;
+ }
+
+
+
+ /**
+ * @return Raw type of the current instance.
+ */
+ public abstract Class<?> rawType();
+
+
+
+ /**
+ * @return Returns extra interfaces <strong>if relevant</strong>, otherwise empty List.
+ */
+ public List<Type> extraInterfaces() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * @return Returns an array with the raw types of {@link #extraInterfaces()} <strong>if relevant</strong>.
+ */
+ public Class<?>[] rawExtraInterfaces() {
+ return new Class[0];
+ }
+
+ /**
+ * @return Returns true if metadata knows about extra-interfaces {@link #extraInterfaces()} <strong>if relevant</strong>.
+ */
+ public boolean hasRawExtraInterfaces() {
+ return rawExtraInterfaces().length > 0;
+ }
+
+
+
+ /**
+ * @return Actual type arguments matching the type variables of the raw type represented by this {@link GenericMetadataSupport} instance.
+ */
+ public Map<TypeVariable, Type> actualTypeArguments() {
+ TypeVariable[] typeParameters = rawType().getTypeParameters();
+ LinkedHashMap<TypeVariable, Type> actualTypeArguments = new LinkedHashMap<TypeVariable, Type>();
+
+ for (TypeVariable typeParameter : typeParameters) {
+
+ Type actualType = getActualTypeArgumentFor(typeParameter);
+
+ actualTypeArguments.put(typeParameter, actualType);
+ // logger.log("For '" + rawType().getCanonicalName() + "' returning explicit TypeVariable : { '" + typeParameter + "(" + System.identityHashCode(typeParameter) + ")" + "' : '" + actualType +"' }");
+ }
+
+ return actualTypeArguments;
+ }
+
+ protected Type getActualTypeArgumentFor(TypeVariable typeParameter) {
+ Type type = this.contextualActualTypeParameters.get(typeParameter);
+ if (type instanceof TypeVariable) {
+ TypeVariable typeVariable = (TypeVariable) type;
+ return getActualTypeArgumentFor(typeVariable);
+ }
+
+ return type;
+ }
+
+
+
+ /**
+ * Resolve current method generic return type to a {@link GenericMetadataSupport}.
+ *
+ * @param method Method to resolve the return type.
+ * @return {@link GenericMetadataSupport} representing this generic return type.
+ */
+ public GenericMetadataSupport resolveGenericReturnType(Method method) {
+ Type genericReturnType = method.getGenericReturnType();
+ // logger.log("Method '" + method.toGenericString() + "' has return type : " + genericReturnType.getClass().getInterfaces()[0].getSimpleName() + " : " + genericReturnType);
+
+ if (genericReturnType instanceof Class) {
+ return new NotGenericReturnTypeSupport(genericReturnType);
+ }
+ if (genericReturnType instanceof ParameterizedType) {
+ return new ParameterizedReturnType(this, method.getTypeParameters(), (ParameterizedType) method.getGenericReturnType());
+ }
+ if (genericReturnType instanceof TypeVariable) {
+ return new TypeVariableReturnType(this, method.getTypeParameters(), (TypeVariable) genericReturnType);
+ }
+
+ throw new MockitoException("Ouch, it shouldn't happen, type '" + genericReturnType.getClass().getCanonicalName() + "' on method : '" + method.toGenericString() + "' is not supported : " + genericReturnType);
+ }
+
+ /**
+ * Create an new instance of {@link GenericMetadataSupport} inferred from a {@link Type}.
+ *
+ * <p>
+ * At the moment <code>type</code> can only be a {@link Class} or a {@link ParameterizedType}, otherwise
+ * it'll throw a {@link MockitoException}.
+ * </p>
+ *
+ * @param type The class from which the {@link GenericMetadataSupport} should be built.
+ * @return The new {@link GenericMetadataSupport}.
+ * @throws MockitoException Raised if type is not a {@link Class} or a {@link ParameterizedType}.
+ */
+ public static GenericMetadataSupport inferFrom(Type type) {
+ Checks.checkNotNull(type, "type");
+ if (type instanceof Class) {
+ return new FromClassGenericMetadataSupport((Class<?>) type);
+ }
+ if (type instanceof ParameterizedType) {
+ return new FromParameterizedTypeGenericMetadataSupport((ParameterizedType) type);
+ }
+
+ throw new MockitoException("Type meta-data for this Type (" + type.getClass().getCanonicalName() + ") is not supported : " + type);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //// Below are specializations of GenericMetadataSupport that could handle retrieval of possible Types
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Generic metadata implementation for {@link Class}.
+ *
+ * Offer support to retrieve generic metadata on a {@link Class} by reading type parameters and type variables on
+ * the class and its ancestors and interfaces.
+ */
+ private static class FromClassGenericMetadataSupport extends GenericMetadataSupport {
+ private final Class<?> clazz;
+
+ public FromClassGenericMetadataSupport(Class<?> clazz) {
+ this.clazz = clazz;
+
+ for (Class currentExploredClass = clazz;
+ currentExploredClass != null && currentExploredClass != Object.class;
+ currentExploredClass = superClassOf(currentExploredClass)
+ ) {
+ readActualTypeParametersOnDeclaringClass(currentExploredClass);
+ }
+ }
+
+ private Class superClassOf(Class currentExploredClass) {
+ Type genericSuperclass = currentExploredClass.getGenericSuperclass();
+ if (genericSuperclass instanceof ParameterizedType) {
+ Type rawType = ((ParameterizedType) genericSuperclass).getRawType();
+ return (Class) rawType;
+ }
+ return (Class) genericSuperclass;
+ }
+
+ private void readActualTypeParametersOnDeclaringClass(Class<?> clazz) {
+ registerTypeParametersOn(clazz.getTypeParameters());
+ registerTypeVariablesOn(clazz.getGenericSuperclass());
+ for (Type genericInterface : clazz.getGenericInterfaces()) {
+ registerTypeVariablesOn(genericInterface);
+ }
+ }
+
+ @Override
+ public Class<?> rawType() {
+ return clazz;
+ }
+ }
+
+
+ /**
+ * Generic metadata implementation for "standalone" {@link ParameterizedType}.
+ *
+ * Offer support to retrieve generic metadata on a {@link ParameterizedType} by reading type variables of
+ * the related raw type and declared type variable of this parameterized type.
+ *
+ * This class is not designed to work on ParameterizedType returned by {@link Method#getGenericReturnType()}, as
+ * the ParameterizedType instance return in these cases could have Type Variables that refer to type declaration(s).
+ * That's what meant the "standalone" word at the beginning of the Javadoc.
+ * Instead use {@link ParameterizedReturnType}.
+ */
+ private static class FromParameterizedTypeGenericMetadataSupport extends GenericMetadataSupport {
+ private final ParameterizedType parameterizedType;
+
+ public FromParameterizedTypeGenericMetadataSupport(ParameterizedType parameterizedType) {
+ this.parameterizedType = parameterizedType;
+ readActualTypeParameters();
+ }
+
+ private void readActualTypeParameters() {
+ registerTypeVariablesOn(parameterizedType.getRawType());
+ registerTypeVariablesOn(parameterizedType);
+ }
+
+ @Override
+ public Class<?> rawType() {
+ return (Class<?>) parameterizedType.getRawType();
+ }
+ }
+
+
+ /**
+ * Generic metadata specific to {@link ParameterizedType} returned via {@link Method#getGenericReturnType()}.
+ */
+ private static class ParameterizedReturnType extends GenericMetadataSupport {
+ private final ParameterizedType parameterizedType;
+ private final TypeVariable[] typeParameters;
+
+ public ParameterizedReturnType(GenericMetadataSupport source, TypeVariable[] typeParameters, ParameterizedType parameterizedType) {
+ this.parameterizedType = parameterizedType;
+ this.typeParameters = typeParameters;
+ this.contextualActualTypeParameters = source.contextualActualTypeParameters;
+
+ readTypeParameters();
+ readTypeVariables();
+ }
+
+ private void readTypeParameters() {
+ registerTypeParametersOn(typeParameters);
+ }
+
+ private void readTypeVariables() {
+ registerTypeVariablesOn(parameterizedType);
+ }
+
+ @Override
+ public Class<?> rawType() {
+ return (Class<?>) parameterizedType.getRawType();
+ }
+
+ }
+
+
+ /**
+ * Generic metadata for {@link TypeVariable} returned via {@link Method#getGenericReturnType()}.
+ */
+ private static class TypeVariableReturnType extends GenericMetadataSupport {
+ private final TypeVariable typeVariable;
+ private final TypeVariable[] typeParameters;
+ private Class<?> rawType;
+
+
+
+ public TypeVariableReturnType(GenericMetadataSupport source, TypeVariable[] typeParameters, TypeVariable typeVariable) {
+ this.typeParameters = typeParameters;
+ this.typeVariable = typeVariable;
+ this.contextualActualTypeParameters = source.contextualActualTypeParameters;
+
+ readTypeParameters();
+ readTypeVariables();
+ }
+
+ private void readTypeParameters() {
+ registerTypeParametersOn(typeParameters);
+ }
+
+ private void readTypeVariables() {
+ for (Type type : typeVariable.getBounds()) {
+ registerTypeVariablesOn(type);
+ }
+ registerTypeParametersOn(new TypeVariable[] { typeVariable });
+ registerTypeVariablesOn(getActualTypeArgumentFor(typeVariable));
+ }
+
+ @Override
+ public Class<?> rawType() {
+ if (rawType == null) {
+ rawType = extractRawTypeOf(typeVariable);
+ }
+ return rawType;
+ }
+
+ private Class<?> extractRawTypeOf(Type type) {
+ if (type instanceof Class) {
+ return (Class<?>) type;
+ }
+ if (type instanceof ParameterizedType) {
+ return (Class<?>) ((ParameterizedType) type).getRawType();
+ }
+ if (type instanceof BoundedType) {
+ return extractRawTypeOf(((BoundedType) type).firstBound());
+ }
+ if (type instanceof TypeVariable) {
+ /*
+ * If type is a TypeVariable, then it is needed to gather data elsewhere. Usually TypeVariables are declared
+ * on the class definition, such as such as List<E>.
+ */
+ return extractRawTypeOf(contextualActualTypeParameters.get(type));
+ }
+ throw new MockitoException("Raw extraction not supported for : '" + type + "'");
+ }
+
+ @Override
+ public List<Type> extraInterfaces() {
+ Type type = extractActualBoundedTypeOf(typeVariable);
+ if (type instanceof BoundedType) {
+ return Arrays.asList(((BoundedType) type).interfaceBounds());
+ }
+ if (type instanceof ParameterizedType) {
+ return Collections.singletonList(type);
+ }
+ if (type instanceof Class) {
+ return Collections.emptyList();
+ }
+ throw new MockitoException("Cannot extract extra-interfaces from '" + typeVariable + "' : '" + type + "'");
+ }
+
+ /**
+ * @return Returns an array with the extracted raw types of {@link #extraInterfaces()}.
+ * @see #extractRawTypeOf(java.lang.reflect.Type)
+ */
+ public Class<?>[] rawExtraInterfaces() {
+ List<Type> extraInterfaces = extraInterfaces();
+ List<Class<?>> rawExtraInterfaces = new ArrayList<Class<?>>();
+ for (Type extraInterface : extraInterfaces) {
+ Class<?> rawInterface = extractRawTypeOf(extraInterface);
+ // avoid interface collision with actual raw type (with typevariables, resolution ca be quite aggressive)
+ if(!rawType().equals(rawInterface)) {
+ rawExtraInterfaces.add(rawInterface);
+ }
+ }
+ return rawExtraInterfaces.toArray(new Class[rawExtraInterfaces.size()]);
+ }
+
+ private Type extractActualBoundedTypeOf(Type type) {
+ if (type instanceof TypeVariable) {
+ /*
+ If type is a TypeVariable, then it is needed to gather data elsewhere. Usually TypeVariables are declared
+ on the class definition, such as such as List<E>.
+ */
+ return extractActualBoundedTypeOf(contextualActualTypeParameters.get(type));
+ }
+ if (type instanceof BoundedType) {
+ Type actualFirstBound = extractActualBoundedTypeOf(((BoundedType) type).firstBound());
+ if (!(actualFirstBound instanceof BoundedType)) {
+ return type; // avoid going one step further, ie avoid : O(TypeVar) -> K(TypeVar) -> Some ParamType
+ }
+ return actualFirstBound;
+ }
+ return type; // irrelevant, we don't manage other types as they are not bounded.
+ }
+ }
+
+
+
+ /**
+ * Non-Generic metadata for {@link Class} returned via {@link Method#getGenericReturnType()}.
+ */
+ private static class NotGenericReturnTypeSupport extends GenericMetadataSupport {
+ private final Class<?> returnType;
+
+ public NotGenericReturnTypeSupport(Type genericReturnType) {
+ returnType = (Class<?>) genericReturnType;
+ }
+
+ @Override
+ public Class<?> rawType() {
+ return returnType;
+ }
+ }
+
+
+
+ /**
+ * Type representing bounds of a type
+ *
+ * @see TypeVarBoundedType
+ * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a>
+ * @see WildCardBoundedType
+ * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1</a>
+ */
+ public interface BoundedType extends Type {
+ Type firstBound();
+
+ Type[] interfaceBounds();
+ }
+
+ /**
+ * Type representing bounds of a type variable, allows to keep all bounds information.
+ *
+ * <p>It uses the first bound in the array, as this array is never null and always contains at least
+ * one element (Object is always here if no bounds are declared).</p>
+ *
+ * <p>If upper bounds are declared with SomeClass and additional interfaces, then firstBound will be SomeClass and
+ * interfacesBound will be an array of the additional interfaces.
+ *
+ * i.e. <code>SomeClass</code>.
+ * <pre class="code"><code class="java">
+ * interface UpperBoundedTypeWithClass<E extends Comparable<E> & Cloneable> {
+ * E get();
+ * }
+ * // will return Comparable type
+ * </code></pre>
+ * </p>
+ *
+ * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a>
+ */
+ public static class TypeVarBoundedType implements BoundedType {
+ private final TypeVariable typeVariable;
+
+
+ public TypeVarBoundedType(TypeVariable typeVariable) {
+ this.typeVariable = typeVariable;
+ }
+
+ /**
+ * @return either a class or an interface (parameterized or not), if no bounds declared Object is returned.
+ */
+ public Type firstBound() {
+ return typeVariable.getBounds()[0]; //
+ }
+
+ /**
+ * On a Type Variable (typeVar extends C_0 & I_1 & I_2 & etc), will return an array
+ * containing I_1 and I_2.
+ *
+ * @return other bounds for this type, these bounds can only be only interfaces as the JLS says,
+ * empty array if no other bound declared.
+ */
+ public Type[] interfaceBounds() {
+ Type[] interfaceBounds = new Type[typeVariable.getBounds().length - 1];
+ System.arraycopy(typeVariable.getBounds(), 1, interfaceBounds, 0, typeVariable.getBounds().length - 1);
+ return interfaceBounds;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ return typeVariable.equals(((TypeVarBoundedType) o).typeVariable);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return typeVariable.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "{firstBound=" + firstBound() + ", interfaceBounds=" + Arrays.deepToString(interfaceBounds()) + '}';
+ }
+
+ public TypeVariable typeVariable() {
+ return typeVariable;
+ }
+ }
+
+ /**
+ * Type representing bounds of a wildcard, allows to keep all bounds information.
+ *
+ * <p>The JLS says that lower bound and upper bound are mutually exclusive, and that multiple bounds
+ * are not allowed.
+ *
+ * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a>
+ */
+ public static class WildCardBoundedType implements BoundedType {
+ private final WildcardType wildcard;
+
+
+ public WildCardBoundedType(WildcardType wildcard) {
+ this.wildcard = wildcard;
+ }
+
+ /**
+ * @return The first bound, either a type or a reference to a TypeVariable
+ */
+ public Type firstBound() {
+ Type[] lowerBounds = wildcard.getLowerBounds();
+ Type[] upperBounds = wildcard.getUpperBounds();
+
+ return lowerBounds.length != 0 ? lowerBounds[0] : upperBounds[0];
+ }
+
+ /**
+ * @return An empty array as, wildcard don't support multiple bounds.
+ */
+ public Type[] interfaceBounds() {
+ return new Type[0];
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ return wildcard.equals(((TypeVarBoundedType) o).typeVariable);
+
+ }
+
+ @Override
+ public int hashCode() {
+ return wildcard.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "{firstBound=" + firstBound() + ", interfaceBounds=[]}";
+ }
+
+ public WildcardType wildCard() {
+ return wildcard;
+ }
+ }
+
+}
+
+
diff --git a/src/main/java/org/mockito/internal/util/reflection/InstanceField.java b/src/main/java/org/mockito/internal/util/reflection/InstanceField.java new file mode 100644 index 0000000..c3f019d --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/InstanceField.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection; + +import org.mockito.internal.util.Checks; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +/** + * Represents an accessible instance field. + * + * Contains the instance reference on which the field can be read adn write. + */ +public class InstanceField { + private final Field field; + private final Object instance; + private FieldReader fieldReader; + + /** + * Create a new InstanceField. + * + * @param field The field that should be accessed, note that no checks are performed to ensure + * the field belong to this instance class. + * @param instance The instance from which the field shall be accessed. + */ + public InstanceField(Field field, Object instance) { + this.field = Checks.checkNotNull(field, "field"); + this.instance = Checks.checkNotNull(instance, "instance"); + } + + /** + * Safely read the field. + * + * @return the field value. + * @see FieldReader + */ + public Object read() { + return reader().read(); + } + + /** + * Set the given value to the field of this instance. + * + * @param value The value that should be written to the field. + * @see FieldSetter + */ + public void set(Object value) { + new FieldSetter(instance, field).set(value); + } + + /** + * Check that the field is not null. + * + * @return <code>true</code> if <code>null</code>, else <code>false</code>. + */ + public boolean isNull() { + return reader().isNull(); + } + + /** + * Check if the field is annotated by the given annotation. + * + * @param annotationClass The annotation type to check. + * @return <code>true</code> if the field is annotated by this annotation, else <code>false</code>. + */ + public boolean isAnnotatedBy(Class<? extends Annotation> annotationClass) { + return field.isAnnotationPresent(annotationClass); + } + + /** + * Returns the annotation instance for the given annotation type. + * + * @param annotationClass Tha annotation type to retrieve. + * @param <A> Type of the annotation. + * @return The annotation instance. + */ + public <A extends Annotation> A annotation(Class<A> annotationClass) { + return field.getAnnotation(annotationClass); + } + + /** + * Returns the JDK {@link Field} instance. + * + * @return The actual {@link Field} instance. + */ + public Field jdkField() { + return field; + } + + private FieldReader reader() { + if (fieldReader == null) { + fieldReader = new FieldReader(instance, field); + } + return fieldReader; + } + + /** + * Returns the name of the field. + * + * @return Name of the field. + */ + public String name() { + return field.getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InstanceField that = (InstanceField) o; + return field.equals(that.field) && instance.equals(that.instance); + } + + @Override + public int hashCode() { + int result = field.hashCode(); + result = 31 * result + instance.hashCode(); + return result; + } +} diff --git a/src/main/java/org/mockito/internal/util/reflection/LenientCopyTool.java b/src/main/java/org/mockito/internal/util/reflection/LenientCopyTool.java new file mode 100644 index 0000000..2418ced --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/LenientCopyTool.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.util.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+@SuppressWarnings("unchecked")
+public class LenientCopyTool {
+
+ FieldCopier fieldCopier = new FieldCopier();
+
+ public <T> void copyToMock(T from, T mock) {
+ copy(from, mock, from.getClass(), mock.getClass().getSuperclass());
+ }
+
+ public <T> void copyToRealObject(T from, T to) {
+ copy(from, to, from.getClass(), to.getClass());
+ }
+
+ private <T> void copy(T from, T to, Class fromClazz, Class toClass) {
+ while (fromClazz != Object.class) {
+ copyValues(from, to, fromClazz);
+ fromClazz = fromClazz.getSuperclass();
+ }
+ }
+
+ private <T> void copyValues(T from, T mock, Class classFrom) {
+ Field[] fields = classFrom.getDeclaredFields();
+
+ for (int i = 0; i < fields.length; i++) {
+ // ignore static fields
+ Field field = fields[i];
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+ AccessibilityChanger accessibilityChanger = new AccessibilityChanger();
+ try {
+ accessibilityChanger.enableAccess(field);
+ fieldCopier.copyValue(from, mock, field);
+ } catch (Throwable t) {
+ //Ignore - be lenient - if some field cannot be copied then let's be it
+ } finally {
+ accessibilityChanger.safelyDisableAccess(field);
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/reflection/Whitebox.java b/src/main/java/org/mockito/internal/util/reflection/Whitebox.java new file mode 100644 index 0000000..54a7cc6 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/Whitebox.java @@ -0,0 +1,55 @@ +/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockito.internal.util.reflection;
+
+import java.lang.reflect.Field;
+
+public class Whitebox {
+
+ public static Object getInternalState(Object target, String field) {
+ Class<?> c = target.getClass();
+ try {
+ Field f = getFieldFromHierarchy(c, field);
+ f.setAccessible(true);
+ return f.get(target);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to get internal state on a private field. Please report to mockito mailing list.", e);
+ }
+ }
+
+ public static void setInternalState(Object target, String field, Object value) {
+ Class<?> c = target.getClass();
+ try {
+ Field f = getFieldFromHierarchy(c, field);
+ f.setAccessible(true);
+ f.set(target, value);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to set internal state on a private field. Please report to mockito mailing list.", e);
+ }
+ }
+
+ private static Field getFieldFromHierarchy(Class<?> clazz, String field) {
+ Field f = getField(clazz, field);
+ while (f == null && clazz != Object.class) {
+ clazz = clazz.getSuperclass();
+ f = getField(clazz, field);
+ }
+ if (f == null) {
+ throw new RuntimeException(
+ "You want me to get this field: '" + field +
+ "' on this class: '" + clazz.getSimpleName() +
+ "' but this field is not declared withing hierarchy of this class!");
+ }
+ return f;
+ }
+
+ private static Field getField(Class<?> clazz, String field) {
+ try {
+ return clazz.getDeclaredField(field);
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+ }
+}
\ No newline at end of file diff --git a/src/main/java/org/mockito/internal/util/reflection/package.html b/src/main/java/org/mockito/internal/util/reflection/package.html new file mode 100644 index 0000000..ac2eca0 --- /dev/null +++ b/src/main/java/org/mockito/internal/util/reflection/package.html @@ -0,0 +1,8 @@ +<!-- + ~ Copyright (c) 2007 Mockito contributors + ~ This program is made available under the terms of the MIT License. + --> + +<body> +reflection utilities +</body>
\ No newline at end of file |