Return-Path: Delivered-To: apmail-aries-commits-archive@www.apache.org Received: (qmail 2779 invoked from network); 27 Feb 2011 18:32:32 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 27 Feb 2011 18:32:32 -0000 Received: (qmail 11833 invoked by uid 500); 27 Feb 2011 18:32:32 -0000 Delivered-To: apmail-aries-commits-archive@aries.apache.org Received: (qmail 11186 invoked by uid 500); 27 Feb 2011 18:32:31 -0000 Mailing-List: contact commits-help@aries.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@aries.apache.org Delivered-To: mailing list commits@aries.apache.org Received: (qmail 11178 invoked by uid 99); 27 Feb 2011 18:32:30 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 27 Feb 2011 18:32:30 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 27 Feb 2011 18:32:22 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id AF4512388A40; Sun, 27 Feb 2011 18:31:59 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1075107 [2/2] - in /aries/tags/testsupport-0.1-incubating: ./ testsupport-unit/ testsupport-unit/src/ testsupport-unit/src/main/ testsupport-unit/src/main/java/ testsupport-unit/src/main/java/org/ testsupport-unit/src/main/java/org/apache/... Date: Sun, 27 Feb 2011 18:31:59 -0000 To: commits@aries.apache.org From: zoe@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20110227183159.AF4512388A40@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/DefaultReturnTypeHandlers.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/DefaultReturnTypeHandlers.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/DefaultReturnTypeHandlers.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/DefaultReturnTypeHandlers.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + *

This class contains some return type handlers that provides some default behavior.

+ */ +public class DefaultReturnTypeHandlers +{ + /** A handler for Longs */ + public static final ReturnTypeHandler LONG_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return Long.valueOf(0); + } + }; + /** A handler for Integers */ + public static final ReturnTypeHandler INT_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return Integer.valueOf(0); + } + }; + /** A handler for Shorts */ + public static final ReturnTypeHandler SHORT_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return Short.valueOf((short)0); + } + }; + /** A handler for Bytes */ + public static final ReturnTypeHandler BYTE_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return Byte.valueOf((byte)0); + } + }; + /** A handler for Characters */ + public static final ReturnTypeHandler CHAR_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return Character.valueOf(' '); + } + }; + /** A handler for Strings */ + public static final ReturnTypeHandler STRING_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return ""; + } + }; + /** A handler for Lists */ + public static final ReturnTypeHandler LIST_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return new ArrayList(); + } + }; + /** A handler for Maps */ + public static final ReturnTypeHandler MAP_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return new HashMap(); + } + }; + /** A handler for Setss */ + public static final ReturnTypeHandler SET_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return new HashSet(); + } + }; + /** A handler for Floats */ + public static final ReturnTypeHandler FLOAT_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return new Float(0.0f); + } + }; + /** A handler for Doubles */ + public static final ReturnTypeHandler DOUBLE_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return new Double(0.0d); + } + }; + /** A handler for Booleans */ + public static final ReturnTypeHandler BOOLEAN_HANDLER = new ReturnTypeHandler() { + public Object handle(Class class1, Skeleton parent) + { + return Boolean.FALSE; + } + }; + /** + * Register all the default handlers against the specified skeleton. + * + * @param s the skeleton + */ + public static void registerDefaultHandlers(Skeleton s) + { + s.registerReturnTypeHandler(Double.class, DOUBLE_HANDLER); + s.registerReturnTypeHandler(Float.class, FLOAT_HANDLER); + s.registerReturnTypeHandler(Long.class, LONG_HANDLER); + s.registerReturnTypeHandler(Integer.class, INT_HANDLER); + s.registerReturnTypeHandler(Short.class, SHORT_HANDLER); + s.registerReturnTypeHandler(Byte.class, BYTE_HANDLER); + s.registerReturnTypeHandler(Boolean.class, BOOLEAN_HANDLER); + s.registerReturnTypeHandler(Character.class, CHAR_HANDLER); + s.registerReturnTypeHandler(String.class, STRING_HANDLER); + s.registerReturnTypeHandler(List.class, LIST_HANDLER); + s.registerReturnTypeHandler(Map.class, MAP_HANDLER); + s.registerReturnTypeHandler(Set.class, SET_HANDLER); + s.registerReturnTypeHandler(double.class, DOUBLE_HANDLER); + s.registerReturnTypeHandler(float.class, FLOAT_HANDLER); + s.registerReturnTypeHandler(long.class, LONG_HANDLER); + s.registerReturnTypeHandler(int.class, INT_HANDLER); + s.registerReturnTypeHandler(short.class, SHORT_HANDLER); + s.registerReturnTypeHandler(byte.class, BYTE_HANDLER); + s.registerReturnTypeHandler(char.class, CHAR_HANDLER); + s.registerReturnTypeHandler(boolean.class, BOOLEAN_HANDLER); + } +} \ No newline at end of file Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ExceptionListener.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ExceptionListener.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ExceptionListener.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ExceptionListener.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks; + +/** + *

This class receives notification that an exception has been thrown from + * a mock object. + *

+ */ +public interface ExceptionListener +{ + /* ------------------------------------------------------------------------ */ + /* exceptionNotification method + /* ------------------------------------------------------------------------ */ + /** + * This method is called when an exception has been thrown from a mock. + * + * @param t the exception or error thrown. + */ + public void exceptionNotification(Throwable t); +} Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCall.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCall.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCall.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCall.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,573 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks; + +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + *

This class represents a method call that has been or is expected to be + * made. It encapsulates the class that the call was made on, the method + * that was invoked and the arguments passed.

+ */ +public final class MethodCall +{ + /** An empty object array */ + private static Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + /** The name of the class invoked */ + private String _className; + /** The array of interfaces implemented by the class */ + private Class[] _interfaces = new Class[0]; + /** The method invoked */ + private String _methodName; + /** The arguments passed */ + private Object[] _arguments = EMPTY_OBJECT_ARRAY; + /** The object invoked */ + private Object _invokedObject; + /** A list of comparators to use, instead of the objects .equals methods */ + private static Map, Comparator> equalsHelpers = new HashMap, Comparator>(); + + /* ------------------------------------------------------------------------ */ + /* MethodCall method + /* ------------------------------------------------------------------------ */ + /** + * This constructor allows a MethodCall to be created when the class can be + * located statically, rather than dynamically. + * + * @param clazz The class. + * @param methodName The method name. + * @param arguments The arguments. + */ + public MethodCall(Class clazz, String methodName, Object ... arguments) + { + _className = clazz.getName(); + _methodName = methodName; + _arguments = arguments; + } + + /* ------------------------------------------------------------------------ */ + /* MethodCall method + /* ------------------------------------------------------------------------ */ + /** + * This method is used by the Skeleton in order create an instance of a + * MethodCall representing an invoked interface. + * + * NOTE: If possible changing this so the constructor does not need to be + * default visibility would be good, given the problems with default + * visibility. + * + * @param invokedObject The object that was invoked. + * @param methodName The name of the method invoked. + * @param arguments The arguments passed. + */ + MethodCall(Object invokedObject, String methodName, Object ... arguments) + { + _className = invokedObject.getClass().getName(); + _interfaces = invokedObject.getClass().getInterfaces(); + _methodName = methodName; + + this._arguments = (arguments == null) ? EMPTY_OBJECT_ARRAY : arguments; + + _invokedObject = invokedObject; + } + + /* ------------------------------------------------------------------------ */ + /* getArguments method + /* ------------------------------------------------------------------------ */ + /** + * This method returns the arguments. + * + * @return The arguments. + */ + public Object[] getArguments() + { + return _arguments; + } + + /* ------------------------------------------------------------------------ */ + /* getClassName method + /* ------------------------------------------------------------------------ */ + /** + * Returns the name of the class the method was invoked or was defined on. + * + * @return the classname. + */ + public String getClassName() + { + return _className; + } + + /* ------------------------------------------------------------------------ */ + /* getMethodName method + /* ------------------------------------------------------------------------ */ + /** + * Returns the name of the method that was (or will be) invoked. + * + * @return the method name + */ + public String getMethodName() + { + return _methodName; + } + + /* ------------------------------------------------------------------------ */ + /* checkClassName method + /* ------------------------------------------------------------------------ */ + /** + * This method checks that the class names specified in the method call are + * compatible, i.e. one is a superclass of the other. + * + * @param one The first method call. + * @param two The second method call. + * @return true if the classes can be assigned to each other. + */ + private boolean checkClassName(MethodCall one, MethodCall two) + { + // TODO make this stuff work better. + if (one._className.equals("java.lang.Object")) + { + return true; + } + else if (two._className.equals("java.lang.Object")) + { + return true; + } + else if (one._className.equals(two._className)) + { + return true; + } + else + { + // check the other class name is one of the implemented interfaces + boolean result = false; + + for (int i = 0; i < two._interfaces.length; i++) + { + if (two._interfaces[i].getName().equals(one._className)) + { + result = true; + break; + } + } + + if (!result) + { + for (int i = 0; i < one._interfaces.length; i++) + { + if (one._interfaces[i].getName().equals(two._className)) + { + result = true; + break; + } + } + } + + return result; + } + } + + /* ------------------------------------------------------------------------ */ + /* equals method + /* ------------------------------------------------------------------------ */ + /** + * Returns true if and only if the two object represent the same call. + * + * @param obj The object to be compared. + * @return true if the specified object is the same as this. + */ + @Override + public boolean equals(Object obj) + { + + if (obj == null) return false; + + if (obj == this) return true; + + if (obj instanceof MethodCall) + { + MethodCall other = (MethodCall)obj; + + if (!checkClassName(this, other)) + { + return false; + } + + if (!other._methodName.equals(this._methodName)) return false; + if (other._arguments.length != this._arguments.length) return false; + + for (int i = 0; i < this._arguments.length; i++) + { + boolean thisArgNull = this._arguments[i] == null; + boolean otherArgClazz = other._arguments[i] instanceof Class; + boolean otherArgNull = other._arguments[i] == null; + boolean thisArgClazz = this._arguments[i] instanceof Class; + + if (thisArgNull) + { + if (otherArgNull) + { + // This is OK + } + else if (otherArgClazz) + { + // This is also OK + } + else + { + return false; + } + // this argument is OK. + } + else if (otherArgNull) + { + if (thisArgClazz) + { + // This is OK + } + else + { + return false; + } + // this argument is OK. + } + else if (otherArgClazz) + { + if (thisArgClazz) + { + Class otherArgClass = (Class) other._arguments[i]; + Class thisArgClass = (Class) this._arguments[i]; + + if (otherArgClass.equals(Class.class) || thisArgClass.equals(Class.class)) + { + // do nothing + } else if (!(otherArgClass.isAssignableFrom(thisArgClass) || + thisArgClass.isAssignableFrom(otherArgClass))) + { + return false; + } + } + else + { + Class clazz = (Class)other._arguments[i]; + if (clazz.isPrimitive()) + { + if (clazz.equals(byte.class)) + { + return this._arguments[i].getClass().equals(Byte.class); + } + else if (clazz.equals(boolean.class)) + { + return this._arguments[i].getClass().equals(Boolean.class); + } + else if (clazz.equals(short.class)) + { + return this._arguments[i].getClass().equals(Short.class); + } + else if (clazz.equals(char.class)) + { + return this._arguments[i].getClass().equals(Character.class); + } + else if (clazz.equals(int.class)) + { + return this._arguments[i].getClass().equals(Integer.class); + } + else if (clazz.equals(long.class)) + { + return this._arguments[i].getClass().equals(Long.class); + } + else if (clazz.equals(float.class)) + { + return this._arguments[i].getClass().equals(Float.class); + } + else if (clazz.equals(double.class)) + { + return this._arguments[i].getClass().equals(Double.class); + } + } + else + { + if (!clazz.isInstance(this._arguments[i])) + { + return false; + } + } + } + } + else if (thisArgClazz) + { + Class clazz = (Class)this._arguments[i]; + if (clazz.isPrimitive()) + { + if (clazz.equals(byte.class)) + { + return other._arguments[i].getClass().equals(Byte.class); + } + else if (clazz.equals(boolean.class)) + { + return other._arguments[i].getClass().equals(Boolean.class); + } + else if (clazz.equals(short.class)) + { + return other._arguments[i].getClass().equals(Short.class); + } + else if (clazz.equals(char.class)) + { + return other._arguments[i].getClass().equals(Character.class); + } + else if (clazz.equals(int.class)) + { + return other._arguments[i].getClass().equals(Integer.class); + } + else if (clazz.equals(long.class)) + { + return other._arguments[i].getClass().equals(Long.class); + } + else if (clazz.equals(float.class)) + { + return other._arguments[i].getClass().equals(Float.class); + } + else if (clazz.equals(double.class)) + { + return other._arguments[i].getClass().equals(Double.class); + } + } + else + { + if (!clazz.isInstance(other._arguments[i])) + { + return false; + } + } + } + else if (this._arguments[i] instanceof Object[] && other._arguments[i] instanceof Object[]) + { + return equals((Object[])this._arguments[i], (Object[])other._arguments[i]); + } + else + { + int result = compareUsingComparators(this._arguments[i], other._arguments[i]); + + if (result == 0) continue; + else if (result == 1) return false; + else if (!!!this._arguments[i].equals(other._arguments[i])) return false; + } + } + } + + return true; + } + + /** + * Compare two arrays calling out to the custom comparators and handling + * AtomicIntegers nicely. + * + * TODO remove the special casing for AtomicInteger. + * + * @param arr1 + * @param arr2 + * @return true if the arrays are equals, false otherwise. + */ + private boolean equals(Object[] arr1, Object[] arr2) + { + if (arr1.length != arr2.length) return false; + + for (int k = 0; k < arr1.length; k++) { + if (arr1[k] == arr2[k]) continue; + if (arr1[k] == null && arr2[k] != null) return false; + if (arr1[k] != null && arr2[k] == null) return false; + + int result = compareUsingComparators(arr1[k], arr2[k]); + + if (result == 0) continue; + else if (result == 1) return false; + + if (arr1[k] instanceof AtomicInteger && arr2[k] instanceof AtomicInteger && + ((AtomicInteger)arr1[k]).intValue() == ((AtomicInteger)arr2[k]).intValue()) + continue; + + if (!!!arr1[k].equals(arr2[k])) return false; + + } + + return true; + } + + /** + * Attempt to do the comparison using the comparators. This logic returns: + * + *
    + *
  • 0 if they are equal
  • + *
  • 1 if they are not equal
  • + *
  • -1 no comparison was run
  • + *
+ * + * @param o1 The first object. + * @param o2 The second object. + * @return 0, 1 or -1 depending on whether the objects were equal, not equal or no comparason was run. + */ + private int compareUsingComparators(Object o1, Object o2) + { + if (o1.getClass() == o2.getClass()) { + @SuppressWarnings("unchecked") + Comparator compare = (Comparator) equalsHelpers.get(o1.getClass()); + + if (compare != null) { + if (compare.compare(o1, o2) == 0) return 0; + else return 1; + } + } + + return -1; + } + + /* ------------------------------------------------------------------------ */ + /* hashCode method + /* ------------------------------------------------------------------------ */ + /** + * Returns the hashCode (obtained by returning the hashCode of the + * methodName). + * + * @return The hashCode + */ + @Override + public int hashCode() + { + return _methodName.hashCode(); + } + + /* ------------------------------------------------------------------------ */ + /* toString method + /* ------------------------------------------------------------------------ */ + /** + * Returns a string representation of the method call. + * + * @return string representation. + */ + @Override + public String toString() + { + StringBuffer buffer = new StringBuffer(); + buffer.append(this._className); + buffer.append('.'); + buffer.append(this._methodName); + buffer.append("("); + + for (int i = 0; i < this._arguments.length; i++) + { + if (this._arguments[i] != null) + { + if (this._arguments[i] instanceof Class) + { + buffer.append(((Class)this._arguments[i]).getName()); + } + else if (Proxy.isProxyClass(this._arguments[i].getClass())) + { + // If the object is a dynamic proxy, just use the proxy class name to avoid calling toString on the proxy + buffer.append(this._arguments[i].getClass().getName()); + } + else if (this._arguments[i] instanceof Object[]) + { + buffer.append(Arrays.toString((Object[])this._arguments[i])); + } + else + { + buffer.append(String.valueOf(this._arguments[i])); + } + } + else + { + buffer.append("null"); + } + + if (i + 1 < this._arguments.length) + buffer.append(", "); + } + + buffer.append(")"); + String string = buffer.toString(); + return string; + } + + /* ------------------------------------------------------------------------ */ + /* getInterfaces method + /* ------------------------------------------------------------------------ */ + /** + * This method returns the list of interfaces implemented by the class that + * was called. + * + * @return Returns the interfaces. + */ + public Class[] getInterfaces() + { + return this._interfaces; + } + + /* ------------------------------------------------------------------------ */ + /* getInvokedObject method + /* ------------------------------------------------------------------------ */ + /** + * This method returns the invoked object. + * + * @return The object that was invoked or null if an expected call. + */ + public Object getInvokedObject() + { + return _invokedObject; + } + + /* ------------------------------------------------------------------------ */ + /* registerEqualsHelper method + /* ------------------------------------------------------------------------ */ + /** + * The native equals for an object may not provide the behaviour required by + * the tests. As an example AtomicInteger does not define a .equals, but tests + * may wish to compare it being passed in a method call for equality. This + * method allows a Comparator to be specified for any type and the Comparator + * will be used to determine equality in place of the .equals method. + * + *

The Comparator must not throw exceptions, and must return 0 for equality + * or any other integer for inequality. + *

+ * + * @param the type of the class and comparator. + * @param type the type of the class for which the comparator will be called. + * @param comparator the comparator to call. + */ + public static void registerEqualsHelper(Class type, Comparator comparator) + { + equalsHelpers.put(type, comparator); + } + + /* ------------------------------------------------------------------------ */ + /* removeEqualsHelper method + /* ------------------------------------------------------------------------ */ + /** + * This method removes any registered comparator specified for the given type. + * + * @param type the type to remove the comparator from. + */ + public static void removeEqualsHelper(Class type) + { + equalsHelpers.remove(type); + } +} \ No newline at end of file Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCallHandler.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCallHandler.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCallHandler.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/MethodCallHandler.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks; + +/** + * Implementations of this interface perform function when a method is called. The + * handler is provided details of the method called along with the skeleton used + * for the call. + */ +public interface MethodCallHandler +{ + /** + * @param methodCall the method that was called + * @param parent the skeleton it was called on + * @return an object to be returned (optional) + * @throws Exception an exception in case of failure. + */ + public Object handle(MethodCall methodCall, Skeleton parent) throws Exception; +} Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ReturnTypeHandler.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ReturnTypeHandler.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ReturnTypeHandler.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/ReturnTypeHandler.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks; + +/** + *

Return type handlers return objects that implement the specified class.

+ */ +public interface ReturnTypeHandler +{ + + /** + * This method is called when a method call handler has not been registered + * and an object of a specific type needs to be returned. The handle method + * is called along with the type that is required. + * + * @param clazz the class to create an object for + * @param parent the skeleton requesting the class. + * @return an instance of the class, or something that can be assigned to it. + * @throws Exception if a failure occurs. + */ + public Object handle(Class clazz, Skeleton parent) throws Exception; +} \ No newline at end of file Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/Skeleton.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/Skeleton.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/Skeleton.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/Skeleton.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,1353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks; + +import java.lang.ref.SoftReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import junit.framework.Assert; +import junit.framework.AssertionFailedError; + +import org.apache.aries.unittest.mocks.annotations.InjectSkeleton; +import org.apache.aries.unittest.mocks.annotations.Singleton; + +/** + *

The Skeleton class is an implementation of the + * java.lang.reflect.InvocationHandler that can be used for + * dynamic mock objects. + *

+ * + *
    + *
  1. The static newMock methods can be used to create completely new mock + * objects backed by an entirely new skeleton. + *
  2. + *
  3. The static getSkeleton method can be used to obtain the skeleton + * backing a given mock. + *
  4. + *
  5. The createMock methods can be used to create a new mock object based on + * the skeleton that is invoked. + *
  6. + *
  7. The registerMethodCallHandler method can be used to register a handler + * that will be invoked when a method is called. + *
  8. + *
  9. The registerReturnTypeHandler method can be used to register a handler + * that will be invoked when a method with a specific return type is + * invoked. It should be noted that registered ReturnTypeHandlers will be + * invoked only if a method call handler has not been registered for the + * method that was invoked. + *
  10. + *
  11. The setReturnValue method can be used to set a value that will be + * returned when a method is invoked. + *
  12. + *
  13. The checkCalls methods can be used to determine if the methods in the + * list should have been called. They return a boolean to indicate if the + * expected calls occurred. + *
  14. + *
  15. The assertCalls method performs the same operation as the checkCalls, + * but throws an junit.framework.AssertionFailedError if the calls don't + * match. This intended for use within the junit test framework + *
  16. + *
  17. If no method call or return type handlers have been registered for a + * call then if the return type is an interface then a mock that implements + * that interface will be returned, otherwise null will be returned. + *
  18. + *
+ */ +public final class Skeleton implements InvocationHandler +{ + /** A list of calls made on this skeleton */ + private List _methodCalls; + /** The invocation handler to call after MethodCall and ReturnType handlers */ + private DefaultInvocationHandler default_Handler; + /** The method call handlers */ + private Map _callHandlers; + /** The type handlers */ + private Map, ReturnTypeHandler> _typeHandlers; + /** The parameter map */ + private Map _mockParameters; + /** A Map of mock objects to Maps of properties */ + private Map> _objectProperties; + /** A Map of exception notification listeners */ + private Map, List> _notificationListeners; + /** The template class used to create this Skeleton, may be null */ + private Object _template; + /** Cached template objects */ + private static ConcurrentMap> _singletonMocks = new ConcurrentHashMap>(); + + // Constructors + + /* ------------------------------------------------------------------------ */ + /* Skeleton constructor + /* ------------------------------------------------------------------------ */ + /** + * constructs the skeleton with the default method call handlers and the + * default return type handlers. + */ + private Skeleton() + { + reset(); + } + + // Static methods create methods + + /* ------------------------------------------------------------------------ */ + /* newMock method + /* ------------------------------------------------------------------------ */ + /** + * This method returns a completely new mock object backed by a new skeleton + * object. It is equivalent to + * new Skeleton().createMock(interfaceClazzes) + * + * @param interfaceClazzes the classes the mock should implement + * @return the new mock object. + */ + public final static Object newMock(Class ... interfaceClazzes) + { + return new Skeleton().createMock(interfaceClazzes); + } + + /* ------------------------------------------------------------------------ */ + /* newMock method + /* ------------------------------------------------------------------------ */ + /** + * This method returns a completely new mock object backed by a new skeleton + * object. It is equivalent to + * new Skeleton().createMock(interfaceClazzes) + * + * @param The object type. + * @param interfaceClazz the classes the mock should implement + * @return the new mock object. + */ + public final static T newMock(Class interfaceClazz) + { + return interfaceClazz.cast(new Skeleton().createMock(interfaceClazz)); + } + + /** + * It is often the case that only a subset of methods on an interface are needed, but + * those methods that are needed are quite complex. In this case a static mock forces + * you into implementing lots of methods you do not need, and produces problems when + * new methods are added to the interface being implemented. This method can essentially + * be used to complete the interface implementation. The object passed in is an instance + * of a class that implements a subset of the methods on the supplied interface. It does + * not need to implement the interface itself. The returned object will implement the full + * interface and delegate to the methods on the templateObject where necessary. + * + * @param The object type. + * @param template The template object for the mock + * @param interfaceClazz The interface to implement + * @return An implementation of the interface that delegates (where appropraite) onto the template. + */ + public final static T newMock(final Object template, Class interfaceClazz) + { + Class templateClass = template.getClass(); + + if (templateClass.getAnnotation(Singleton.class) != null) { + SoftReference mock = _singletonMocks.get(template); + if (mock != null) { + Object theMock = mock.get(); + if (theMock == null) { + _singletonMocks.remove(template); + } else if (interfaceClazz.isInstance(theMock)) { + return interfaceClazz.cast(theMock); + } + } + } + + Skeleton s = new Skeleton(); + s._template = template; + + for (Method m : interfaceClazz.getMethods()) { + try { + final Method m2 = templateClass.getMethod(m.getName(), m.getParameterTypes()); + + MethodCall mc = new MethodCall(interfaceClazz, m.getName(), (Object[])m.getParameterTypes()); + s.registerMethodCallHandler(mc, new MethodCallHandler() + { + public Object handle(MethodCall methodCall, Skeleton parent) throws Exception + { + + try { + m2.setAccessible(true); + return m2.invoke(template, methodCall.getArguments()); + } catch (InvocationTargetException ite) { + if(ite.getTargetException() instanceof Exception) + throw (Exception)ite.getTargetException(); + else throw new Exception(ite.getTargetException()); + } + } + }); + } catch (NoSuchMethodException e) { + // do nothing here, it is a method not on the interface so ignore it. + } + } + + Field[] fs = template.getClass().getFields(); + + for (Field f : fs) { + InjectSkeleton sk = f.getAnnotation(InjectSkeleton.class); + + if (sk != null) { + f.setAccessible(true); + try { + f.set(template, s); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + Object o = s.createMock(interfaceClazz); + _singletonMocks.put(template, new SoftReference(o) { + @Override + public boolean enqueue() + { + _singletonMocks.remove(template); + + System.out.println("Done cleanup"); + + return super.enqueue(); + } + }); + return interfaceClazz.cast(o); + } + + // static mock query methods + + /* ------------------------------------------------------------------------ */ + /* getSkeleton method + /* ------------------------------------------------------------------------ */ + /** + * This method returns the Skeleton backing the supplied mock object. If the + * supplied object is not a mock an IllegalArgumentException will be thrown. + * + * @param mock the mock object + * @return the skeleton backing the mock object + * @throws IllegalArgumentException thrown if the object is not a mock. + */ + public final static Skeleton getSkeleton(Object mock) + throws IllegalArgumentException + { + InvocationHandler ih = Proxy.getInvocationHandler(mock); + if (ih instanceof Skeleton) + { + return (Skeleton)ih; + } + throw new IllegalArgumentException("The supplied proxy (" + mock + ") was not an Aries dynamic mock "); + } + + /* ------------------------------------------------------------------------ */ + /* isSkeleton method + /* ------------------------------------------------------------------------ */ + /** + * This method returns true if and only the provided object is backed by a + * Skeleton. Another way to think about this is if it returns true then a + * call to getSkeleton will not result in an IllegalArgumentException, and is + * guaranteed to return a Skeleton. + * + * @param mock the mock to test. + * @return true if it is backed by a skeleton. + */ + public final static boolean isSkeleton(Object mock) + { + if (Proxy.isProxyClass(mock.getClass())) { + InvocationHandler ih = Proxy.getInvocationHandler(mock); + + return (ih instanceof Skeleton); + } + + return false; + } + + // InvocationHandler defined methods. + + /* ------------------------------------------------------------------------ */ + /* invoke method + /* ------------------------------------------------------------------------ */ + /** + * This method is invoked by the mock objects. It constructs a MethodCall + * object representing the call and adds it to the list of calls that were + * made. (It should be noted that if the method is toString, hashCode or + * equals then they are not added to the list.) It then calls a registered + * MethodCallHandler, if a MethodCallHandler is not registered then a + * ReturnTypeHandler is invoked. If a ReturnTypeHandler is not invoked then + * the registered default InvocationHandler is called. By default the Skeleton + * is constructed with a DefaultInvocationHandler. If the invoked method has + * an interface as a return type then the DefaultInvocationHandler will return + * a new mock implementing that interface. If the return type is a class null + * will be returned. + * + * @param targetObject The mock object that was invoked. + * @param calledMethod The method that was called. + * @param arguments The arguments that were passed. + * @return The return of the method invoked. + * @throws Throwable Any exception thrown. + */ + public Object invoke(Object targetObject, Method calledMethod, Object[] arguments) + throws Throwable + { + String methodName = calledMethod.getName(); + MethodCall call = new MethodCall(targetObject, methodName, arguments); + + if (!DefaultMethodCallHandlers.isDefaultMethodCall(call)) + { + _methodCalls.add(call); + } + + Object result; + + try + { + if (_callHandlers.containsKey(call)) + { + MethodCallHandler handler = _callHandlers.get(call); + result = handler.handle(call, this); + } + else if (isReadWriteProperty(targetObject.getClass(), calledMethod)) + { + String propertyName = methodName.substring(3); + if (methodName.startsWith("get") || methodName.startsWith("is")) + { + if (methodName.startsWith("is")) propertyName = methodName.substring(2); + + Map properties = _objectProperties.get(targetObject); + if (properties == null) + { + properties = new HashMap(); + _objectProperties.put(targetObject, properties); + } + + if (properties.containsKey(propertyName)) + { + result = properties.get(propertyName); + } + else if (_typeHandlers.containsKey(calledMethod.getReturnType())) + { + result = createReturnTypeProxy(calledMethod.getReturnType()); + } + else + { + result = default_Handler.invoke(targetObject, calledMethod, arguments); + } + } + // Must be a setter. + else + { + Map properties = _objectProperties.get(targetObject); + if (properties == null) + { + properties = new HashMap(); + _objectProperties.put(targetObject, properties); + } + + properties.put(propertyName, arguments[0]); + result = null; + } + } + else if (_typeHandlers.containsKey(calledMethod.getReturnType())) + { + result = createReturnTypeProxy(calledMethod.getReturnType()); + } + else + { + result = default_Handler.invoke(targetObject, calledMethod, arguments); + } + } + catch (Throwable t) + { + Class throwableType = t.getClass(); + List listeners = _notificationListeners.get(throwableType); + if (listeners != null) + { + for (ExceptionListener listener : listeners) + { + listener.exceptionNotification(t); + } + } + + throw t; + } + + return result; + } + + // MethodCall registration methods + + /* ------------------------------------------------------------------------ */ + /* registerMethodCallHandler method + /* ------------------------------------------------------------------------ */ + /** + * This method registers a MethodCallHandler for the specified MethodCall. + * + * @param call The method that was called. + * @param handler The MethodCallHandler. + */ + public void registerMethodCallHandler(MethodCall call, MethodCallHandler handler) + { + deRegisterMethodCallHandler(call); + _callHandlers.put(call, handler); + } + + /* ------------------------------------------------------------------------ */ + /* deRegisterMethodCallHandler method + /* ------------------------------------------------------------------------ */ + /** + * This method removes a registered MethodCallHandler for the specified + * MethodCall. + * + * @param call the specified MethodCall + */ + public void deRegisterMethodCallHandler(MethodCall call) + { + _callHandlers.remove(call); + } + + /* ------------------------------------------------------------------------ */ + /* reset method + /* ------------------------------------------------------------------------ */ + /** + * This method resets the skeleton to the state it was in prior just after it + * was constructed. + */ + public void reset() + { + _methodCalls = new LinkedList(); + _callHandlers = new HashMap(); + _typeHandlers = new HashMap, ReturnTypeHandler>(); + DefaultReturnTypeHandlers.registerDefaultHandlers(this); + DefaultMethodCallHandlers.registerDefaultHandlers(this); + default_Handler = new DefaultInvocationHandler(this); + _mockParameters = new HashMap(); + _objectProperties = new HashMap>(); + _notificationListeners = new HashMap, List>(); + } + + /* ------------------------------------------------------------------------ */ + /* clearMethodCalls method + /* ------------------------------------------------------------------------ */ + /** + * This method clears the method calls list for the skeleton + */ + public void clearMethodCalls() + { + _methodCalls = new LinkedList(); + } + + + /* ------------------------------------------------------------------------ */ + /* setReturnValue method + /* ------------------------------------------------------------------------ */ + /** + * This is a convenience method for registering a method call handler where + * a specific value should be returned when a method is called, rather than + * some logic needs to be applied. The value should be an object or the object + * version of the primitive for the methods return type, so if the method + * returns short the value must be an instance of java.lang.Short, not + * java.lang.Integer. + * + * @param call the method being called. + * @param value the value to be returned when that method is called. + */ + public void setReturnValue(MethodCall call, final Object value) + { + Class clazz; + try { + clazz = Class.forName(call.getClassName()); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("This should be impossible as we have already seen this class loaded"); + } + + + Method[] methods = clazz.getMethods(); + + methods: for (Method m : methods) { + if(!!!m.getName().equals(call.getMethodName())) + continue methods; + + Object[] args = call.getArguments(); + Class[] parms = m.getParameterTypes(); + + if (args.length == parms.length) { + for (int i = 0; i < args.length; i++) { + if (args[i] instanceof Class && args[i].equals(parms[i])) { + } else if (parms[i].isInstance(args[i])) { + } else { + continue methods; + } + } + + Class returnType = m.getReturnType(); + if (returnType.isPrimitive()) { + if (returnType == boolean.class) returnType = Boolean.class; + else if (returnType == byte.class) returnType = Byte.class; + else if (returnType == short.class) returnType = Short.class; + else if (returnType == char.class) returnType = Character.class; + else if (returnType == int.class) returnType = Integer.class; + else if (returnType == long.class) returnType = Long.class; + else if (returnType == float.class) returnType = Float.class; + else if (returnType == double.class) returnType = Double.class; + } + + if (value != null && !!!returnType.isInstance(value)) { + throw new IllegalArgumentException("The object cannot be returned by the requested method: " + call); + } else break methods; + } + } + + + + registerMethodCallHandler(call, new MethodCallHandler() + { + public Object handle(MethodCall methodCall, Skeleton parent) throws Exception + { + return value; + } + }); + } + + /* ------------------------------------------------------------------------ */ + /* setThrow method + /* ------------------------------------------------------------------------ */ + /** + * This is a convenience method for registering a method call handler where + * a specific exception should be thrown when the method is called, rather + * than some logic needs to be applied. + * + * @param call the method being called + * @param thingToThrow the exception to throw. + */ + public void setThrows(MethodCall call, final Exception thingToThrow) + { + registerMethodCallHandler(call, new MethodCallHandler() + { + public Object handle(MethodCall methodCall, Skeleton parent) throws Exception + { + thingToThrow.fillInStackTrace(); + throw thingToThrow; + } + }); + } + + /* ------------------------------------------------------------------------ */ + /* setThrow method + /* ------------------------------------------------------------------------ */ + /** + * This is a convenience method for registering a method call handler where + * a specific exception should be thrown when the method is called, rather + * than some logic needs to be applied. + * + * @param call the method being called + * @param thingToThrow the exception to throw. + */ + public void setThrows(MethodCall call, final Error thingToThrow) + { + registerMethodCallHandler(call, new MethodCallHandler() + { + public Object handle(MethodCall methodCall, Skeleton parent) throws Exception + { + thingToThrow.fillInStackTrace(); + throw thingToThrow; + } + }); + } + + // ReturnType registration methods + + /* ------------------------------------------------------------------------ */ + /* registerReturnTypeHandler method + /* ------------------------------------------------------------------------ */ + /** + * This method registers a ReturnTypeHandler for the specified class. + * + * @param clazz The class to be handled. + * @param handler The ReturnTypeHandler + */ + public void registerReturnTypeHandler(Class clazz, ReturnTypeHandler handler) + { + deRegisterReturnTypeHandler(clazz); + _typeHandlers.put(clazz, handler); + } + + /* ------------------------------------------------------------------------ */ + /* deRegisterReturnTypeHandler method + /* ------------------------------------------------------------------------ */ + /** + * This method removes a registration for a ReturnTypeHandler for the + * specified class. + * + * @param clazz The class to deregister the handler for. + */ + public void deRegisterReturnTypeHandler(Class clazz) + { + _typeHandlers.remove(clazz); + } + + // Exception notification methods + + /* ------------------------------------------------------------------------ */ + /* registerExceptionListener method + /* ------------------------------------------------------------------------ */ + /** + * This method registers an ExceptionListener when the specified Exception is + * thrown. + * + * @param throwableType The type of the Throwable + * @param listener The listener. + */ + public void registerExceptionListener(Class throwableType, ExceptionListener listener) + { + List l = _notificationListeners.get(throwableType); + if (l == null) + { + l = new ArrayList(); + _notificationListeners.put(throwableType, l); + } + l.add(listener); + } + + // parameter related methods + + /* ------------------------------------------------------------------------ */ + /* setParameter method + /* ------------------------------------------------------------------------ */ + /** + * This method allows a parameter to be set. It is intended to be used by + * MethodCallHandlers and ReturnTypeHandlers. + * + * @param key The key + * @param value The value + */ + public void setParameter(String key, Object value) + { + _mockParameters.put(key, value); + } + + /* ------------------------------------------------------------------------ */ + /* getParameter method + /* ------------------------------------------------------------------------ */ + /** + * This method allows a parameter to be retrieved. + * + * @param key the key the parameter was set using + * @return the parameter + */ + public Object getParameter(String key) + { + return _mockParameters.get(key); + } + + /* ------------------------------------------------------------------------ */ + /* getTemplateObject method + /* ------------------------------------------------------------------------ */ + /** + * @return the template object, if one was used when initializing this skeleton. + */ + public Object getTemplateObject() + { + return _template; + } + + // default InvocationHandler related methods + + /** + * @param defaultHandler The defaultHandler to set. + */ + public void setDefaultHandler(DefaultInvocationHandler defaultHandler) + { + this.default_Handler = defaultHandler; + } + + // MethodCall list check methods + + /* ------------------------------------------------------------------------ */ + /* checkCalls method + /* ------------------------------------------------------------------------ */ + /** + * This method checks that the calls in the list occurred. If the addCalls + * boolean is true then their must be an exact match. If the allCalls boolean + * is false then the calls in the list must occur in that order, but other + * calls can be in between. + * + * @param calls The expected calls list + * @param allCalls true if an exact match comparison should be performed + * @return true if they the expected calls match. + */ + public boolean checkCalls(List calls, boolean allCalls) + { + boolean found = false; + if (allCalls) + { + return calls.equals(_methodCalls); + } + else + { + Iterator actual = _methodCalls.iterator(); + for (MethodCall expectedCall : calls) + { + found = false; + actual: while (actual.hasNext()) + { + MethodCall actualCall = actual.next(); + if (actualCall.equals(expectedCall)) + { + found = true; + break actual; + } + } + } + } + + return found; + } + + /* ------------------------------------------------------------------------ */ + /* checkCall method + /* ------------------------------------------------------------------------ */ + /** + * Checks that the specified method has been called on this skeleton + * + * @param call the call that should have been called. + * @return true if the MethodCall occurs in the list. + */ + public boolean checkCall(MethodCall call) + { + return this._methodCalls.contains(call); + } + + // MethodCall list assert methods + + /* ------------------------------------------------------------------------ */ + /* assertCalls method + /* ------------------------------------------------------------------------ */ + /** + * This method checks that the MethodCalls objects in the given list were + * made and throws an AssertionFailedError if they were not. If allCalls is + * true the given list and the calls list must be identical. If allCalls is + * false other calls could have been made on the skeleton in between ones + * specified in the list. + * + * @param calls the list of calls + * @param allCalls whether an exact match between the lists is required + * @throws AssertionFailedError if a failure has occurred. + */ + public void assertCalled(List calls, boolean allCalls) throws AssertionFailedError + { + if (allCalls) + { + if ((calls == null) && (_methodCalls == null)) return; + + if (calls == null) + { + throw new AssertionFailedError("expected null, but was " + _methodCalls); + } + + if (_methodCalls == null) + { + throw new AssertionFailedError("expected:" + calls + " but was null"); + } + + if (calls.equals(_methodCalls)) return; + + // OK compare lists and decide on differences - initially all the lists are different + int startOfDifferences = 0; + // Remove the common start of sequence + boolean lastItemSame = true; + + for (int i = 0; i < calls.size() && i < _methodCalls.size() && lastItemSame; i++) + { + if ((calls.get(i) == null) && (_methodCalls.get(i) == null)) + { + lastItemSame = true; + } + else if ((calls.get(i) == null) || (_methodCalls.get(i) == null)) + { + lastItemSame = false; + } + else + { + lastItemSame = calls.get(i).equals(_methodCalls.get(i)); + } + + if (lastItemSame) startOfDifferences++; + + }//for + // Now remove the common bit at the end + int endOfDifferencesInExpected = calls.size(); + int endOfDifferencesInReceived = _methodCalls.size(); + lastItemSame = true; + + while ((endOfDifferencesInExpected > startOfDifferences) + && (endOfDifferencesInReceived > startOfDifferences) + && lastItemSame) + { + int ap = endOfDifferencesInExpected - 1; + int bp = endOfDifferencesInReceived - 1; + + if ((calls.get(ap) == null) && (_methodCalls.get(bp) == null)) + { + lastItemSame = true; + } + else if ((calls.get(ap) == null) || (_methodCalls.get(bp) == null)) + { + lastItemSame = false; + } + else + { + lastItemSame = calls.get(ap).equals(_methodCalls.get(bp)); + } + + if (lastItemSame) + { + endOfDifferencesInExpected--; + endOfDifferencesInReceived--; + } + + }//while + + String failureText; + // OK, now build the failureText + if (endOfDifferencesInExpected == startOfDifferences) + { + failureText = + "Expected calls and actual calls differed because " + + _methodCalls.subList(startOfDifferences, endOfDifferencesInReceived) + + " inserted after element " + + startOfDifferences; + + } + else if (endOfDifferencesInReceived == startOfDifferences) + { + failureText = + "Expected calls and actual calls differed because " + + calls.subList(startOfDifferences, endOfDifferencesInExpected) + + " missing after element " + + startOfDifferences; + + } + else + { + if ((endOfDifferencesInExpected == startOfDifferences + 1) + && (endOfDifferencesInReceived == startOfDifferences + 1)) + { + + failureText = + "Expected calls and actual calls differed because element " + + startOfDifferences + + " is different (calls:" + + calls.get(startOfDifferences) + + " but was:"+_methodCalls.get(startOfDifferences)+") "; + + } + else if (endOfDifferencesInExpected == startOfDifferences + 1) + { + + failureText = + "Expected calls and actual calls differed because element " + + startOfDifferences + + " (" + + calls.get(startOfDifferences) + + ") has been replaced by " + + _methodCalls.subList(startOfDifferences, endOfDifferencesInReceived); + } + else + { + failureText = + "Expected calls and actual calls differed because elements between " + + startOfDifferences + + " and " + + (endOfDifferencesInExpected - 1) + + " are different (expected:" + + calls.subList(startOfDifferences, endOfDifferencesInExpected) + + " but was:" + + _methodCalls.subList(startOfDifferences, endOfDifferencesInReceived) + + ")"; + }//if + }//if + + throw new AssertionFailedError(failureText + " expected:" + calls + " but was:" + _methodCalls); + } + else + { + Iterator expected = calls.iterator(); + Iterator actual = _methodCalls.iterator(); + while (expected.hasNext()) + { + boolean found = false; + MethodCall expectedCall = expected.next(); + MethodCall actualCall = null; + + actual: while (actual.hasNext()) + { + actualCall = actual.next(); + if (actualCall.equals(expectedCall)) + { + found = true; + break actual; + } + } + + if (!found) + { + throw new AssertionFailedError( "The method call " + + expectedCall + + " was expected but has not occurred (actual calls = "+_methodCalls+")"); + } + } + } + } + + /* ------------------------------------------------------------------------ */ + /* assertCall method + /* ------------------------------------------------------------------------ */ + /** + * This does the same as checkCall, but throws an + * junit.framework.AssertionFailedError if the call did not occur. + * + * @param call the call that was expected + */ + public void assertCalled(MethodCall call) + { + if (!checkCall(call)) + { + throw new AssertionFailedError("The method call " + + call + + " was expected but has not occurred. Actual calls: " + getMethodCallsAsString()); + } + } + + /* ------------------------------------------------------------------------ */ + /* assertCalledExactNumberOfTimes method + /* ------------------------------------------------------------------------ */ + /** + * This method asserts that the method specified in the call parameter has + * been called the number of times specified by numberOfCalls. If + * numberOfCalls is zero this method is equivalent to assertNotCalled. + * + * @param call The call that was made. + * @param numberOfCalls The number of times the call should have been made. + */ + public void assertCalledExactNumberOfTimes(MethodCall call, int numberOfCalls) + { + int callCount = 0; + + for (MethodCall callMade : _methodCalls) + { + if (callMade.equals(call)) + { + callCount++; + } + } + + if (numberOfCalls != callCount) + { + throw new AssertionFailedError("The method call " + call + + " should have been called " + numberOfCalls + + " time(s), but was called " + callCount + " time(s)"); + } + } + + /* ------------------------------------------------------------------------ */ + /* assertNotCalled method + /* ------------------------------------------------------------------------ */ + /** + * This method throws an junit.framework.AssertionFailedError if the specified + * call was invoked on the skeleton. + * + * @param call the call to check. + */ + public void assertNotCalled(MethodCall call) + { + Assert.assertFalse( "The method call " + + call + + " occurred in the skeleton " + + this.toString(), checkCall(call)); + } + + /* ------------------------------------------------------------------------ */ + /* assertMockNotCalled method + /* ------------------------------------------------------------------------ */ + /** + * This method throws an junit.framework.AssertionFailedError if the skeleton + * has had any methods invoked on it. + */ + public void assertSkeletonNotCalled() + { + Assert.assertEquals("The skeleton " + this.toString() + + " has had the following method invoked on it " + getMethodCallsAsString(), + 0, _methodCalls.size()); + } + + // Instance mock creation methods + + /* ------------------------------------------------------------------------ */ + /* createMock method + /* ------------------------------------------------------------------------ */ + /** + * Creates a new Mock using this skeleton backing it. + * + * @param interfaceClasses an array of interface the mock should implement. + * @return the mock + */ + public Object createMock(Class ... interfaceClasses) + { + ClassLoader cl; + + if (interfaceClasses.length > 0) cl = interfaceClasses[0].getClassLoader(); + else cl = Thread.currentThread().getContextClassLoader(); + + return Proxy.newProxyInstance(cl, interfaceClasses, this); + } + + /* ------------------------------------------------------------------------ */ + /* createMock method + /* ------------------------------------------------------------------------ */ + /** + * Creates a new Mock using this skeleton backing it. + * + * @param The object type + * @param interfaceClass an array of interface the mock should implement. + * @return the mock + */ + public T createMock(Class interfaceClass) + { + return interfaceClass.cast(Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] {interfaceClass}, this)); + } + + /* ------------------------------------------------------------------------ */ + /* invokeReturnTypeHandlers method + /* ------------------------------------------------------------------------ */ + /** + * This method invokes the return type proxy for the specified class. If a + * ReturnTypeHandler for that type has not been registered then if the class + * represents an interface a new mock will be returned, backed by this + * skeleton, otherwise null will be returned. + * + * @param type the type to be invoked. + * @return the returned object. + * @throws Exception if an error occurs when invoking the return type handler. + */ + public Object invokeReturnTypeHandlers(Class type) throws Exception + { + if (_typeHandlers.containsKey(type)) + { + ReturnTypeHandler rth = _typeHandlers.get(type); + return rth.handle(type, this); + } + else if (type.isInterface()) + { + return createMock(type); + } + else + { + return null; + } + } + + // Miscellaneous methods that have been deprecated and will be removed. + + /* ------------------------------------------------------------------------ */ + /* createReturnTypeProxy method + /* ------------------------------------------------------------------------ */ + /** + * create a proxy for given return type. + * + * @deprecated use invokeReturnTypeHandlers instead + * + * @param type The return type for which a handler is required + * @return ReturnTypeHandler The return type handler + * @throws Exception Thrown if an exception occurs. + */ + /* ------------------------------------------------------------------------ */ + @Deprecated + private final Object createReturnTypeProxy(Class type) throws Exception + { + return invokeReturnTypeHandlers(type); + } + + /* ------------------------------------------------------------------------ */ + /* isReadWriteProperty method + /* ------------------------------------------------------------------------ */ + /** + * This method returns true if the method passed a getter for a read write + * java bean property. This is worked out by checking that a setter and getter + * exist for the property and that the setter and getter take and return + * exactly the same time. + * + * @param objClass The object the read write method has been invoked on. + * @param method The method to be checked. + * @return true if it is a getter or setter for a read write property. + */ + private boolean isReadWriteProperty(Class objClass, Method method) + { + String methodName = method.getName(); + String propertyName = methodName.substring(3); + Class[] parameters = method.getParameterTypes(); + Class clazz; + boolean result = false; + + if (methodName.startsWith("get") && parameters.length == 0) + { + clazz = method.getReturnType(); + try + { + objClass.getMethod("set" + propertyName, clazz); + result = true; + } + catch (NoSuchMethodException e) + { + if (isPrimitive(clazz)) + { + clazz = getOtherForm(clazz); + try + { + objClass.getMethod("set" + propertyName, clazz); + result = true; + } + catch (NoSuchMethodException e1) + { + + } + } + } + } + else if (methodName.startsWith("is") && parameters.length == 0) + { + clazz = method.getReturnType(); + if (clazz.equals(Boolean.class) || clazz.equals(boolean.class)) + { + propertyName = methodName.substring(2); + try + { + objClass.getMethod("set" + propertyName, clazz); + result = true; + } + catch (NoSuchMethodException e) + { + + if (isPrimitive(clazz)) + { + clazz = getOtherForm(clazz); + try + { + objClass.getMethod("set" + propertyName, clazz); + result = true; + } + catch (NoSuchMethodException e1) + { + + } + } + } + } + } + else if (methodName.startsWith("set") && parameters.length == 1) + { + clazz = parameters[0]; + + try + { + Method getter = objClass.getMethod("get" + propertyName, new Class[0]); + result = checkClasses(getter.getReturnType(), clazz); + } + catch (NoSuchMethodException e) + { + if (isPrimitive(clazz)) + { + clazz = getOtherForm(clazz); + try + { + Method getter = objClass.getMethod("get" + propertyName, new Class[0]); + result = checkClasses(getter.getReturnType(), clazz); + } + catch (NoSuchMethodException e1) + { + if (clazz.equals(Boolean.class) || clazz.equals(boolean.class)) + { + try + { + Method getter = objClass.getMethod("is" + propertyName, new Class[0]); + result = checkClasses(getter.getReturnType(), clazz); + } + catch (NoSuchMethodException e2) + { + clazz = getOtherForm(clazz); + try + { + Method getter = objClass.getMethod("is" + propertyName, new Class[0]); + result = checkClasses(getter.getReturnType(), clazz); + } + catch (NoSuchMethodException e3) + { + } + } + } + } + } + } + } + + return result; + } + + + /* ------------------------------------------------------------------------ */ + /* isPrimitive method + /* ------------------------------------------------------------------------ */ + /** + * This method returns true if the class object represents a primitive or the + * object version of a primitive. + * + * @param clazz The class to be checked. + * @return true if it is a primitive or a wrapper. + */ + private boolean isPrimitive(Class clazz) + { + boolean result = false; + + if (clazz.isPrimitive()) + { + result = true; + } + else + { + result = clazz.equals(Boolean.class) || clazz.equals(Byte.class) || + clazz.equals(Short.class) || clazz.equals(Character.class) || + clazz.equals(Integer.class) || clazz.equals(Long.class) || + clazz.equals(Float.class) || clazz.equals(Double.class); + } + + return result; + } + + /* ------------------------------------------------------------------------ */ + /* getOtherForm method + /* ------------------------------------------------------------------------ */ + /** + * This method takes a class representing either a primitive or an object + * wrapper. If the class is a primitive type the object wrapper class is + * returned. If the class is an object wrapper class the primitive type is + * returned. + * + * @param clazz + * @return the class representing the primitive object wrapper. + */ + private Class getOtherForm(Class clazz) + { + Class result = null; + + if (clazz.equals(boolean.class)) result = Boolean.class; + else if (clazz.equals(byte.class)) result = Byte.class; + else if (clazz.equals(short.class)) result = Short.class; + else if (clazz.equals(char.class)) result = Character.class; + else if (clazz.equals(int.class)) result = Integer.class; + else if (clazz.equals(long.class)) result = Long.class; + else if (clazz.equals(float.class)) result = Float.class; + else if (clazz.equals(double.class)) result = Double.class; + else if (clazz.equals(Boolean.class)) result = boolean.class; + else if (clazz.equals(Byte.class)) result = byte.class; + else if (clazz.equals(Short.class)) result = short.class; + else if (clazz.equals(Character.class)) result = char.class; + else if (clazz.equals(Integer.class)) result = int.class; + else if (clazz.equals(Long.class)) result = long.class; + else if (clazz.equals(Float.class)) result = float.class; + else if (clazz.equals(Double.class)) result = double.class; + + return result; + } + + /* ------------------------------------------------------------------------ */ + /* checkClasses method + /* ------------------------------------------------------------------------ */ + /** + * This method returns true if the two classes are the same or if one is + * primitive that the other is a primitive wrapper. + * + * @param type1 + * @param type2 + * @return true if the classes are compatible. + */ + private boolean checkClasses(Class type1, Class type2) + { + boolean result = false; + + if ((type1.isPrimitive() && type2.isPrimitive()) || + (!type1.isPrimitive() && !type2.isPrimitive())) + { + result = type1.equals(type2); + } + else + { + result = (type1.equals(boolean.class) && type2.equals(Boolean.class)) || + (type1.equals(byte.class) && type2.equals(Byte.class)) || + (type1.equals(short.class) && type2.equals(Short.class)) || + (type1.equals(char.class) && type2.equals(Character.class)) || + (type1.equals(int.class) && type2.equals(Integer.class)) || + (type1.equals(long.class) && type2.equals(Long.class)) || + (type1.equals(float.class) && type2.equals(Float.class)) || + (type1.equals(double.class) && type2.equals(Double.class)) || + (type2.equals(boolean.class) && type1.equals(Boolean.class)) || + (type2.equals(byte.class) && type1.equals(Byte.class)) || + (type2.equals(short.class) && type1.equals(Short.class)) || + (type2.equals(char.class) && type1.equals(Character.class)) || + (type2.equals(int.class) && type1.equals(Integer.class)) || + (type2.equals(long.class) && type1.equals(Long.class)) || + (type2.equals(float.class) && type1.equals(Float.class)) || + (type2.equals(double.class) && type1.equals(Double.class)); + } + + return result; + } + + /* ------------------------------------------------------------------------ */ + /* getMethodCallsAsString method + /* ------------------------------------------------------------------------ */ + /** + * This method builds a String that contains the method calls that have been + * made on this skeleton. It puts each call on a separate line. + * + * @return the string representation of the method call. + */ + private String getMethodCallsAsString() + { + StringBuilder builder = new StringBuilder(); + + for (MethodCall call : _methodCalls) + { + builder.append(call); + builder.append("\r\n"); + } + + return builder.toString(); + } + +} Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/InjectSkeleton.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/InjectSkeleton.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/InjectSkeleton.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/InjectSkeleton.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * If a field on a template is marked with this annotation then it will be + * injected with the Skeleton instance for the most recently created mock. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface InjectSkeleton +{ + +} \ No newline at end of file Added: aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/Singleton.java URL: http://svn.apache.org/viewvc/aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/Singleton.java?rev=1075107&view=auto ============================================================================== --- aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/Singleton.java (added) +++ aries/tags/testsupport-0.1-incubating/testsupport-unit/src/main/java/org/apache/aries/unittest/mocks/annotations/Singleton.java Sun Feb 27 18:31:58 2011 @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.aries.unittest.mocks.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation can be applied to template objects. If a template object's + * class has this annotation and is passed multiple times to the Skeleton.newMock + * method with the same interface class the same mock will be returned, unless + * the garbage collector has cleared the previous mock. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Singleton +{ + +} \ No newline at end of file