DZone Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

Snippets has posted 5883 posts at DZone. View Full User Profile

Auto-test For Equals Function In Java Classes Through Annotations

10.19.2008
| 5290 views |
  • submit to reddit
        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. <br />
 * The appropriate parameters for instances to be tested for equality 
 * are stored in annotations just above the equals() method itself. <br />
 * JDK6, JUnit4.4, this class is also a test case, and its main function will launch JUnit on itself. 
 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
 * @see <a href="http://stackoverflow.com/questions/190007/automagic-unit-tests-for-upholding-object-method-contracts-in-java">
 * Automagic unit tests for upholding Object method contracts in Java?</a>
 */
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. <br />
	 * 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. <br />
	 * 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 <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	@interface Directives {
		/** array of directives. */
		EqualsInstancesDirective[] value();
	}
	
	/**
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	@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. <br />
	 * 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<Class<?>> someClasses)
	{
		for (final Class<?> aClass : someClasses) 
		{
			final Constructor<?>[] someConstructors = aClass.getDeclaredConstructors();
			if(someConstructors.length > 1)
			{
				final List<Object> someValues = new ArrayList<Object>();
				final List<Object> someDefaultValues = new ArrayList<Object>();
				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<Object> someValues,
			final List<Object> 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<Object> someDefaultValues, final List<Object> 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<Object> 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<Class<?>> 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. <br />
	 * 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. <br />
	 * Used for this short key example test class.
	 * @see <a href="http://stackoverflow.com/questions/114085/what-is-a-performant-string-hashing-function-that-results-in-a-32-bit-integer-w#114102">
	 * What is a performant string hashing function that results in a 32 bit integer with low collision rates? </a>
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	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. <br />
		 * 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 <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	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<Class<?>> getClasses() throws MyException
		{
			final List<Class<?>> someClasses = ReflectionUtils.getClasses(this.packageName);
			final List<Class<?>> someClassesWithEquals = new ArrayList<Class<?>>(); 
			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. <br />
	 * Used to examine dynamic java instances
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	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. <br />
	     * 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<Class<?>> getClasses(final String packageName) throws MyException
	    {
	        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	        //assert classLoader != null;
	        final String path = packageName.replace('.', '/');
	    	final List<Class<?>> classes = new ArrayList<Class<?>>();
	        Enumeration<URL> 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<Class<?>> getClassesFromJARFile(final String jar, final String packageName) throws MyException
	    {
	    	final List<Class<?>> classes = new ArrayList<Class<?>>();
	    	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<Class<?>> 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<Class<?>> getClassesFromDirectory(final File directory, final String packageName) throws MyException
	    {
	        final List<Class<?>> classes = new ArrayList<Class<?>>();
	        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. <br />
		 * 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. <br />
	     * 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. <br />
	     * 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. <br />
	 * Used for applicative static exception container.
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	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. <br />
	 * Mostly String constants.
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	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. <br />
	 * Encapsulate basic operations.
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	public static final class JavaUtils
	{
		private JavaUtils() {/* */}
		/**
		 * Mock Action, do nothing. <br />
		 * Used for FindBugs workaround: <br />
		 * 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. <br />
		 * Used for CheckStyle workaround: <br />
		 * "This block should not be empty".
		 */
		static void mockAction()
		{
			if(returnsFalse())
			{
				JavaUtils.mockAction(); //System. out.println("do nothing");
			}
		}
		
		/**
		 * Returns boolean false. <br />
		 * Used for FindBugs workaround like a method not called: <br />
		 * CN: Class implements Cloneable but does not define or use clone method
		 * @return false
		 */
		private static boolean returnsFalse()
		{
			return false;
		}
		
	}
	
	/**
	 * Generic String utilities functions. <br />
	 * Encapsulate basic operations.
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	public static final class StringUtils
	{
		private StringUtils() {/* */}

		/**
		 * Check if a string is  empty. <br />
		 * 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. <br />
		 * 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<br />
	     * Means that a string "strictly" contains another if and only if the substring is found 
	     * but not at the beginning of the string<br /> 
	     * "abcd", "ab" => false. <br /> 
	     * "abcd", "bc" => true. <br /> 
	     * Avoid "findBugs warning" <b>Method checks to see if result of String.indexOf is positive</b> <br />
	     * "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<br />
	     * Means that a string "strictly" contains another if and only if the substring is found 
	     * but not at the beginning of the string<br /> 
	     * "abcd", "ab" => false. <br /> 
	     * "abcd", "c" => true if positive index equals '1'. <br /> 
	     * Avoid "findBugs warning" <b>Method checks to see if result of String.indexOf is positive</b> <br />
	     * "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 <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	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. <br />
	 * Check if well-formed and returns java array of string values (one array by dimension detected). <br />
	 * Can be a string representation with various dimension and value delimiters, and separators (for value and delimiters), like in:
	 * <pre>[ { "first value first dimension", 'second value first dimension" ] , 
	 *  { 'first value second dimension'; 'second value second dimension" } }</pre>
	 * (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 ';'. <br />
	 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
	 */
	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. <br />
		 * 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. <br />
		 * 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. <br />
		 * 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. <br />
		 * 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. <br />
		 * Can contains values if mono-dimensional.
		 * @author <a href="http://stackoverflow.com/users/6309/vonc">VonC</a>
		 */
		public static class ArrayDimension
		{
			private ArrayDimension parent = null;
			private final ArrayList<ArrayDimension> children = new ArrayList<ArrayDimension>();
			private final ArrayList<String> values = new ArrayList<String>();
			final ArrayDimension getParent() { return this.parent; }
			final ArrayList<String> getValues() { return this.values; }
			final ArrayList<ArrayDimension> 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. <br />
			 * @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. <br />
			 * 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 . <br />
			 * 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. <br />
			 * 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. <br />
			 * 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;
			}
			
		}
	}
	
}