Platinum Partner

Auto-test For Equals Function In Java Classes Through Annotations

See comments and http://stackoverflow.com/questions/190007


/**
 * 
 */
package test;


import static test.MyClass.GenericConstants.COLON;
import static test.MyClass.GenericConstants.DOT;
import static test.MyClass.GenericConstants.EXCLAMATION_MARK;
import static test.MyClass.GenericConstants.SIMPLE_QUOTE;
import static test.MyClass.GenericConstants.SPACE;
import static test.MyClass.EqualsInstancesDirective.InstanceStatus.*;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import junit.framework.Assert;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.internal.runners.TextListener;
import org.junit.runner.JUnitCore;

import test.MyClass.ArrayStringParser.ArrayDimension;
/**
 * Class with overridden hash() and equals() method to be automatically tested. 
* The appropriate parameters for instances to be tested for equality * are stored in annotations just above the equals() method itself.
* JDK6, JUnit4.4, this class is also a test case, and its main function will launch JUnit on itself. * @author VonC * @see * Automagic unit tests for upholding Object method contracts in Java? */ public class MyClass { /* * ######################################################################## * Private attributes for this object. * They will play a role in the equal() and hash() methods * They also involve a non-empty constructor * ######################################################################## */ private String string = null; private int integer = 0; private boolean check = false; /* * ######################################################################## * Constructors to be used in equals() tests * They may be a default constructor, but it is not always the case * ######################################################################## */ /** * Default constructor, which must always, since it is declared, be used for equals() test.
* By default, the string is null, integer 0 and check false */ public MyClass() {/**/} /** * Constructor with parameter, to be used if equals() comes with the right annotations.
* That is annotations representing the right Api * @param aString * @param anInteger * @param aCheck */ public MyClass(final String aString, final int anInteger, final boolean aCheck) { this.string = aString; this.integer = anInteger; this.check = aCheck; } /* * ######################################################################## * Definition of the runtime annotation used to specify valid equality tests * The directive is about how to build the right valid instances * ######################################################################## */ /** * @author VonC */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Directives { /** array of directives. */ EqualsInstancesDirective[] value(); } /** * @author VonC */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface EqualsInstancesDirective{ /** Parameters for the constructors.*/ String parameters(); /** Checks if those parameters builds a default value or not (by default, not). */ InstanceStatus status() default NOT_DEFAULT; /** The instance can either be equal to default value or not. */ public static enum InstanceStatus { DEFAULT, NOT_DEFAULT } } /* * ######################################################################## * Hash() and equals() overridden methods * They should always be defined: if only one is overridden, * any equals() test must fail immediately * ######################################################################## */ /** * Basic equals() function: if all private instances are equal, the objects are equal.
* The trick is to know how to build the right instances, hence the EqualsInstancesDirective annotation * @see java.lang.Object#equals(java.lang.Object) */ @Override @Directives({ @EqualsInstancesDirective(parameters = "\"test1\", -1, true"), @EqualsInstancesDirective(parameters = "\"test2\", 2, false"), @EqualsInstancesDirective(parameters = "null, 0, false", status=DEFAULT) }) public final boolean equals(final Object obj) { boolean res = false; if(obj != null && obj instanceof MyClass) { final MyClass aMyClass = (MyClass)obj; if(this.integer == aMyClass.integer && this.check == aMyClass.check) { if(this.string == null && aMyClass.string == null) { res = true; } else if(this.string != null && aMyClass.string != null && this.string.equals(aMyClass.string)) { res = true; } } } return res; } /** * Computes hash for this objects, based on Based on a FNV hash. * @see java.lang.Object#hashCode() */ @Override public final int hashCode() { String aString = Integer.toString(this.integer) + "~" + Boolean.toString(this.check); if(this.string != null) { aString = aString + "~" + this.string; } final int anHashCode = (int)FnvHash.fnvHashCode(aString); return anHashCode; } /** * @see java.lang.Object#toString() */ @Override public final String toString() { return this.string + ": " + this.integer + "(" + this.check + ")"; } /* * ######################################################################## * The following classes should be separate, in a JUNIT package section. * They are here to keep this test self-contained * ######################################################################## */ /** * @throws java.lang.Exception */ @Before public final void setUp() throws Exception { System.out.println("setup"); } private void testClassesWithEquals(final List> someClasses) { for (final Class aClass : someClasses) { final Constructor[] someConstructors = aClass.getDeclaredConstructors(); if(someConstructors.length > 1) { final List someValues = new ArrayList(); final List someDefaultValues = new ArrayList(); final InstanceBuilder aDefaultInstanceBuilder = new InstanceBuilder(aClass, null); someDefaultValues.add(aDefaultInstanceBuilder.build()); Method anEqualMethod; try { anEqualMethod = aClass.getDeclaredMethod("equals", Object.class); fillValues(aClass, someValues, someDefaultValues, anEqualMethod); testObjectsWithEquals(someDefaultValues, someValues); } catch (final SecurityException e) { e.printStackTrace(); } catch (final NoSuchMethodException e) { e.printStackTrace(); } } } } private void fillValues(final Class aClass, final List someValues, final List someDefaultValues, final Method anEqualMethod) { final Annotation[] someAnotations = anEqualMethod.getAnnotations(); if(someAnotations != null && someAnotations.length > 0) { for (final Annotation anAnnotation : someAnotations) { if(anAnnotation.annotationType().getName().equals(Directives.class.getName())) { final EqualsInstancesDirective[] someDirectives = ((Directives)anAnnotation).value(); for (final EqualsInstancesDirective anEqualsInstancesDirective : someDirectives) { final InstanceBuilder anInstanceBuilder = new InstanceBuilder(aClass, anEqualsInstancesDirective); if(anEqualsInstancesDirective.status().equals(EqualsInstancesDirective.InstanceStatus.DEFAULT)) { someDefaultValues.add(anInstanceBuilder.build()); } else { someValues.add(anInstanceBuilder.build()); } } } } } } private void testObjectsWithEquals(final List someDefaultValues, final List someValues) { for (final Object aDefaultValue : someDefaultValues) { testObject(aDefaultValue, someDefaultValues, true); testObject(aDefaultValue, someValues, false); } for (final Object aValue : someValues) { testObject(aValue, someValues, false); } } private void testObject(final Object aValue, final List someValues, final boolean isEqual) { for (final Object anotherValue : someValues) { if(isEqual || aValue != anotherValue) { if(aValue.equals(anotherValue) != isEqual) { Assert.failNotSame("Equality test fails between '"+aValue.toString() + "' and '"+anotherValue.toString()+"'", Boolean.valueOf(isEqual), Boolean.valueOf(!isEqual)); } } } } /** * @throws MyException * */ @Test public final void testEquals() throws MyException { System.out.println("testEquals"); final ClassWithAnnotedEqualsDetector aDetector = new ClassWithAnnotedEqualsDetector("test"); final List> someClasses = aDetector.getClasses(); for (final Class aClass : someClasses) { System.out.println("Will test equals on: " + aClass.getName()); } testClassesWithEquals(someClasses); } /** * @throws java.lang.Exception */ @After public final void tearDown() throws Exception { System.out.println("tearDown"); } /** * Launches the JUnit test on itself.
* Self-contained test * @param args ignored */ public static void main(final String... args) { final JUnitCore aRunner = new JUnitCore(); final TextListener aTextListener = new TextListener(); aRunner.addListener(aTextListener); aRunner.run(MyClass.class); } /* * ######################################################################## * The following classes should be in their on java files * with their own packages. * They are here to keep this test self-contained * ######################################################################## */ /** * Computes a FNV hash.
* Used for this short key example test class. * @see * What is a performant string hashing function that results in a 32 bit integer with low collision rates? * @author VonC */ public static final class FnvHash { private FnvHash() {/* */} private static final int K_FNV_OFFSET = (int) 2166136261L; private static final int K_FNV_PRIME = 16777619; /** * Computes a Fowler/Noll/Vo (FNV) hash.
* Useful for short key * From http://forums.devx.com/showthread.php?t=141108 * @param str identifier of the object (MUST NOT BE NULL) * @return hash value, always defined */ public static long fnvHashCode(final String str) { final int anHashShift = 0x0000ffff; final long anHashLongShift = 0x00000000ffffffffL; int hash = K_FNV_OFFSET; for (int i = 0; i < str.length(); i++) { hash ^= (anHashShift & str.charAt(i)); hash *= K_FNV_PRIME; } return anHashLongShift & hash; } } /* * ######################################################################## * Parse all classpath locations to seek eligible classes to test. * Meaning: classes with a criteria making them eligible * Hence the Eligible interface * ######################################################################## */ /** * @author VonC */ public static class ClassWithAnnotedEqualsDetector { private String packageName = null; /** * @param aPackageName * @throws MyException */ public ClassWithAnnotedEqualsDetector(final String aPackageName) throws MyException { this.packageName = aPackageName; if(StringUtils.isEmpty(this.packageName)) { throw new MyException("a package must be provided"); } } /** * @return list of classes with annotated equals, empty if none, never null * @throws MyException */ public final List> getClasses() throws MyException { final List> someClasses = ReflectionUtils.getClasses(this.packageName); final List> someClassesWithEquals = new ArrayList>(); for (final Class aClass : someClasses) { try { final Method anEqualMethod = aClass.getDeclaredMethod("equals", Object.class); // must check if hashCode is also defined final Method anHashMethod = aClass.getDeclaredMethod("hashCode"); // but there is no usage for that variable JavaUtils.mockAction(anEqualMethod); JavaUtils.mockAction(anHashMethod); someClassesWithEquals.add(aClass); } catch (final SecurityException e) { // nothing to do JavaUtils.mockAction(); } catch (final NoSuchMethodException e) { // nothing to do JavaUtils.mockAction(); } } return someClassesWithEquals; } } /** * Utilities based on reflection java method.
* Used to examine dynamic java instances * @author VonC */ public static final class ReflectionUtils { private ReflectionUtils() {/* */} private static final String POURCENT_20 = "%20"; private static final String DOT_CLASS = ".class"; private static final int DOT_CLASS_LENGTH = 6; //private static final String DOT_JAVA = ".java"; //private static final int DOT_JAVA_LENGTH = 5; private static final String DOT_JAR = ".jar"; private static final String UNABLE_TO_FIND_CLASS_NAMED_QUOTE = "Unable to find class named '"; /** * Scans all classes accessible from the context class loader which belong to the given package and sub-packages.
* Look within directories resources and within jar resources * @param packageName The base package * @return The classes found, empty list if none found * @throws MyException if problem during class detection * @throws Error if unable to access resources of class loader, to look within a jar resource or to find a class */ public static List> getClasses(final String packageName) throws MyException { final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); //assert classLoader != null; final String path = packageName.replace('.', '/'); final List> classes = new ArrayList>(); Enumeration anEnumerator = null; try { anEnumerator = classLoader.getResources(path); } catch(final IOException ioe) { throw new MyException("Unable to access resources of current thread class loader for path '" + path + SIMPLE_QUOTE, ioe); } if (anEnumerator != null) { while (anEnumerator.hasMoreElements()) { String filePath = anEnumerator.nextElement().getFile(); // WINDOWS HACK if(StringUtils.strictContains(filePath, POURCENT_20)) { filePath = filePath.replaceAll(POURCENT_20, SPACE); } if (filePath != null) { if (StringUtils.strictContains(filePath,EXCLAMATION_MARK) && StringUtils.strictContains(filePath,DOT_JAR)) { String jarPath = filePath.substring(0, filePath.indexOf(EXCLAMATION_MARK)).substring(filePath.indexOf(COLON) + 1); // WINDOWS HACK if (jarPath.indexOf(COLON) >= 0) { jarPath = jarPath.substring(1); } classes.addAll(getClassesFromJARFile(jarPath, path)); } else { classes.addAll(getClassesFromDirectory(new File(filePath), packageName)); } } } } return classes; } private static List> getClassesFromJARFile(final String jar, final String packageName) throws MyException { final List> classes = new ArrayList>(); JarInputStream jarFile = null; final StringBuffer anExceptionMessage = new StringBuffer(); try { jarFile = new JarInputStream(new FileInputStream(jar)); JarEntry jarEntry; do { try { jarEntry = jarFile.getNextJarEntry(); } catch(final IOException ioe) { throw new MyException( anExceptionMessage.append("Unable to get next jar entry from jar file '"). append(jar).append(SIMPLE_QUOTE).toString(), ioe); } finally { //closeJarFile(jarFile); JavaUtils.mockAction(); } if (jarEntry != null) { extractClassFromJar(jar, packageName, classes, jarEntry); } } while (jarEntry != null); closeJarFile(jarFile); } catch(final IOException ioe) { throw new MyException( anExceptionMessage.append("Unable to get Jar input stream from '").append(jar).append(SIMPLE_QUOTE).toString(), ioe); } finally { closeJarFile(jarFile); } return classes; } /** * @param jar * @param packageName * @param classes * @param jarEntry * @throws Error */ private static void extractClassFromJar(final String jar, final String packageName, final List> classes, final JarEntry jarEntry) throws MyException { String className = jarEntry.getName(); if (className.endsWith(DOT_CLASS)) { className = className.substring(0, className.length() - DOT_CLASS_LENGTH); if (className.startsWith(packageName)) { try { classes.add(Class.forName(className.replace('/', '.'))); } catch (final ClassNotFoundException cnfe) { throw new MyException(UNABLE_TO_FIND_CLASS_NAMED_QUOTE + className.replace('/', '.') + "' within jar '" + jar + SIMPLE_QUOTE, cnfe); } } } } /** * @param jarFile */ private static void closeJarFile(final JarInputStream jarFile) { if(jarFile != null) { try { jarFile.close(); } catch(final IOException ioe) { JavaUtils.mockAction(); } } } /** * Recursive method used to find all classes in a given directory and subdirs. * * @param directory The base directory * @param packageName The package name for classes found inside the base directory * @return The classes (empty list if none found) * @throws ClassNotFoundException */ private static List> getClassesFromDirectory(final File directory, final String packageName) throws MyException { final List> classes = new ArrayList>(); if (directory.exists()) { final File[] files = directory.listFiles(); for (int iFiles = 0; iFiles < files.length; iFiles++) { final File file = files[iFiles]; if (file.isDirectory() && file.getName().indexOf(DOT) == -1) { //assert ; classes.addAll(getClassesFromDirectory(file, new StringBuffer().append(packageName).append(DOT).append(file.getName()).toString())); } else if (file.getName().endsWith(DOT_CLASS)) { final String aClassName = new StringBuffer().append(packageName).append('.'). append(file.getName().substring(0, file.getName().length() - DOT_CLASS_LENGTH)).toString(); try { classes.add(Class.forName(aClassName)); } catch (final ClassNotFoundException cnfe) { throw new MyException( new StringBuffer().append(UNABLE_TO_FIND_CLASS_NAMED_QUOTE).append(aClassName). append("' within directory '").append(directory.getAbsolutePath()). append(SIMPLE_QUOTE).toString(), cnfe); } } } } return classes; } /** * Check if aClass is an subclass of anotherClass.
* USefull when the Type is not statically known * @param aClass class to check (regarding its superclass) * @param anotherClass reference class * @return false if one of the parameters is null, if no relation is found. True otherwise (subclass or equality) */ public static boolean isAsSubclassOf(final Class aClass, final Class anotherClass) { boolean res = false; if(aClass != null && anotherClass != null) { if(aClass == anotherClass) { res = true; } else { try { aClass.asSubclass(anotherClass); res = true; } catch(final ClassCastException e) { JavaUtils.mockAction(e); res = false; } } } return res; } /** * Get the number of dimensions represented by the java array.
* int[][][] will gives 3 * @param anArrayClass Class representing an array of a class, can be null * @return 0 if not an array, class otherwise */ public static int getArrayClassDimensionsNumber(final Class anArrayClass) { int anArrayClassDimensionsNumber = 0; if (anArrayClass != null && anArrayClass.isArray()) { try { Class anArrayActualClass = anArrayClass; while (anArrayActualClass.isArray()) { anArrayClassDimensionsNumber = anArrayClassDimensionsNumber + 1; anArrayActualClass = anArrayActualClass.getComponentType(); } } catch (final Throwable e) { /*FALLTHRU*/ JavaUtils.mockAction(); } } return anArrayClassDimensionsNumber; } /** * Get the Class represented by the java array.
* int[][][] will gives Integer * @param anArrayClass Class representing an array of a class, can be null * @return null if not an array, class otherwise */ public static Class getArrayClass(final Class anArrayClass) { Class anArrayActualClass = null; if (anArrayClass != null && anArrayClass.isArray()) { try { anArrayActualClass = anArrayClass; while (anArrayActualClass.isArray()) { anArrayActualClass = anArrayActualClass.getComponentType(); } } catch (final Throwable e) { /*FALLTHRU*/ JavaUtils.mockAction(); } } return anArrayActualClass; } } /** * Applicative Exception to encapsulate all internal explicit exceptions encountered.
* Used for applicative static exception container. * @author VonC */ public static final class MyException extends Exception { private static final long serialVersionUID = -8257625811507034547L; /** * Constructs a new exception with the specified detail message. * @param message the detail message * @see java.lang.Exception#Exception(java.lang.String) */ public MyException(final String message) { super(message); } /** * Constructs a new exception with the specified detail message and * cause. * @param message the detail message * @param cause the cause * @see java.lang.Exception#Exception(java.lang.String, java.lang.Throwable) */ public MyException(final String message, final Throwable cause) { super(message, cause); } /** * Constructs a new exception with the specified cause and a detail * message. * @param cause the cause * @see java.lang.Exception#Exception(java.lang.Throwable) * */ public MyException(final Throwable cause) { super(cause); } } /** * Generic constants that can be used anywhere.
* Mostly String constants. * @author VonC */ public static final class GenericConstants { private GenericConstants() {/* */} /** * space ' ' (no quotes). */ public static final String SPACE= " "; /** * Exclamation mark !. */ public static final String EXCLAMATION_MARK = "!"; /** * colon ':'. */ public static final String COLON = ":"; /** * Simple quote '. */ public static final String SIMPLE_QUOTE = "'"; /** * dot '.'. */ public static final String DOT = "."; /** * Closing round bracket ). */ public static final String CLOSING_ROUND_BRACKET= ")"; } /** * Generic java utilities functions.
* Encapsulate basic operations. * @author VonC */ public static final class JavaUtils { private JavaUtils() {/* */} /** * Mock Action, do nothing.
* Used for FindBugs workaround:
* DLS: Dead store of class literal * This instruction assigns a class literal to a variable and then never uses it. * @param anObject an object (ignored if null) */ static void mockAction(final Object anObject) { if(returnsFalse() && anObject != null) { JavaUtils.mockAction(); //System. out.println("do nothing on " + o.toString()); } } /** * Mock Action, do nothing.
* Used for CheckStyle workaround:
* "This block should not be empty". */ static void mockAction() { if(returnsFalse()) { JavaUtils.mockAction(); //System. out.println("do nothing"); } } /** * Returns boolean false.
* Used for FindBugs workaround like a method not called:
* CN: Class implements Cloneable but does not define or use clone method * @return false */ private static boolean returnsFalse() { return false; } } /** * Generic String utilities functions.
* Encapsulate basic operations. * @author VonC */ public static final class StringUtils { private StringUtils() {/* */} /** * Check if a string is empty.
* Meaning null or length == 0 (empty string) * @param s string to be tested * @return true if empty, false otherwise */ public static boolean isEmpty(final String s) { return !isNotEmpty(s); } /** * Check if a string is not empty.
* Meaning not null and length > 0 * @param s string to be tested * @return true if not empty, false otherwise */ public static boolean isNotEmpty(final String s) { final boolean notempty = (s != null) && s.length() > 0; return notempty; } /** * Check if a string contains another string at a position greater than zero
* Means that a string "strictly" contains another if and only if the substring is found * but not at the beginning of the string
* "abcd", "ab" => false.
* "abcd", "bc" => true.
* Avoid "findBugs warning" Method checks to see if result of String.indexOf is positive
* "The method invokes String.indexOf and checks to see if the result is positive or non-positive. * It is much more typical to check to see if the result is negative or non-negative. * It is positive only if the substring checked for occurs * at some place other than at the beginning of the String." * @param aString string containing or not the substring * @param amsg substring looked for in 'aString' * @return true if substring found, false otherwise (null parameters, ...) */ static boolean strictContains(final String aString, final String amsg) { return strictContains(aString, amsg, 0); } /** * Check if a string contains another string at a position greater than a positive index
* Means that a string "strictly" contains another if and only if the substring is found * but not at the beginning of the string
* "abcd", "ab" => false.
* "abcd", "c" => true if positive index equals '1'.
* Avoid "findBugs warning" Method checks to see if result of String.indexOf is positive
* "The method invokes String.indexOf and checks to see if the result is positive or non-positive. * It is much more typical to check to see if the result is negative or non-negative. * It is positive only if the substring checked for occurs * at some place other than at the beginning of the String." * @param aString string containing or not the substring * @param aMsg substring looked for in 'aString' * @param anIndex positive index * @return true if substring found, false otherwise (null parameters, ...) */ private static boolean strictContains(final String aString, final String aMsg, final int anIndex) { boolean res = false; if(aString != null && aMsg != null && aMsg.length() > 0) { final int i = aString.indexOf(aMsg); final boolean notcontains = i < 0; if(!notcontains && i > anIndex) { res = true; } } return res; } } /* * ######################################################################## * The following methods are used to find the right constructor for a given set of parameters. * They could be grouped in a separated static class * ######################################################################## */ /** * @author VonC */ public static final class InstanceBuilder { private Class myClass = null; private EqualsInstancesDirective directive = null; /** * @param aClass * @param anEqualsInstancesDirective */ public InstanceBuilder(final Class aClass, final EqualsInstancesDirective anEqualsInstancesDirective) { this.myClass = aClass; this.directive = anEqualsInstancesDirective; } Object build() { Object aValue = null; String[] someArgs = new String[] {}; if(this.directive != null) { someArgs = this.directive.parameters().split(",\\s*"); } final Object[] someParameters = new Object[someArgs.length]; final Constructor aConstructor = findConstructor(this.myClass, someArgs, someParameters); if(aConstructor == null) { Assert.fail("Unable to find constructor for class '" + this.myClass.getName() + "' directive: " + this.directive); } else { Exception anException = null; try { aValue = aConstructor.newInstance(someParameters); } catch (final IllegalArgumentException e) { anException = e; } catch (final InstantiationException e) { anException = e; } catch (final IllegalAccessException e) { anException = e; } catch (final InvocationTargetException e) { anException = e; } if(anException != null) { Assert.fail("Unable to build instance from constructor for class '" + this.myClass.getName() + "' because of '" + anException.getMessage()); } } return aValue; } private static Constructor findConstructor(final Class aClass, final String[] someArgs, final Object[] someParameters) { Constructor aConstructor = null; final Constructor[] someDeclaredConstructors = aClass.getDeclaredConstructors(); for (final Constructor aDeclaredConstructor : someDeclaredConstructors) { final Class[] someConstructorParameters = aDeclaredConstructor.getParameterTypes(); if(someConstructorParameters.length == someArgs.length) { final boolean paramsOk = fillParameters(someArgs, someParameters, someConstructorParameters); if(paramsOk) { aConstructor = aDeclaredConstructor; break; } } } return aConstructor; } private static boolean fillParameters(final String[] someArgs, final Object[] someParameters, final Class[] someConstructorParameters) { boolean paramsOk = true; int iConstructorParam = 0; for (final Class aConstructorParamClass : someConstructorParameters) { if(ReflectionUtils.isAsSubclassOf(aConstructorParamClass, String.class)== false) { if(aConstructorParamClass.isPrimitive()) { if(fillParameter(aConstructorParamClass, iConstructorParam, someArgs[iConstructorParam], someParameters) == false) { paramsOk = false; break; } } else if(aConstructorParamClass.isArray()) { if(fillArrayParameter(aConstructorParamClass, iConstructorParam, someArgs[iConstructorParam], someParameters) == false) { paramsOk = false; break; } } else { paramsOk = false; break; } } else { someParameters[iConstructorParam] = someArgs[iConstructorParam]; if(someArgs[iConstructorParam].equals("null")) { someParameters[iConstructorParam] = null; } } iConstructorParam++; } return paramsOk; } private static boolean fillParameter(final Class aConstructorParamClass, final int anIndexConstructorParams, final String aParameter, final Object[] someParameters) { Object aParameterValue = null; try { aParameterValue = getParameterObjFromPrimitiveArg(aConstructorParamClass, aParameter); } catch(final NumberFormatException nfe) { //isParameterSucessfullyFilled = false; JavaUtils.mockAction(); } if(aParameterValue!= null) { someParameters[anIndexConstructorParams] = aParameterValue; } return aParameterValue != null; } private static Object getParameterObjFromPrimitiveArg(final Class methodParamClass, final String aParameter) { Object aParameterValue = null; if(isInteger(methodParamClass)) { aParameterValue = Integer.valueOf(aParameter); } else if(isBoolean(methodParamClass)) { aParameterValue = Boolean.valueOf(aParameter); } else if(isCharacter(methodParamClass)) { if(aParameter.length() == 1) { aParameterValue = Character.valueOf(aParameter.charAt(0)); } } else if(isLong(methodParamClass)) { aParameterValue = Long.valueOf(aParameter); } else if(isDouble(methodParamClass)) { aParameterValue = Double.valueOf(aParameter); } else if(isFloat(methodParamClass)) { aParameterValue = Float.valueOf(aParameter); } else if(isByte(methodParamClass)) { aParameterValue = Byte.valueOf(aParameter); } else if(isShort(methodParamClass)) { aParameterValue = Short.valueOf(aParameter); } return aParameterValue; } private static boolean isInteger(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Integer.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Integer.TYPE); } private static boolean isBoolean(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Boolean.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Boolean.TYPE); } private static boolean isCharacter(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Character.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Character.TYPE); } private static boolean isLong(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Long.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Long.TYPE); } private static boolean isDouble(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Double.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Double.TYPE); } private static boolean isFloat(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Float.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Float.TYPE); } private static boolean isByte(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Byte.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Byte.TYPE); } private static boolean isShort(final Class methodParamClass) { return ReflectionUtils.isAsSubclassOf(methodParamClass, Short.class) || ReflectionUtils.isAsSubclassOf(methodParamClass, Short.TYPE); } private static boolean fillArrayParameter(final Class methodParamClass, final int indexMethodParams, final String aParameter, final Object[] someParameters) { boolean isParameterSucessfullyFilled = true; final int anArrayDimensionNumber = ReflectionUtils.getArrayClassDimensionsNumber(methodParamClass); final ArrayStringParser anArrayStringParser = new ArrayStringParser(aParameter); int anArrayStringDepth = -1; try { anArrayStringDepth = anArrayStringParser.getNbDimensions(); if(anArrayStringDepth != anArrayDimensionNumber) { isParameterSucessfullyFilled = false; } final Class anArrayClass = ReflectionUtils.getArrayClass(methodParamClass); final Object anArray = getParameterArrayObjFromPrimArg(anArrayClass, methodParamClass, anArrayStringParser.getMainArrayDimension()); if(anArray != null) { someParameters[indexMethodParams] = anArray; } else { isParameterSucessfullyFilled = false; } } catch(final MyException cce) { isParameterSucessfullyFilled = false; //LOG.warning("Method " + aMethodName + " is rejected because parameter number " //+ indexMethodParams + " can not be parsed: " + cce.getMessage()); } return isParameterSucessfullyFilled; } private static Object getParameterArrayObjFromPrimArg(final Class anArrayClass, final Class anArrayClassArray, final ArrayDimension anArrayDimension) throws MyException { Object anArray = null; final boolean hasValues = anArrayDimension.hasValues(); final int aNumberOfElements = anArrayDimension.getNbElements(); final Class aSubArrayClass = anArrayClassArray.getComponentType(); final int[] someDimensions = new int[] { aNumberOfElements } ; anArray = Array.newInstance(aSubArrayClass, someDimensions); for(int jNbElems = 0; jNbElems < aNumberOfElements; jNbElems++) { Object aParameter = null; if(hasValues) { final String aValue = anArrayDimension.getValue(jNbElems); aParameter = getParameterObjFromPrimitiveArg(anArrayClass, aValue); } else { aParameter = getParameterArrayObjFromPrimArg(anArrayClass, aSubArrayClass, anArrayDimension.getChild(jNbElems)); } Array.set(anArray, jNbElems, aParameter); } return anArray; } } /** * Able to parse a string representation of an array.
* Check if well-formed and returns java array of string values (one array by dimension detected).
* Can be a string representation with various dimension and value delimiters, and separators (for value and delimiters), like in: *
[ { "first value first dimension", 'second value first dimension" ] , 
	 *  { 'first value second dimension'; 'second value second dimension" } }
* (here, the dimension delimiters are '{' and '[', and they do not necessary matches, * the value delimiters are " and ', and they do not necessary matches, * the value separators are ',' or ';'.
* @author VonC */ public static class ArrayStringParser { private final char[] dimensionOpeningDelimiters = new char[] {'[' }; private final char[] dimensionEndingDelimiters = new char[] {']' }; private final char[] valueDelimiters = new char[] {'\"' }; private final char[] dimensionSeparator = new char[] {',' }; private final char[] valueSeparator = new char[] {',' }; private String array = null; private ArrayDimension mainDimension = null; private ArrayDimension currentDimension = null; private StringBuffer currentValue = null; private int nbDimensions = 0; /** * Build a parser with default dimension and value delimiters, and default dimension and value separators.
* Means '[', '"' and ',' are used. * @param anArray String representation of an array, can be null or empty */ public ArrayStringParser(final String anArray) { this.array = anArray; } private static final int DIMENSION_EXPECTED = 0; private static final int DIMENSION_OR_VALUE_EXPECTED = 1; private static final int END_VALUE_EXPECTED = 2; private static final int END_DIMENSION_OR_VALUE_SEPARATOR_EXPECTED = 3; private static final int END_DIMENSION_OR_DIMENSION_SEPARATOR_EXPECTED = 4; private static final int VALUE_EXPECTED = 5; private int mode = DIMENSION_EXPECTED; private int parserIndex = 0; private Boolean hasBeenParsed = Boolean.FALSE; private static final Object GUARD = new Object(); /** * Parse the memorize String representation of an array.
* Will ignore any first or trailing spaces, tabs or newline characters (ignored also between values) * @throws MyException if problem during parsing */ public final void parse() throws MyException { synchronized (GUARD) { if(this.hasBeenParsed.booleanValue() == false) { this.currentDimension = null; this.mainDimension = null; this.mode = DIMENSION_EXPECTED; this.parserIndex = 0; if(StringUtils.isNotEmpty(this.array)) { while(this.parserIndex < this.array.length()) { if(this.mode == DIMENSION_EXPECTED) { parseDimensionExpected(); } if(this.mode == DIMENSION_OR_VALUE_EXPECTED) { parseDimensionOrValueExpected(); } if(this.mode == END_VALUE_EXPECTED) { parseEndValueExpected(); } if(this.mode == END_DIMENSION_OR_VALUE_SEPARATOR_EXPECTED) { parseEndDimOrValueSeparExpected(); } if(this.mode == END_DIMENSION_OR_DIMENSION_SEPARATOR_EXPECTED) { parseEndDimOrDimSeparExpected(); } if(this.mode == VALUE_EXPECTED) { parseValueExpected(); } } this.nbDimensions = checkArrayDimensionNumber(this.mainDimension); } this.hasBeenParsed = Boolean.TRUE; } } } /** * Get the number of dimensions of this array, based on number of dimensions parsed.
* If the String has not been parsed yet, this call will trigger the parse() method automatically (only once). * @return number of dimensions detected, 1 or more * @throws MyException if trouble during parsing */ public final int getNbDimensions() throws MyException { parse(); return this.nbDimensions; } /** * Get main Array Dimension as parsed.
* If the String has not been parsed yet, this call will trigger the parse() method automatically (only once). * @return main Array Dimension, never null * @throws MyException if trouble during parsing */ public final ArrayDimension getMainArrayDimension() throws MyException { parse(); return this.mainDimension; } private int checkArrayDimensionNumber(final ArrayDimension aDimension) throws MyException { int aNbDimFound = 0; if(aDimension != null) { int aChildDepth = 0; for (final ArrayDimension aChildArrayDimension : aDimension.getChildren()) { aChildDepth = checkArrayDimensionNumber(aChildArrayDimension); if(aChildDepth != aNbDimFound && aNbDimFound > 0) { throw new MyException(new StringBuffer().append("Incoherent depth found on child number "). append(aDimension.getChildren().indexOf(aChildArrayDimension)).toString()); } aNbDimFound = aChildDepth; } if(aDimension.getChildren().size() == 0) { aNbDimFound = aDimension.getDepth() + 1; } } return aNbDimFound; } /** * ' \t\n\r\x0B\f'. * @param c char to check */ private boolean isWhiteSpace(final char c) { if(c == ' ' || c == '\t' || c == '\n') { return true; } if(c == '\r' || c == '\f' || c == '\u000B') { return true; } return false; } private boolean isOpeningDimension(final char c) { return isPartOf(this.dimensionOpeningDelimiters, c); } private boolean isEndingDimension(final char c) { return isPartOf(this.dimensionEndingDelimiters, c); } private boolean isValueDelimiter(final char c) { return isPartOf(this.valueDelimiters, c); } private boolean isValueSeparator(final char c) { return isPartOf(this.valueSeparator, c); } private boolean isDimensionSeparator(final char c) { return isPartOf(this.dimensionSeparator, c); } private boolean isPartOf(final char[] someChars, final char aChar) { for (int ichars = 0; ichars < someChars.length; ichars++) { final char c = someChars[ichars]; if(c == aChar) { return true; } } return false; } private String displayCharArray(final char[] someChars, final boolean isShort) { final StringBuffer msg = new StringBuffer(""); if(someChars.length == 1) { if(isShort) { msg.append("the following character "); } msg.append('\'').append(String.valueOf(someChars[0])).append('\''); } else { if(isShort) { msg.append("one of the following characters: "); } msg.append('\''); for (int iChars = 0; iChars < someChars.length; iChars++) { final char c = someChars[iChars]; msg.append(String.valueOf(c)).append("'"); if(iChars < (someChars.length - 1)) { msg.append(", '"); } } } return msg.toString(); } private String getUnableToParseMessage() { final StringBuffer aMsg = new StringBuffer("Unable to parse '"); aMsg.append("': "); if(this.parserIndex < this.array.length()) { if(this.parserIndex> 0) { aMsg.append(this.array.substring(0, this.parserIndex -1)).append('\''); } aMsg.append(">>>").append(this.array.charAt(this.parserIndex)).append("<<<"); if(this.parserIndex < (this.array.length() - 1)) { aMsg.append('\'').append(this.array.substring(this.parserIndex + 1)).append('\'').append(""); } } else { aMsg.append(this.array).append('\'').append(">>><<<"); } return aMsg.toString(); } private void parseDimensionExpected() throws MyException { boolean isDimensionFound = false; boolean isCharProcessed = false; while(isDimensionFound == false) { final char c = this.array.charAt(this.parserIndex); isCharProcessed = false; if(isWhiteSpace(c)) { isCharProcessed = true; // char ignored } else if(isOpeningDimension(c)) { this.mode = DIMENSION_OR_VALUE_EXPECTED; isDimensionFound = true; this.currentDimension = new ArrayDimension(this.currentDimension); if(this.mainDimension == null) { this.mainDimension = this.currentDimension; } isCharProcessed = true; } if(isCharProcessed == false || (isDimensionFound == false && canParseNextChar() == false)) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("expects opening dimension char ("). append(displayCharArray(this.dimensionOpeningDelimiters, false)).append(")").toString()); } this.parserIndex = this.parserIndex + 1; } } private void parseDimensionOrValueExpected() throws MyException { boolean isDimensionOrValueFound = false; boolean isCharProcessed = false; while(isDimensionOrValueFound == false) { final char c = this.array.charAt(this.parserIndex); isCharProcessed = false; if(isWhiteSpace(c)) { isCharProcessed = true; // char ignored } else if(isOpeningDimension(c)) { this.currentDimension = new ArrayDimension(this.currentDimension); isDimensionOrValueFound = true; isCharProcessed = true; } else if(isValueDelimiter(c)) { this.currentValue = new StringBuffer(); this.mode = END_VALUE_EXPECTED; isDimensionOrValueFound = true; isCharProcessed = true; } if(isCharProcessed == false || (isDimensionOrValueFound == false && canParseNextChar() == false)) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("expects opening dimension ("). append(displayCharArray(this.dimensionOpeningDelimiters, true)).append(") "). append("or a value delimiter (").append(displayCharArray(this.valueDelimiters, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).toString()); } this.parserIndex = this.parserIndex + 1; } } private void parseValueExpected() throws MyException { boolean isValueFound = false; boolean isCharProcessed = false; while(isValueFound == false) { final char c = this.array.charAt(this.parserIndex); isCharProcessed = false; if(isWhiteSpace(c)) { isCharProcessed = true; // char ignored } else if(isValueDelimiter(c)) { this.currentValue = new StringBuffer(); this.mode = END_VALUE_EXPECTED; isValueFound = true; isCharProcessed = true; } if(isCharProcessed == false || (isValueFound == false && canParseNextChar() == false)) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("expects a value delimiter (").append(displayCharArray(this.valueDelimiters, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).toString()); } this.parserIndex = this.parserIndex + 1; } } private boolean canParseNextChar() { return this.parserIndex < (this.array.length() - 1); } private void parseEndValueExpected() throws MyException { boolean isEndValueFound = false; boolean isCharProcessed = false; while(isEndValueFound == false) { final char c = this.array.charAt(this.parserIndex); if(isValueDelimiter(c)) { this.currentDimension.getValues().add(this.currentValue.toString()); this.currentValue = null; this.mode = END_DIMENSION_OR_VALUE_SEPARATOR_EXPECTED; isEndValueFound = true; isCharProcessed = true; } else if(c == '\\') { this.currentValue.append(c); if(canParseNextChar()) { this.parserIndex = this.parserIndex + 1; this.currentValue.append(this.array.charAt(this.parserIndex)); } isCharProcessed = true; } else { this.currentValue.append(c); isCharProcessed = true; } if(isCharProcessed == false || (isEndValueFound == false && canParseNextChar() == false)) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("expects end of value delimiter ("). append(displayCharArray(this.valueDelimiters, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).toString()); } this.parserIndex = this.parserIndex + 1; } } private void parseEndDimOrValueSeparExpected() throws MyException { boolean isEndDimOrValueSeparatorFound = false; boolean isCharProcessed = false; while(isEndDimOrValueSeparatorFound == false) { final char c = this.array.charAt(this.parserIndex); if(isWhiteSpace(c)) { isCharProcessed = true; // char ignored } else if(isValueSeparator(c)) { this.mode = VALUE_EXPECTED; isEndDimOrValueSeparatorFound = true; isCharProcessed = true; } else if(isEndingDimension(c)) { this.mode = END_DIMENSION_OR_DIMENSION_SEPARATOR_EXPECTED; this.currentDimension = this.currentDimension.getParent(); if(this.currentDimension == null) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("one too many ending dimension character found ("). append(displayCharArray(this.dimensionEndingDelimiters, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).toString()); } isEndDimOrValueSeparatorFound = true; isCharProcessed = true; } if(isCharProcessed == false || (isEndDimOrValueSeparatorFound == false && canParseNextChar() == false)) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("expects value separator ("). append(displayCharArray(this.valueSeparator, true)).append("), "). append("or an ending dimension char (").append(displayCharArray(this.valueSeparator, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).toString()); } this.parserIndex = this.parserIndex + 1; } } private void parseEndDimOrDimSeparExpected() throws MyException { boolean isEndDimOrDimSeparatorFound = false; boolean isCharProcessed = false; while(isEndDimOrDimSeparatorFound == false) { final char c = this.array.charAt(this.parserIndex); if(isWhiteSpace(c)) { isCharProcessed = true; // char ignored } else if(isDimensionSeparator(c)) { if(this.currentDimension == null) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("the string representation of the array should be finished, no more ending dimension char ("). append(displayCharArray(this.dimensionEndingDelimiters, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).append(" expected.").toString()); } this.mode = DIMENSION_EXPECTED; isEndDimOrDimSeparatorFound = true; isCharProcessed = true; } else if(isEndingDimension(c)) { if(this.currentDimension == null) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("one too many ending dimension char found ("). append(displayCharArray(this.dimensionEndingDelimiters, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).toString()); } this.mode = END_DIMENSION_OR_DIMENSION_SEPARATOR_EXPECTED; this.currentDimension = this.currentDimension.getParent(); isEndDimOrDimSeparatorFound = true; isCharProcessed = true; } if(isCharProcessed == false || (isEndDimOrDimSeparatorFound == false && canParseNextChar() == false)) { throw new MyException(new StringBuffer().append(getUnableToParseMessage()). append("expects dimension separator ("). append(displayCharArray(this.valueSeparator, true)).append("),"). append(' ').append("or an ending dimension character ("). append(displayCharArray(this.valueSeparator, true)). append(GenericConstants.CLOSING_ROUND_BRACKET).toString()); } this.parserIndex = this.parserIndex + 1; } } /** * Represent the dimension of an Array.
* Can contains values if mono-dimensional. * @author VonC */ public static class ArrayDimension { private ArrayDimension parent = null; private final ArrayList children = new ArrayList(); private final ArrayList values = new ArrayList(); final ArrayDimension getParent() { return this.parent; } final ArrayList getValues() { return this.values; } final ArrayList getChildren() { return this.children; } ArrayDimension(final ArrayDimension aParent) { this.parent = aParent; if(aParent != null) { aParent.children.add(this); } } /** * Check if the given dimension represents a value container.
* @return true if has values, false otherwise */ public final boolean hasValues() /*throws MyException*/ { boolean res = false; if(this.children.size() == 0) { res = true; } return res; } /** * Get sub-dimension array.
* Does not check if there is value or not * @param anIndex MUST be POSITIVE and < number of children 0-based * @return sub-array dimension */ public final ArrayDimension getChild(final int anIndex) { return this.children.get(anIndex); } /** * 0-based index .
* base on position within dimensions * @return 0 or more, never negative */ public final int getDepth() { int aDepth = 0; ArrayDimension aParent = this.parent; while(aParent != null) { aDepth = aDepth + 1; aParent = aParent.parent; } return aDepth; } /** * Check if the given depth, gives the numbers of sub-dimensions or values.
* If the String has not been parsed yet, this call will trigger the parse() method automatically (only once). * @return Number of values if has values, number of children otherwise */ public final int getNbElements() { int nbElements = 0; if(this.values.size() > 0) { nbElements = this.values.size(); } else { nbElements = this.children.size(); } return nbElements; } /** * Get value for a given index.
* index must take into account dimensions * @param anIndex 0-based index, multi-dimension wide * @return String value, never null, can be empty */ public final String getValue(final int anIndex) { String aValue = null; if(this.children.size() == 0) { aValue = this.values.get(anIndex); } return aValue; } } } }
{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}