ca.sqlpower.object.annotation.SPAnnotationProcessor.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.object.annotation.SPAnnotationProcessor.java

Source

/*
 * Copyright (c) 2009, SQL Power Group Inc.
 *
 * This file is part of SQL Power Library.
 *
 * SQL Power Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * SQL Power Library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

package ca.sqlpower.object.annotation;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;

import javax.annotation.processing.Completion;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;

import org.apache.commons.collections.IteratorUtils;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;

import ca.sqlpower.dao.PersistedSPOProperty;
import ca.sqlpower.dao.PersistedSPObject;
import ca.sqlpower.dao.PersisterUtils;
import ca.sqlpower.dao.SPPersistenceException;
import ca.sqlpower.dao.SPPersister;
import ca.sqlpower.dao.SPPersister.DataType;
import ca.sqlpower.dao.helper.AbstractSPPersisterHelper;
import ca.sqlpower.dao.helper.PersisterHelperFinder;
import ca.sqlpower.dao.helper.SPPersisterHelper;
import ca.sqlpower.dao.session.SessionPersisterSuperConverter;
import ca.sqlpower.object.SPListener;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.object.annotation.ConstructorParameter.ParameterType;

/**
 * This {@link AnnotationProcessorFactory} creates {@link SPAnnotationProcessor}
 * s which handle generating source code for persister helper classes based on
 * the supported annotations used in {@link SPObject}s. These persister helpers
 * will be used by a session {@link SPPersister}. In order to invoke this
 * factory through Maven to generate the persister helper classes, simply
 * navigate to this workspace project folder in a terminal window and call
 * "mvn apt:process". It will place the generated persister helper files in
 * build/generated-sources/apt/. In order to invoke this factory through Ant,
 * just run the build through Ant.
 * 
 */
public class SPAnnotationProcessor implements Processor {

    /**
     * The {@link Set} of supported annotation types which the
     * {@link SPAnnotationProcessor} will process.
     */
    private final Set<String> annotations = new HashSet<String>();

    /**
     * Creates a new {@link SPAnnotationProcessor} and populates the set
     * of supported annotations.
     */
    public SPAnnotationProcessor() {
        annotations.add(Persistable.class.getName());
        annotations.add(Constructor.class.getName());
        annotations.add(ConstructorParameter.class.getName());
        annotations.add(Accessor.class.getName());
        annotations.add(Mutator.class.getName());
        annotations.add(MutatorParameter.class.getName());
    }

    @Override
    public Set<String> getSupportedOptions() {
        return Collections.emptySet();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }

    //TODO: commons.collections4 fixes this
    @SuppressWarnings("unchecked")
    @Override
    public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation,
            ExecutableElement member, String userText) {
        return (Iterable<? extends Completion>) IteratorUtils.emptyIterator();
    }
    //old processor code

    /**
     * The license file contents to prepend to a generated
     * {@link SPPersisterHelper}.
     */
    private final static String LICENSE_COMMENT_FILE_PATH = "src/main/resources/license_in_comment.txt";

    /**
     * @see SPPersisterHelper#commitObject(PersistedSPObject, Multimap, List,
     *      SessionPersisterSuperConverter)
     */
    private final static String COMMIT_OBJECT_METHOD_NAME = "commitObject";

    /**
     * @see SPPersisterHelper#commitProperty(SPObject, String, Object, DataType,
     *      SessionPersisterSuperConverter)
     */
    private final static String COMMIT_PROPERTY_METHOD_NAME = "commitProperty";

    /**
     * @see SessionPersisterSuperConverter#convertToBasicType(Object, Object...)
     */
    private final static String CONVERT_TO_BASIC_TYPE_METHOD_NAME = "convertToBasicType";

    /**
     * @see SessionPersisterSuperConverter#convertToComplexType(Object, Class)
     */
    private final static String CONVERT_TO_COMPLEX_TYPE_METHOD_NAME = "convertToComplexType";

    /**
     * @see AbstractSPPersisterHelper#createSPPersistenceExceptionMessage(SPObject,
     *      String)
     */
    private final static String CREATE_EXCEPTION_MESSAGE_METHOD_NAME = "createSPPersistenceExceptionMessage";

    /**
     * @see PersisterHelperFinder#findPersister(Class)
     */
    private final static String FIND_PERSISTER_METHOD_NAME = "findPersister";

    /**
     * @see AbstractSPPersisterHelper#findPersistedSPObject(String, String,
     *      String, List)
     */
    private final static String FIND_PERSISTED_OBJECT_METHOD_NAME = "findPersistedSPObject";

    /**
     * @see AbstractSPPersisterHelper#findPropertyAndRemove(String, String,
     *      Multimap)
     */
    private final static String FIND_PROPERTY_AND_REMOVE_METHOD_NAME = "findPropertyAndRemove";

    /**
     * @see SPPersisterHelper#findProperty(SPObject, String,
     *      SessionPersisterSuperConverter)
     */
    private final static String FIND_PROPERTY_METHOD_NAME = "findProperty";

    /**
     * @see SPPersisterHelper#getPersistedProperties()
     */
    private final static String GET_PERSISTED_PROPERTIES_METHOD_NAME = "getPersistedProperties";

    /**
     * @see SPObject#getParent()
     */
    private final static String GET_PARENT_METHOD_NAME = "getParent";

    /**
     * @see SPObject#getUUID()
     */
    private final static String GET_UUID_METHOD_NAME = "getUUID";

    /**
     * @see SPPersisterHelper#persistObject(SPObject, int, SPPersister,
     *      SessionPersisterSuperConverter)
     */
    private final static String PERSIST_OBJECT_METHOD_NAME = "persistObject";

    /**
     * @see SPPersisterHelper#persistObjectProperties(SPObject, SPPersister,
     *      SessionPersisterSuperConverter, List)
     */
    private final static String PERSIST_OBJECT_PROPERTIES_METHOD_NAME = "persistObjectProperties";

    /**
     * @see SPPersister#persistProperty(String, String, DataType, Object,
     *      Object)
     */
    private final static String PERSIST_PROPERTY_METHOD_NAME = "persistProperty";

    /**
     * The {@link AnnotationProcessorEnvironment} this
     * {@link AnnotationProcessor} will work with. The environment will give
     * useful information about classes annotated with {@link Persistable}.
     */
    private ProcessingEnvironment environment;

    /**
     * This contains the additional fully qualified class names that need to be
     * imported into the persister for the persister helper methods. This set
     * will be cleared at the start of creating each file.
     */
    private final Set<String> importedClassNames = new HashSet<String>();

    /**
     * This type generic parameter defines which {@link SPObject} class a
     * specific {@link SPPersisterHelper} handles for persisting objects and
     * properties.
     */
    private final String TYPE_GENERIC_PARAMETER = "T";

    /**
     * Initializes new {@link SPAnnotationProcessor} that deals exclusively with
     * annotations used in {@link SPObject}s which can generate
     * {@link SPPersisterHelper} classes for session {@link SPPersister}s.
     * 
     * @param environment
     *            The {@link AnnotationProcessorEnvironment} this processor will
     *            work with.
     */
    @Override
    public void init(ProcessingEnvironment environment) {
        this.environment = environment;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement el : annotations) {
            if (!this.annotations.contains(el.getQualifiedName().toString())) {
                return false;
            }
        }
        Map<Class<? extends SPObject>, SPClassVisitor> visitors = new HashMap<Class<? extends SPObject>, SPClassVisitor>();
        for (Element typeDecl : roundEnv.getElementsAnnotatedWith(Persistable.class)) {
            SPClassVisitor visitor = new SPClassVisitor(typeDecl);
            typeDecl.accept(visitor, null);
            if (visitor.isValid() && visitor.getVisitedClass() != null) {
                visitors.put(visitor.getVisitedClass(), visitor);
            }
        }

        // This block checks if each of the classes has super classes that
        // contain persistable properties. If so, they should inherit those
        // persistable properties. Any additional packages should be
        // imported as well.
        for (Entry<Class<? extends SPObject>, SPClassVisitor> e : visitors.entrySet()) {
            Class<? extends SPObject> superClass = e.getKey();
            SPClassVisitor visitor = e.getValue();

            Multimap<String, String> mutatorImports = HashMultimap.create(visitor.getMutatorImports());
            Map<String, Class<?>> propertiesToAccess = new HashMap<String, Class<?>>(
                    visitor.getPropertiesToAccess());
            Multimap<String, String> accessorAdditionalInfo = LinkedHashMultimap
                    .create(visitor.getAccessorAdditionalInfo());
            Map<String, Class<?>> propertiesToMutate = new HashMap<String, Class<?>>(
                    visitor.getPropertiesToMutate());
            Multimap<String, MutatorParameterObject> mutatorExtraParameters = LinkedHashMultimap
                    .create(visitor.getMutatorExtraParameters());
            Multimap<String, Class<? extends Exception>> mutatorThrownTypes = HashMultimap
                    .create(visitor.getMutatorThrownTypes());
            Set<String> propertiesToPersistOnlyIfNonNull = new HashSet<String>(
                    visitor.getPropertiesToPersistOnlyIfNonNull());

            importedClassNames.clear();

            // Generate the persister helper file if the SPObject class is not abstract.
            if (!Modifier.isAbstract(visitor.getVisitedClass().getModifiers())) {
                generatePersisterHelperFile(superClass, visitor.getConstructorImports(),
                        visitor.getConstructorParameters(), propertiesToAccess, accessorAdditionalInfo,
                        mutatorImports, propertiesToMutate, mutatorExtraParameters, mutatorThrownTypes,
                        propertiesToPersistOnlyIfNonNull);
            } else {
                generateAbstractPersisterHelperFile(superClass, visitor.getConstructorImports(), propertiesToAccess,
                        accessorAdditionalInfo, mutatorImports, propertiesToMutate, mutatorExtraParameters,
                        mutatorThrownTypes, propertiesToPersistOnlyIfNonNull);
            }
        }
        return true;
    }

    /**
     * Generates the Java source file for an {@link SPPersisterHelper} class
     * that is to be used by a session {@link SPPersister} or workspace
     * persister listener. This generated persister helper class should deal
     * with creating new objects and applying persisted properties to a given
     * {@link SPObject}, or persisting objects and properties from an
     * {@link SPObject} to an {@link SPPersister}.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param constructorImports
     *            The {@link Set} of imports that the generated persister helper
     *            requires for calling the {@link Constructor} annotated
     *            constructor.
     * @param constructorParameters
     *            The {@link List} of {@link ConstructorParameterObject}s that
     *            contain information about what the parameter should be used
     *            for.
     * @param propertiesToAccess
     *            The {@link Map} of getter method names of persistable
     *            properties to its property type.
     * @param accessorAdditionalInfo
     *            The {@link Multimap} of getter methods mapped to additional
     *            properties a session {@link SPPersister} requires to convert
     *            the getter's returned value from a complex to basic
     *            persistable type.
     * @param mutatorImports
     *            The {@link Multimap} of setter methods to imports that the
     *            generated persister helper requires for calling the
     *            {@link Mutator} annotated setters.
     * @param propertiesToMutate
     *            The {@link Map} of setter method names of persistable
     *            properties to its property type.
     * @param mutatorExtraParameters
     *            The {@link Multimap} of setter methods mapped to each of its
     *            extra parameters (second parameter and onwards).
     * @param mutatorThrownTypes
     *            The {@link Multimap} of {@link Exception}s thrown by each
     *            persistable property setter.
     * @param propertiesToPersistOnlyIfNonNull
     *            The {@link Set} of persistable properties that can only be
     *            persisted if its value is not null.
     */
    private void generatePersisterHelperFile(Class<? extends SPObject> visitedClass, Set<String> constructorImports,
            List<ConstructorParameterObject> constructorParameters, Map<String, Class<?>> propertiesToAccess,
            Multimap<String, String> accessorAdditionalInfo, Multimap<String, String> mutatorImports,
            Map<String, Class<?>> propertiesToMutate,
            Multimap<String, MutatorParameterObject> mutatorExtraParameters,
            Multimap<String, Class<? extends Exception>> mutatorThrownTypes,
            Set<String> propertiesToPersistOnlyIfNonNull) {
        try {
            final String helperPackage = visitedClass.getPackage().getName() + "."
                    + PersisterHelperFinder.GENERATED_PACKAGE_NAME;
            final String simpleClassName = visitedClass.getSimpleName() + "PersisterHelper";
            final Class<?> superclass = visitedClass.getSuperclass();
            int tabs = 0;

            Filer f = environment.getFiler();
            PrintWriter pw = new PrintWriter(
                    f.createSourceFile(helperPackage + "." + simpleClassName).openOutputStream());
            tabs++;
            final String commitObjectMethod = generateCommitObjectMethod(visitedClass, constructorParameters, tabs);
            final String commitPropertyMethod = generateCommitPropertyMethod(visitedClass, propertiesToMutate,
                    mutatorExtraParameters, mutatorThrownTypes, tabs);
            final String findPropertyMethod = generateFindPropertyMethod(visitedClass, propertiesToAccess,
                    accessorAdditionalInfo, tabs);
            final String persistObjectMethod = generatePersistObjectMethod(visitedClass, constructorParameters,
                    propertiesToAccess, propertiesToPersistOnlyIfNonNull, tabs);
            final String PersistObjectMethodHelper = generatePersistObjectMethodHelper(visitedClass,
                    propertiesToAccess, propertiesToMutate, propertiesToPersistOnlyIfNonNull, tabs);
            final String getPersistedPropertiesMethod = generateGetPersistedPropertyListMethod(visitedClass,
                    propertiesToMutate, tabs);
            tabs--;

            if (superclass == Object.class) {
                importedClassNames.add(AbstractSPPersisterHelper.class.getName());
            } else {
                importedClassNames.add(PersisterHelperFinder.getPersisterHelperClassName(superclass.getName()));
            }
            final String imports = generateImports(visitedClass, constructorImports, mutatorImports);

            pw.print(generateWarning());
            pw.print("\n");
            pw.print(generateLicense());
            pw.print("\n");
            pw.print("package " + helperPackage + ";\n");
            pw.print("\n");
            pw.print(imports);
            pw.print("\n");

            if (superclass == Object.class) {
                pw.print(String.format("public class %s extends %s<%s> {\n", simpleClassName,
                        AbstractSPPersisterHelper.class.getSimpleName(), visitedClass.getSimpleName()));
            } else if (Modifier.isAbstract(superclass.getModifiers())) {
                pw.print(String.format("public class %s extends %s<%s> {\n", simpleClassName,
                        superclass.getSimpleName() + "PersisterHelper", visitedClass.getSimpleName()));
            } else {
                pw.print(String.format("public class %s extends %s {\n", simpleClassName,
                        superclass.getSimpleName() + "PersisterHelper"));
            }

            pw.print("\n");
            pw.print(commitObjectMethod);
            pw.print("\n");
            pw.print(commitPropertyMethod);
            pw.print("\n");
            pw.print(findPropertyMethod);
            pw.print("\n");
            pw.print(persistObjectMethod);
            pw.print("\n");
            pw.print(PersistObjectMethodHelper);
            pw.print("\n");
            pw.print(getPersistedPropertiesMethod);
            pw.print("\n");

            pw.print("}\n");
            pw.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Generates the Java source file that is a helper of {@link SPPersisterHelper}.
     * These helpers are not true {@link SPPersisterHelper}s because they only do
     * part of a helper's job. They also do not implement the interface because nothing
     * outside of the {@link SPPersisterHelper}s should be using them directly.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param constructorImports
     *            The {@link Set} of imports that the generated persister helper
     *            requires for calling the {@link Constructor} annotated
     *            constructor.
     * @param constructorParameters
     *            The {@link List} of {@link ConstructorParameterObject}s that
     *            contain information about what the parameter should be used
     *            for.
     * @param propertiesToAccess
     *            The {@link Map} of getter method names of persistable
     *            properties to its property type.
     * @param accessorAdditionalInfo
     *            The {@link Multimap} of getter methods mapped to additional
     *            properties a session {@link SPPersister} requires to convert
     *            the getter's returned value from a complex to basic
     *            persistable type.
     * @param mutatorImports
     *            The {@link Multimap} of setter methods to imports that the
     *            generated persister helper requires for calling the
     *            {@link Mutator} annotated setters.
     * @param propertiesToMutate
     *            The {@link Map} of setter method names of persistable
     *            properties to its property type.
     * @param mutatorExtraParameters
     *            The {@link Multimap} of setter methods mapped to each of its
     *            extra parameters (second parameter and onwards).
     * @param mutatorThrownTypes
     *            The {@link Multimap} of {@link Exception}s thrown by each
     *            persistable property setter.
     * @param propertiesToPersistOnlyIfNonNull
     *            The {@link Set} of persistable properties that can only be
     *            persisted if its value is not null.
     */
    private void generateAbstractPersisterHelperFile(Class<? extends SPObject> visitedClass,
            Set<String> constructorImports, Map<String, Class<?>> propertiesToAccess,
            Multimap<String, String> accessorAdditionalInfo, Multimap<String, String> mutatorImports,
            Map<String, Class<?>> propertiesToMutate,
            Multimap<String, MutatorParameterObject> mutatorExtraParameters,
            Multimap<String, Class<? extends Exception>> mutatorThrownTypes,
            Set<String> propertiesToPersistOnlyIfNonNull) {
        try {
            final String helperPackage = visitedClass.getPackage().getName() + "."
                    + PersisterHelperFinder.GENERATED_PACKAGE_NAME;
            final String simpleClassName = visitedClass.getSimpleName() + "PersisterHelper";
            final Class<?> superclass = visitedClass.getSuperclass();
            int tabs = 0;

            Filer f = environment.getFiler();
            PrintWriter pw = new PrintWriter(
                    f.createSourceFile(helperPackage + "." + simpleClassName).openOutputStream());
            tabs++;
            final String commitPropertyMethod = generateCommitPropertyMethod(visitedClass, propertiesToMutate,
                    mutatorExtraParameters, mutatorThrownTypes, tabs);
            final String findPropertyMethod = generateFindPropertyMethod(visitedClass, propertiesToAccess,
                    accessorAdditionalInfo, tabs);
            final String persistObjectMethodHelper = generatePersistObjectMethodHelper(visitedClass,
                    propertiesToAccess, propertiesToMutate, propertiesToPersistOnlyIfNonNull, tabs);
            final String getPersistedPropertiesMethod = generateGetPersistedPropertyListMethod(visitedClass,
                    propertiesToMutate, tabs);
            tabs--;

            if (superclass == Object.class) {
                importedClassNames.add(AbstractSPPersisterHelper.class.getName());
            } else {
                importedClassNames.add(PersisterHelperFinder.getPersisterHelperClassName(superclass.getName()));
            }
            final String generateImports = generateImports(visitedClass, constructorImports, mutatorImports);

            pw.print(generateWarning());
            pw.print("\n");
            pw.print(generateLicense());
            pw.print("\n");
            pw.print("package " + helperPackage + ";\n");
            pw.print("\n");
            pw.print(generateImports);
            pw.print("\n");

            if (superclass == Object.class) {
                pw.print(String.format("public abstract class %s<%s extends %s> extends %s<%s> {\n",
                        simpleClassName, TYPE_GENERIC_PARAMETER, visitedClass.getSimpleName(),
                        AbstractSPPersisterHelper.class.getSimpleName(), TYPE_GENERIC_PARAMETER));
            } else if (Modifier.isAbstract(superclass.getModifiers())) {
                pw.print(String.format("public abstract class %s<%s extends %s> extends %s<%s> {\n",
                        simpleClassName, TYPE_GENERIC_PARAMETER, visitedClass.getSimpleName(),
                        superclass.getSimpleName() + "PersisterHelper", TYPE_GENERIC_PARAMETER));
            } else {
                pw.print(String.format("public abstract class %s<%s extends %s> extends %s {\n", simpleClassName,
                        TYPE_GENERIC_PARAMETER, visitedClass.getSimpleName() + "PersisterHelper",
                        superclass.getName()));
            }

            pw.print("\n");
            pw.print(commitPropertyMethod);
            pw.print("\n");
            pw.print(findPropertyMethod);
            pw.print("\n");
            pw.print(persistObjectMethodHelper);
            pw.print("\n");
            pw.print(getPersistedPropertiesMethod);
            pw.print("\n");

            pw.print("}\n");
            pw.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Builds a String with the given number of tab characters to use for
     * indenting generated code.
     * 
     * @param i
     *            The number of tab characters.
     * @return A String with the given number of tab characters.
     */
    private String indent(int i) {
        StringBuilder sb = new StringBuilder();
        final String tab = "\t";
        for (; i > 0; i--) {
            sb.append(tab);
        }
        return sb.toString();
    }

    /**
     * Generates and returns a warning in a comment indicating that a source
     * file is in fact generated by this annotation processor based on
     * annotations within {@link SPObject}s.
     */
    private String generateWarning() {
        StringBuilder sb = new StringBuilder();

        niprintln(sb, "/*");
        niprintln(sb, " * This is a GENERATED class based on hand made annotations in "
                + SPObject.class.getSimpleName() + " classes");
        niprintln(sb, " * and should NOT be modified here. If you need to change this class, modify");
        niprintln(sb, " * " + SPAnnotationProcessor.class.getSimpleName() + " instead.");
        niprintln(sb, " */");

        return sb.toString();
    }

    /**
     * Generates and returns the GPL license header in a comment, as Eclipse
     * does whenever a new source file is created. The license is taken from
     * src/license_in_comment.txt.
     */
    private String generateLicense() {
        StringBuilder sb = new StringBuilder();
        try {
            FileReader fr = new FileReader(LICENSE_COMMENT_FILE_PATH);
            BufferedReader br = new BufferedReader(fr);
            String line;
            while ((line = br.readLine()) != null) {
                niprintln(sb, line);
            }
            fr.close();
            br.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return sb.toString();
    }

    /**
     * Generates and returns source code for importing packages that are
     * required by the persister helper this class is generating.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param constructorImports
     *            The {@link Set} of packages that visitedClass uses in its
     *            {@link Constructor} annotated constructor and need to be
     *            imported.
     * @param mutatorImports
     *            The {@link Multimap} of setter methods to packages that
     *            visitedClass uses in its {@link Mutator} annotated methods and
     *            needs to be imported.
     * @return The source code for the generated imports.
     */
    private String generateImports(Class<? extends SPObject> visitedClass, Set<String> constructorImports,
            Multimap<String, String> mutatorImports) {
        final String helperPackage = visitedClass.getPackage().getName() + "."
                + PersisterHelperFinder.GENERATED_PACKAGE_NAME;

        // Using a TreeSet here to sort imports alphabetically.
        Set<String> allImports = new TreeSet<String>();
        if (!Modifier.isAbstract(visitedClass.getModifiers())) {
            allImports.addAll(constructorImports);
        }
        allImports.addAll(mutatorImports.values());

        StringBuilder sb = new StringBuilder();

        // XXX Need to import any additional classes this generated persister helper
        // class requires, aside from those needed in visitedClass.
        allImports.add(List.class.getName());
        allImports.add(visitedClass.getName());
        allImports.add(SPPersistenceException.class.getName());
        allImports.add(SPPersister.class.getName());
        allImports.add(SessionPersisterSuperConverter.class.getName());
        allImports.add(SPObject.class.getName());
        allImports.add(DataType.class.getName());
        allImports.addAll(importedClassNames);

        for (String pkg : allImports) {
            // No need to import java.lang as it is automatically imported.
            // No need to import package if the persister helper is already
            // in the package.
            // Also want to keep array classes out
            if (!pkg.startsWith("java.lang") && !pkg.startsWith("[L")) {
                // Nested classes, enums, etc. will be separated by the "$"
                // character but we need to change them to "." so it can be
                // imported correctly.
                String pkgName = pkg.replaceAll("\\$", ".");

                // Only import the package if it is not the same one
                // that the persister helper exists in.
                int index = pkgName.lastIndexOf(".");
                if (index == -1) {
                    index = pkgName.length();
                }
                if (!pkgName.substring(0, index).equals(helperPackage)) {
                    niprintln(sb, "import " + pkgName + ";");
                }
            }
        }

        return sb.toString();
    }

    /**
     * Generates and returns source code for a commitObject method based on an
     * {@link SPObject} annotated constructor along with its annotated
     * constructor arguments.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param constructorParameters
     *            The {@link Map} of the class property names to constructor
     *            parameter class types. This order of this map must absolutely
     *            be guaranteed as the order of constructor arguments requires
     *            it.
     * @param tabs
     *            The number of tab characters to use to indent the generated
     *            method.
     * @return The source code for the generated commitObject method.
     * @see SPPersister#persistObject(String, String, String, int)
     */
    private String generateCommitObjectMethod(Class<? extends SPObject> visitedClass,
            List<ConstructorParameterObject> constructorParameters, int tabs) {
        StringBuilder sb = new StringBuilder();
        final String objectField = "o";
        final String persistedPropertiesField = "persistedProperties";
        final String persistedObjectField = "pso";
        final String persistedObjectsListField = "persistedObjects";
        final String converterField = "converter";
        final String uuidField = "uuid";
        final String exceptionField = "e";

        // commitObject method header.
        // public <visitedClass> commitObject(
        //       PersistedSPObject pso,
        //       Multimap<String, PersistedSPOProperty> persistedProperties,
        //       List<PersistedSPObject> persistedObjects,
        //       SessionPersisterSuperConverter converter) 
        //       throws SPPersistenceException {
        println(sb, tabs,
                String.format("public %s commitObject(" + "%s %s, %s<%s, %s> %s, %s<%s> %s, %s %s) throws %s {",
                        visitedClass.getSimpleName(), PersistedSPObject.class.getSimpleName(), persistedObjectField,
                        Multimap.class.getSimpleName(), String.class.getSimpleName(),
                        PersistedSPOProperty.class.getSimpleName(), persistedPropertiesField,
                        List.class.getSimpleName(), PersistedSPObject.class.getSimpleName(),
                        persistedObjectsListField, SessionPersisterSuperConverter.class.getSimpleName(),
                        converterField, SPPersistenceException.class.getSimpleName()));

        importedClassNames.add(PersistedSPObject.class.getName());
        importedClassNames.add(PersistedSPOProperty.class.getName());
        importedClassNames.add(Multimap.class.getName());
        tabs++;

        // String uuid = pso.getUUID();
        println(sb, tabs, String.format("%s %s = %s.%s();", String.class.getSimpleName(), uuidField,
                persistedObjectField, GET_UUID_METHOD_NAME));

        // Assign each constructor parameter property to a variable.
        final String parameterTypeField = "parameterType";
        final String classToLoadField = "classToLoad";
        boolean parameterTypeFieldDeclared = false;
        boolean classToLoadFieldDeclared = false;

        for (ConstructorParameterObject cpo : constructorParameters) {
            String parameterType = cpo.getType().getSimpleName();
            String parameterName = cpo.getName();

            if (ParameterType.PROPERTY.equals(cpo.getProperty())) {
                if (cpo.getType() == Object.class) {
                    print(sb, tabs, "");

                    if (!parameterTypeFieldDeclared) {
                        niprint(sb, PersistedSPOProperty.class.getSimpleName() + " ");
                        parameterTypeFieldDeclared = true;
                    }

                    // parameterType = findProperty(uuid, "<parameterName>", persistedProperties);
                    niprintln(sb, String.format("%s = %s(%s, \"%s\", %s);", parameterTypeField,
                            FIND_PROPERTY_METHOD_NAME, uuidField, parameterName, persistedPropertiesField));

                    // <parameterType> <parameterName>;
                    println(sb, tabs, String.format("%s %s;", parameterType, parameterName));
                    // if (parameterType != null) {
                    println(sb, tabs, String.format("if (%s != null) {", parameterTypeField));
                    tabs++;

                    // <parameterName> = 
                    //       (<parameterType>) converter.convertToComplexType(
                    //             findPropertyAndRemove(
                    //                   uuid,
                    //                   "<parameterName>",
                    //                   persistedProperties),
                    //             parameterType.getDataType().getRepresentation()); 
                    println(sb, tabs,
                            String.format(
                                    "%s = (%s) %s.%s(%s(%s, \"%s\", %s), %s.getDataType().getRepresentation());",
                                    parameterName, parameterType, converterField,
                                    CONVERT_TO_COMPLEX_TYPE_METHOD_NAME, FIND_PROPERTY_AND_REMOVE_METHOD_NAME,
                                    uuidField, parameterName, persistedPropertiesField, parameterTypeField));
                    tabs--;
                    println(sb, tabs, "} else {");
                    tabs++;
                    // <parameterName> = null;
                    println(sb, tabs, String.format("%s = null;", parameterName));
                    tabs--;
                    println(sb, tabs, "}");
                } else {
                    // <parameterType> <parameterName> = 
                    //       (<parameterType>) converter.convertToComplexType(
                    //             findPropertyAndRemove(
                    //                   uuid,
                    //                   "<parameterName>",
                    //                   persistedProperties),
                    //             <parameterType>.class);
                    println(sb, tabs,
                            String.format("%s %s = (%s) %s.%s(%s(%s, \"%s\", %s), %s.class);", parameterType,
                                    parameterName, parameterType, converterField,
                                    CONVERT_TO_COMPLEX_TYPE_METHOD_NAME, FIND_PROPERTY_AND_REMOVE_METHOD_NAME,
                                    uuidField, parameterName, persistedPropertiesField, parameterType));
                }
            } else if (ParameterType.CHILD.equals(cpo.getProperty())) {
                String objectUUIDField = parameterName + "UUID";
                String childPersisterHelperField = parameterName + "Helper";
                String childPersistedObject = parameterName + "PSO";

                // String <parameterName>UUID = 
                //       (String) findPropertyAndRemove(
                //             uuid,
                //             "<parameterName>",
                //             persistedProperties);
                println(sb, tabs,
                        String.format("%s %s = (%s) %s(%s, \"%s\", %s);", String.class.getSimpleName(),
                                objectUUIDField, String.class.getSimpleName(), FIND_PROPERTY_AND_REMOVE_METHOD_NAME,
                                uuidField, parameterName, persistedPropertiesField));

                // PersistedSPObject <parameterName>PSO = 
                //       findPersistedSPObject(
                //             uuid,
                //             "<SPObject simple name>",
                //             <parameterName>UUID,
                //             persistedObjects);
                println(sb, tabs,
                        String.format("%s %s = %s(%s, %s, %s);", PersistedSPObject.class.getSimpleName(),
                                childPersistedObject, FIND_PERSISTED_OBJECT_METHOD_NAME, uuidField, objectUUIDField,
                                persistedObjectsListField));

                if (!classToLoadFieldDeclared) {
                    println(sb, tabs, String.format("%s<? extends %s> %s;", Class.class.getSimpleName(),
                            SPObject.class.getSimpleName(), classToLoadField));
                    classToLoadFieldDeclared = true;
                }

                // try {
                println(sb, tabs, "try {");
                tabs++;

                // classToLoad = 
                //       (Class<? extends SPObject>)
                //             <visitedClass>.class.getClassLoader().loadClass(
                //                   <parameterName>.getType());
                println(sb, tabs,
                        String.format("%s = (%s<? extends %s>) %s.class.getClassLoader().loadClass(%s.getType());",
                                classToLoadField, Class.class.getSimpleName(), SPObject.class.getSimpleName(),
                                visitedClass.getSimpleName(), childPersistedObject));

                tabs--;
                // catch (ClassNotFoundException e) {
                println(sb, tabs, String.format("} catch (%s %s) {", ClassNotFoundException.class.getSimpleName(),
                        exceptionField));
                tabs++;
                // throw new SPPersistenceException(null, e);
                println(sb, tabs, String.format("throw new %s(null, %s);",
                        SPPersistenceException.class.getSimpleName(), exceptionField));
                tabs--;
                println(sb, tabs, "}");

                // SPPersisterHelper<? extends SPObject> <parameterName>PersisterHelper;
                println(sb, tabs, String.format("%s<? extends %s> %s;", SPPersisterHelper.class.getSimpleName(),
                        SPObject.class.getSimpleName(), childPersisterHelperField));
                // try {
                println(sb, tabs, "try {");
                tabs++;

                // <parameterName>PersisterHelper = PersisterHelperFinder.findPersister(classToLoad);
                println(sb, tabs, String.format("%s = %s.%s(%s);", childPersisterHelperField,
                        PersisterHelperFinder.class.getSimpleName(), FIND_PERSISTER_METHOD_NAME, classToLoadField));
                tabs--;
                // } catch (Exception e) {
                println(sb, tabs,
                        String.format("} catch (%s %s) {", Exception.class.getSimpleName(), exceptionField));
                tabs++;
                // throw new SPPersistenceException(uuid, e);
                println(sb, tabs, String.format("throw new %s(%s, %s);",
                        SPPersistenceException.class.getSimpleName(), uuidField, exceptionField));
                tabs--;
                println(sb, tabs, "}");
                // <parameterType> <parameterName> = 
                //       (<parameterType>) <parameterName>PersisterHelper.commitObject(
                //             <parameterName>PSO,
                //             persistedProperties,
                //             persistedObjects,
                //             converter);
                println(sb, tabs, String.format("%s %s = (%s) %s.%s(%s, %s, %s, %s);", parameterType, parameterName,
                        parameterType, childPersisterHelperField, COMMIT_OBJECT_METHOD_NAME, childPersistedObject,
                        persistedPropertiesField, persistedObjectsListField, converterField));
                importedClassNames.add(PersisterHelperFinder.class.getName());
                importedClassNames.add(SPPersisterHelper.class.getName());

            } else {
                throw new IllegalStateException("Don't know how to handle " + "property type " + cpo.getProperty());
            }
        }
        niprintln(sb, "");

        // Create and return the new object.
        // <visitedClass> o;
        println(sb, tabs, String.format("%s %s;", visitedClass.getSimpleName(), objectField));
        // try {
        println(sb, tabs, "try {");
        tabs++;
        // o = new <visitedClass>(
        print(sb, tabs, String.format("%s = new %s(", objectField, visitedClass.getSimpleName()));

        boolean firstArg = true;

        // Pass in all of the constructor arguments.
        for (ConstructorParameterObject cpo : constructorParameters) {
            if (!firstArg) {
                niprint(sb, ", ");
            }
            niprint(sb, cpo.getName());
            firstArg = false;
        }

        niprintln(sb, ");");
        tabs--;

        // catch (Exception e) {
        println(sb, tabs, String.format("} catch (%s %s) {", Exception.class.getSimpleName(), exceptionField));
        tabs++;
        // throw new SPPersistenceException(null, e);
        println(sb, tabs, String.format("throw new %s(null, %s);", SPPersistenceException.class.getSimpleName(),
                exceptionField));
        tabs--;
        println(sb, tabs, "}");

        // o.setUUID(uuid);
        println(sb, tabs, String.format("%s.setUUID(%s);", objectField, uuidField));

        // pso.setLoaded(true);
        println(sb, tabs, String.format("%s.setLoaded(true);", persistedObjectField));

        // return o;
        println(sb, tabs, String.format("return %s;", objectField));

        tabs--;
        println(sb, tabs, "}");

        return sb.toString();
    }

    /**
     * Generates and returns source code for a commitProperty method based on
     * setter methods annotated with {@link Mutator} used in a given
     * {@link SPObject} class. The purpose of this commitProperty method is to
     * allow a session {@link SPPersister} to commit a persisted property change
     * into an {@link SPSession}. This helper method will be called by the
     * session {@link SPPersister#commit()} method.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param setters
     *            The {@link Map} of property setter methods to their property
     *            types that can be used by an {@link SPPersister} to persist
     *            properties into an {@link SPSession}.
     * @param mutatorExtraParameters
     *            The {@link Multimap} of setter methods mapped to each of its
     *            extra parameters (second parameter and onwards).
     * @param mutatorThrownTypes
     *            The {@link Multimap} of property setter methods to their
     *            thrown exceptions.
     * @param tabs
     *            The number of tab characters to use to indent this generated
     *            method block.
     * @return The source code for the generated commitProperty method.
     * @see SPPersister#persistProperty(String, String,
     *      ca.sqlpower.dao.SPPersister.DataType, Object)
     * @see SPPersister#commit()
     */
    private String generateCommitPropertyMethod(Class<? extends SPObject> visitedClass,
            Map<String, Class<?>> setters, Multimap<String, MutatorParameterObject> mutatorExtraParameters,
            Multimap<String, Class<? extends Exception>> mutatorThrownTypes, int tabs) {
        StringBuilder sb = new StringBuilder();
        final String genericObjectField = "o";
        final String objectField = "castedObject";
        final String propertyNameField = "propertyName";
        final String newValueField = "newValue";
        final String converterField = "converter";
        final String exceptionField = "e";
        final String dataTypeField = "dataType";

        boolean firstIf = true;

        // commitProperty method header.
        // public void commitProperty(
        //       SPObject o,
        //       String propertyName,
        //       Object newValue,
        //       DataType dataType,
        //       SessionPersisterSuperConverter converter) 
        //       throws SPPersistenceException {
        println(sb, tabs, String.format("public void %s(%s %s, %s %s, %s %s, %s %s, %s %s) throws %s {",
                COMMIT_PROPERTY_METHOD_NAME, SPObject.class.getSimpleName(), genericObjectField,
                String.class.getSimpleName(), propertyNameField, Object.class.getSimpleName(), newValueField,
                DataType.class.getSimpleName(), dataTypeField, SessionPersisterSuperConverter.class.getSimpleName(),
                converterField, SPPersistenceException.class.getSimpleName()));
        tabs++;

        if (!setters.isEmpty()) {
            // If the SPObject class this persister helper handles is abstract,
            // use the type generic defined in the class header.
            // Otherwise, use the SPObject class directly.
            if (Modifier.isAbstract(visitedClass.getModifiers())) {
                // T castedObject = (T) o;
                println(sb, tabs, String.format("%s %s = (%s) %s;", TYPE_GENERIC_PARAMETER, objectField,
                        TYPE_GENERIC_PARAMETER, genericObjectField));
            } else {
                // <visitedClass> castedObject = (<visitedClass>) o;
                println(sb, tabs, String.format("%s %s = (%s) %s;", visitedClass.getSimpleName(), objectField,
                        visitedClass.getSimpleName(), genericObjectField));
            }

            // Search for the matching property name and set the value.
            for (Entry<String, Class<?>> e : setters.entrySet()) {
                String methodName = e.getKey();
                Class<?> type = e.getValue();

                print(sb, tabs, "");

                if (!firstIf) {
                    niprint(sb, "} else ");
                }

                // if (propertyName.equals("<method to property name>") {
                niprintln(sb, String.format("if (%s.equals(\"%s\")) {", propertyNameField,
                        SPAnnotationProcessorUtils.convertMethodToProperty(methodName)));
                tabs++;

                boolean throwsExceptions = mutatorThrownTypes.containsKey(e.getKey());

                if (throwsExceptions) {
                    println(sb, tabs, "try {");
                    tabs++;
                }

                // Assign each extra argument value of setter methods to variables
                // to pass into the call to the setter afterwards.
                for (MutatorParameterObject extraParam : mutatorExtraParameters.get(methodName)) {
                    // <extraParam type> <extraParam name> = 
                    //       <extraParam type>.valueOf("<extraParam name>");
                    println(sb, tabs,
                            String.format("%s %s = %s.valueOf(\"%s\");", extraParam.getType().getSimpleName(),
                                    extraParam.getName(), extraParam.getType().getSimpleName(),
                                    extraParam.getValue()));
                }

                // Pass in the actual property value as the first argument to the setter.
                String conversionType;
                if (type == Object.class) {
                    conversionType = dataTypeField + ".getRepresentation()";
                } else {
                    conversionType = type.getSimpleName() + ".class";
                }

                // castedObject.<setter>(
                //       (<type>) converter.convertToComplexType(
                //             newValue, <dataType.getRepresentation | type.class>);
                print(sb, tabs,
                        String.format("%s.%s((%s) %s.%s(%s, %s)", objectField, methodName, type.getSimpleName(),
                                converterField, CONVERT_TO_COMPLEX_TYPE_METHOD_NAME, newValueField,
                                conversionType));

                // Pass in the variables holding the extra argument values.
                for (MutatorParameterObject extraParam : mutatorExtraParameters.get(methodName)) {
                    // , <extraParam name>
                    niprint(sb, ", " + extraParam.getName());
                }

                niprintln(sb, ");");
                tabs--;

                // Catch any exceptions that the setter throws.
                if (throwsExceptions) {
                    for (Class<? extends Exception> thrownType : mutatorThrownTypes.get(methodName)) {

                        // } catch (<Exception type> e) {
                        println(sb, tabs,
                                String.format("} catch (%s %s) {", thrownType.getSimpleName(), exceptionField));
                        tabs++;

                        // throw new SPPersistenceException(
                        //       castedObject.getUUID(),
                        //       createSPPersistenceExceptionMessage(
                        //             castedObject,
                        //             propertyName),
                        //       e);
                        println(sb, tabs,
                                String.format("throw new %s(%s.%s(), %s(%s, %s), %s);",
                                        SPPersistenceException.class.getSimpleName(), objectField,
                                        GET_UUID_METHOD_NAME, CREATE_EXCEPTION_MESSAGE_METHOD_NAME, objectField,
                                        propertyNameField, exceptionField));
                        tabs--;
                    }
                    println(sb, tabs, "}");
                    tabs--;
                }

                firstIf = false;
            }

            if (!firstIf) {
                println(sb, tabs, "} else {");
                tabs++;
            }
        }

        if (SPObject.class.isAssignableFrom(visitedClass.getSuperclass())) {
            // super.commitProperty(o, <property>, newValue, dataType, converter);
            println(sb, tabs, String.format("super.%s(%s, %s, %s, %s, %s);", COMMIT_PROPERTY_METHOD_NAME,
                    genericObjectField, propertyNameField, newValueField, dataTypeField, converterField));
        } else {
            // Throw an SPPersistenceException if the property is not persistable or unrecognized.
            // throw new SPPersistenceException(
            //       castedObject.getUUID(),
            //       createSPPersistenceExceptionMessage(
            //             castedObject,
            //             propertyName));
            println(sb, tabs,
                    String.format("throw new %s(%s.%s(), %s(%s, %s));",
                            SPPersistenceException.class.getSimpleName(), objectField, GET_UUID_METHOD_NAME,
                            CREATE_EXCEPTION_MESSAGE_METHOD_NAME, objectField, propertyNameField));
        }

        if (!firstIf) {
            tabs--;
            println(sb, tabs, "}");
        }

        tabs--;
        println(sb, tabs, "}");

        return sb.toString();
    }

    private String generateGetPersistedPropertyListMethod(Class<? extends SPObject> visitedClass,
            Map<String, Class<?>> setters, int tabs) {

        importedClassNames.add(ArrayList.class.getName());
        importedClassNames.add(Arrays.class.getName());

        StringBuilder sb = new StringBuilder();
        final String ppaField = "persistedPropertiesArray";
        final String pplField = "persistedPropertiesList";

        // private List<String> persistedPropertiesList = null;
        // We are storing the persisted properties list here to save time and
        // memory on recreating and destroying this list repeatedly. 
        println(sb, tabs, String.format("private %s<%s> %s = null;", List.class.getSimpleName(),
                String.class.getSimpleName(), pplField));

        // public List<String> getPersistedProperties() throws SPPersistenceException {
        println(sb, tabs,
                String.format("public %s<%s> %s() throws %s {", List.class.getSimpleName(),
                        String.class.getSimpleName(), GET_PERSISTED_PROPERTIES_METHOD_NAME,
                        SPPersistenceException.class.getSimpleName()));
        tabs++;

        // If we have already created a list holding the properties return that
        // instead of creating a new one.
        println(sb, tabs, String.format("if (%s != null) return %s;", pplField, pplField));

        // Create array of strings holding persisted properties
        // String[] persistedPropertiesArray = {
        println(sb, tabs, String.format("%s[] %s = {", String.class.getSimpleName(), ppaField));
        Object[] properties = setters.keySet().toArray();
        if (properties.length > 0) {
            tabs++;
            for (int i = 0; i < properties.length; i++) {
                print(sb, tabs, String.format("\"%s\"",
                        SPAnnotationProcessorUtils.convertMethodToProperty((String) properties[i])));
                if (i < properties.length - 1) {
                    niprint(sb, ",");
                }
                niprintln(sb, "");
            }
            tabs--;
        }
        println(sb, tabs, "};");
        // Put properties into list, along with the parent's persisted properties
        // persistedPropertiesList = 
        //       new ArrayList<String>(Arrays.asList(persistedPropertiesArray));
        println(sb, tabs,
                String.format("%s = new %s<%s>(%s.asList(%s));", pplField, ArrayList.class.getSimpleName(),
                        String.class.getSimpleName(), Arrays.class.getSimpleName(), ppaField));
        if (SPObject.class.isAssignableFrom(visitedClass.getSuperclass())) {
            // persistedPropertiesList.addAll(super.getPersistedProperties());
            println(sb, tabs,
                    String.format("%s.addAll(super.%s());", pplField, GET_PERSISTED_PROPERTIES_METHOD_NAME));
        }

        // return persistedPropertiesList;
        println(sb, tabs, String.format("return %s;", pplField));
        tabs--;
        println(sb, tabs, "}");
        return sb.toString();
    }

    /**
     * Generates and returns source code for an
     * {@link SPPersisterHelper#findProperty(SPObject, String, SessionPersisterSuperConverter)}
     * method based on getter methods annotated with {@link Accessor} used in a
     * given {@link SPObject} class. The purpose of this findProperty method is
     * to allow a session {@link SPPersister} to get the value of a given
     * property in an {@link SPObject} and compare it with the expected value.
     * This helper method will be called by the session
     * {@link SPPersister#persistProperty(String, String, ca.sqlpower.dao.SPPersister.DataType, Object, Object)}
     * method.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param getters
     *            The {@link Map} of accessor method names to their property
     *            types, where each property can be persisted by an
     *            {@link SPPersister} into an {@link SPSession}.
     * @param accessorAdditionalInfo
     *            The {@link Multimap} of getter methods mapped to additional
     *            properties a session {@link SPPersister} requires to convert
     *            the getter's returned value from a complex to basic
     *            persistable type.
     * @param tabs
     *            The number of tab characters to use to indent this generated
     *            method block.
     * @return The source code for the generated findProperty method.
     * @see SPPersisterHelper#findProperty(SPObject, String,
     *      SessionPersisterSuperConverter)
     * @see SPPersister#persistProperty(String, String,
     *      ca.sqlpower.dao.SPPersister.DataType, Object, Object)
     */
    private String generateFindPropertyMethod(Class<? extends SPObject> visitedClass, Map<String, Class<?>> getters,
            Multimap<String, String> accessorAdditionalInfo, int tabs) {
        StringBuilder sb = new StringBuilder();
        final String genericObjectField = "o";
        final String objectField = "castedObject";
        final String propertyNameField = "propertyName";
        final String converterField = "converter";

        boolean firstIf = true;

        // findProperty method header.
        // public Object findProperty(
        //       SPObject o,
        //       String propertyName,
        //       SessionPersisterSuperConverter converter)
        //       throws SPPersistenceException {
        println(sb, tabs,
                String.format("public %s %s(%s %s, %s %s, %s %s) throws %s {", Object.class.getSimpleName(),
                        FIND_PROPERTY_METHOD_NAME, SPObject.class.getSimpleName(), genericObjectField,
                        String.class.getSimpleName(), propertyNameField,
                        SessionPersisterSuperConverter.class.getSimpleName(), converterField,
                        SPPersistenceException.class.getSimpleName()));
        tabs++;

        if (!getters.isEmpty()) {
            // If the SPObject class this persister helper handles is abstract,
            // use the type generic defined in the class header.
            // Otherwise, use the SPObject class directly.
            if (Modifier.isAbstract(visitedClass.getModifiers())) {
                // T castedObject = (T) o;
                println(sb, tabs, String.format("%s %s = (%s) %s;", TYPE_GENERIC_PARAMETER, objectField,
                        TYPE_GENERIC_PARAMETER, genericObjectField));
            } else {
                // <visitedClass> castedObject = (<visitedClass>) o;
                println(sb, tabs, String.format("%s %s = (%s) %s;", visitedClass.getSimpleName(), objectField,
                        visitedClass.getSimpleName(), genericObjectField));
            }

            // Search for the matching property name and return the value.
            for (Entry<String, Class<?>> e : getters.entrySet()) {
                String methodName = e.getKey();

                print(sb, tabs, "");

                if (!firstIf) {
                    niprint(sb, "} else ");
                }

                // if (propertyName.equals("<method to property name>") {
                niprintln(sb, String.format("if (%s.equals(\"%s\")) {", propertyNameField,
                        SPAnnotationProcessorUtils.convertMethodToProperty(methodName)));
                tabs++;

                // return converter.convertToBasicType(castedObject.<getter>());
                print(sb, tabs, String.format("return %s.%s(%s.%s()", converterField,
                        CONVERT_TO_BASIC_TYPE_METHOD_NAME, objectField, methodName));

                for (String additionalProperty : accessorAdditionalInfo.get(methodName)) {
                    niprint(sb, String.format(", %s.%s()", objectField, SPAnnotationProcessorUtils
                            .convertPropertyToAccessor(additionalProperty, visitedClass)));
                }

                niprintln(sb, ");");

                tabs--;
                firstIf = false;
            }

            if (!firstIf) {
                println(sb, tabs, "} else {");
                tabs++;
            }
        }

        if (SPObject.class.isAssignableFrom(visitedClass.getSuperclass())) {
            Class<?> superclass = visitedClass.getSuperclass();

            // return super.findProperty(o, propertyName, converter);
            println(sb, tabs, String.format("return super.%s(%s, %s, %s);", FIND_PROPERTY_METHOD_NAME,
                    genericObjectField, propertyNameField, converterField));
        } else {
            // Throw an SPPersistenceException if the property is not persistable or unrecognized.
            // throw new SPPersistenceException(
            //       castedObject.getUUID(),
            //       createSPPersistenceExceptionMessage(
            //             castedObject,
            //             propertyName));
            println(sb, tabs,
                    String.format("throw new %s(%s.%s(), %s(%s, %s));",
                            SPPersistenceException.class.getSimpleName(), objectField, GET_UUID_METHOD_NAME,
                            CREATE_EXCEPTION_MESSAGE_METHOD_NAME, objectField, propertyNameField));
        }

        if (!firstIf) {
            tabs--;
            println(sb, tabs, "}");
        }

        tabs--;
        println(sb, tabs, "}");

        return sb.toString();
    }

    /**
     * Generates and returns source code for a persistObject method based on the
     * constructor annotated with {@link Constructor} with its constructor
     * parameters annotated with {@link ConstructorParameter}, as well as the
     * persistable properties with their getters/setters annotated with
     * {@link Accessor} and {@link Mutator}. This generated method should
     * persist the entire state of an {@link SPObject} to an {@link SPPersister}
     * and should be called from a workspace persister {@link SPListener}. This
     * means that the union of the constructor parameter properties and
     * persistable properties must be persisted.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param constructorParameters
     *            The {@link Map} of property names to constructor parameters,
     *            where values passed into the constructor will set those
     *            respective properties. The order of this map must absolutely
     *            be guaranteed to follow the order of the constructor
     *            parameters. These properties may or may not be persistable, so
     *            it is entirely possible that not all of the properties in this
     *            map exists in the given {@link Set} of persistable properties.
     * @param accessors
     *            The {@link Map} of accessor method names to their property
     *            types which should be persisted into an {@link SPPersister} by
     *            a workspace persister {@link SPListener}.
     * @param propertiesToPersistOnlyIfNonNull
     *            The {@link Set} of persistable properties that can only be
     *            persisted if its value is not null.
     * @param tabs
     *            The number of tab characters to use to indent this generated
     *            method block.
     * @return The source code for the generated persistObject method.
     */
    private String generatePersistObjectMethod(Class<? extends SPObject> visitedClass,
            List<ConstructorParameterObject> constructorParameters, Map<String, Class<?>> accessors,
            Set<String> propertiesToPersistOnlyIfNonNull, int tabs) {
        StringBuilder sb = new StringBuilder();
        final String genericObjectField = "o";
        final String objectField = "castedObject";
        final String indexField = "index";
        final String persisterField = "persister";
        final String converterField = "converter";
        final String uuidField = "uuid";
        final String parentUUIDField = "parentUUID";
        final String exceptionField = "e";

        //Properties already processed by the constructor, to be skipped
        //by the helper persisters that are parent classes to this object class.
        final String preProcessedProps = "preProcessedProperties";

        // persistObject method header.
        // public void persistObject(SPObject o, int index, SPPersister persister, SessionPersisterSuperConverter converter) throws SPPersistenceException {
        println(sb, tabs,
                String.format("public void %s(%s %s, int %s, %s %s, %s %s) throws %s {", PERSIST_OBJECT_METHOD_NAME,
                        SPObject.class.getSimpleName(), genericObjectField, indexField,
                        SPPersister.class.getSimpleName(), persisterField,
                        SessionPersisterSuperConverter.class.getSimpleName(), converterField,
                        SPPersistenceException.class.getSimpleName()));
        tabs++;

        // If the SPObject class this persister helper handles is abstract,
        // use the type generic defined in the class header.
        // Otherwise, use the SPObject class directly.
        if (Modifier.isAbstract(visitedClass.getModifiers())) {
            // T castedObject = (T) o;
            println(sb, tabs, String.format("%s %s = (%s) %s;", TYPE_GENERIC_PARAMETER, objectField,
                    TYPE_GENERIC_PARAMETER, genericObjectField));
        } else {
            // <visitedClass> castedObject = (<visitedClass>) o;
            println(sb, tabs, String.format("%s %s = (%s) %s;", visitedClass.getSimpleName(), objectField,
                    visitedClass.getSimpleName(), genericObjectField));
        }

        // final String uuid = castedObject.getUUID();
        println(sb, tabs, String.format("final %s %s = %s.%s();", String.class.getSimpleName(), uuidField,
                objectField, GET_UUID_METHOD_NAME));

        // String parentUUID = null;
        println(sb, tabs, String.format("%s %s = null;", String.class.getSimpleName(), parentUUIDField));

        // if (castedObject.getParent() != null) {
        println(sb, tabs, String.format("if (%s.%s() != null) {", objectField, GET_PARENT_METHOD_NAME));
        tabs++;

        // parentUUID = castedObject.getParent().getUUID();
        println(sb, tabs, String.format("%s = %s.%s().%s();", parentUUIDField, objectField, GET_PARENT_METHOD_NAME,
                GET_UUID_METHOD_NAME));

        tabs--;
        println(sb, tabs, "}\n");

        // Persist the object.
        // persister.persistObject(parentUUID, "<visitedClass name>", uuid, index);" 
        println(sb, tabs, String.format("%s.%s(%s, \"%s\", %s, %s);", persisterField, PERSIST_OBJECT_METHOD_NAME,
                parentUUIDField, visitedClass.getName(), uuidField, indexField));

        //TODO pass in the actual exception types on any accessors
        //then replace this blanket try/catch with specifics for any accessor
        //that throws an exception.

        // List<String> preProcessedProperties = new ArrayList<String>();
        println(sb, tabs,
                String.format("%s<%s> %s = new %s<%s>();", List.class.getSimpleName(), String.class.getSimpleName(),
                        preProcessedProps, ArrayList.class.getSimpleName(), String.class.getSimpleName()));
        importedClassNames.add(ArrayList.class.getName());
        println(sb, tabs, "try {");
        tabs++;
        if (constructorParameters.isEmpty()) {
            println(sb, tabs, "// No constructor arguments");
        } else {
            println(sb, tabs, "// Constructor arguments");

            final String dataTypeField = "dataType";

            // DataType dataType;
            println(sb, tabs, String.format("%s %s;", DataType.class.getSimpleName(), dataTypeField));
            // Persist all of its constructor argument properties.
            for (ConstructorParameterObject cpo : constructorParameters) {
                //XXX Should this only be properties?
                if (ParameterType.PROPERTY.equals(cpo.getProperty())
                        || ParameterType.CHILD.equals(cpo.getProperty())) {

                    String getPersistedProperty = objectField + "."
                            + SPAnnotationProcessorUtils.convertPropertyToAccessor(cpo.getName(), visitedClass)
                            + "()";
                    if (cpo.getType() == Object.class) {
                        // if (castedObject.<getter>() == null) {
                        println(sb, tabs, String.format("if (%s == null) {", getPersistedProperty));
                        tabs++;
                        // dataType = PersisterUtils.getDataType(null);
                        println(sb, tabs, String.format("%s = %s.getDataType(null);", dataTypeField,
                                PersisterUtils.class.getSimpleName()));
                        tabs--;
                        println(sb, tabs, "} else {");
                        tabs++;
                        // dataType = PersisterUtils.getDataType(castedObject.<getter>().getClass());
                        println(sb, tabs, String.format("%s = %s.getDataType(%s.getClass());", dataTypeField,
                                PersisterUtils.class.getSimpleName(), getPersistedProperty));
                        tabs--;
                        println(sb, tabs, "}");
                        importedClassNames.add(PersisterUtils.class.getName());
                    } else {
                        // dataType = DataType.<type>;
                        println(sb, tabs, String.format("%s = %s.%s;", dataTypeField,
                                DataType.class.getSimpleName(), PersisterUtils.getDataType(cpo.getType()).name()));
                    }

                    // persister.persistProperty(uuid, "<property>", dataType, converter, convertToBasicType(castedObject.<getter>()));
                    println(sb, tabs, String.format("%s.%s(%s, \"%s\", %s, %s.%s(%s.%s()));", persisterField,
                            PERSIST_PROPERTY_METHOD_NAME, uuidField, cpo.getName(), //XXX we should convert this name as the constructor parameter name may be different than the property name defined by the accessor.
                            dataTypeField, converterField, CONVERT_TO_BASIC_TYPE_METHOD_NAME, objectField,
                            SPAnnotationProcessorUtils.convertPropertyToAccessor(cpo.getName(), visitedClass)));

                    // preProcessedProperties.add("<propertyName>");
                    println(sb, tabs, String.format("%s.add(\"%s\");", preProcessedProps, cpo.getName()));
                    importedClassNames.add(DataType.class.getName());
                }
            }
        }
        niprintln(sb, "");

        // persistObjectProperties(o, persister, converter, preProcessedProperties);
        println(sb, tabs, String.format("%s(%s, %s, %s, %s);", PERSIST_OBJECT_PROPERTIES_METHOD_NAME,
                genericObjectField, persisterField, converterField, preProcessedProps));

        tabs--;
        // } catch (Exception e) {
        println(sb, tabs, String.format("} catch (%s %s) {", Exception.class.getSimpleName(), exceptionField));
        tabs++;

        // throw new SPPersistenceException(uuid, e);
        println(sb, tabs, String.format("throw new %s(%s, %s);", SPPersistenceException.class.getSimpleName(),
                uuidField, exceptionField));
        tabs--;
        println(sb, tabs, "}");

        tabs--;
        println(sb, tabs, "}");

        return sb.toString();
    }

    /**
     * Generates and returns source code for persisting the remaining state not
     * done by {@link #generatePersistObjectMethod(Class, List, Map, Set, int)}.
     * This is for classes to allow properties that are not defined in a
     * sub-class to be persisted.
     * <p>
     * In the future we may want to have the parent classes persist properties
     * first and children to persist properties later. We can pool the property
     * changes in {@link PersistedSPOProperty} objects and persist them at the
     * end of the method call.
     * 
     * @param visitedClass
     *            The {@link SPObject} class that is being visited by the
     *            annotation processor.
     * @param accessors
     *            The {@link Map} of accessor method names to their property
     *            types which should be persisted into an {@link SPPersister} by
     *            a workspace persister {@link SPListener}.
     * @param propertiesToPersistOnlyIfNonNull
     *            The {@link Set} of persistable properties that can only be
     *            persisted if its value is not null.
     * @param tabs
     *            The number of tab characters to use to indent this generated
     *            method block.
     * @return The source code for the generated persistObject method.
     */
    private String generatePersistObjectMethodHelper(Class<? extends SPObject> visitedClass,
            Map<String, Class<?>> accessors, Map<String, Class<?>> mutators,
            Set<String> propertiesToPersistOnlyIfNonNull, int tabs) {
        StringBuilder sb = new StringBuilder();
        final String genericObjectField = "o";
        final String objectField = "castedObject";
        final String persisterField = "persister";
        final String converterField = "converter";
        final String uuidField = "uuid";
        final String exceptionField = "e";

        //These are the properties on the sub-class helper calling this abstract
        //helper that have already been persisted by the current persistObject
        //call. These properties do not have to be persisted again.
        final String preProcessedPropField = "preProcessedProps";

        final String staticPreProcessedPropField = "staticPreProcessedProps";

        // persistObject method header.
        // public void persistObjectProperties(
        //       SPObject o,
        //       SPPersister persister,
        //       SessionPersisterSuperConverter converter,
        //       List<String> staticPreProcessedProps)
        //       throws SPPersistenceException {
        println(sb, tabs,
                String.format("public void %s(%s %s, %s %s, %s %s, %s<%s> %s) throws %s {",
                        PERSIST_OBJECT_PROPERTIES_METHOD_NAME, SPObject.class.getSimpleName(), genericObjectField,
                        SPPersister.class.getSimpleName(), persisterField,
                        SessionPersisterSuperConverter.class.getSimpleName(), converterField,
                        List.class.getSimpleName(), String.class.getSimpleName(), staticPreProcessedPropField,
                        SPPersistenceException.class.getSimpleName()));
        tabs++;
        // final List<String> preProcessedProperties = new ArrayList<String>(staticPreProcessedProps);
        println(sb, tabs,
                String.format("final %s<%s> %s = new %s<%s>(%s);", List.class.getSimpleName(),
                        String.class.getSimpleName(), preProcessedPropField, ArrayList.class.getSimpleName(),
                        String.class.getSimpleName(), staticPreProcessedPropField));

        if (!accessors.isEmpty()) {
            boolean lastEntryInIfBlock = false;
            boolean variablesInitialized = false;

            Set<String> getterPropertyNames = new HashSet<String>();
            for (Entry<String, Class<?>> e : mutators.entrySet()) {
                getterPropertyNames.add(SPAnnotationProcessorUtils.convertMethodToProperty(e.getKey()));
            }
            // Persist all of its persistable properties.
            for (Entry<String, Class<?>> e : accessors.entrySet()) {
                String propertyName = SPAnnotationProcessorUtils.convertMethodToProperty(e.getKey());
                if (!getterPropertyNames.contains(propertyName))
                    continue;

                if (!variablesInitialized) {
                    // final String uuid = o.getUUID();
                    println(sb, tabs, String.format("final %s %s = %s.%s();\n", String.class.getSimpleName(),
                            uuidField, genericObjectField, GET_UUID_METHOD_NAME));

                    // If the SPObject class this persister helper handles is abstract,
                    // use the type generic defined in the class header.
                    // Otherwise, use the SPObject class directly.
                    if (Modifier.isAbstract(visitedClass.getModifiers())) {
                        // T castedObject = (T) o;
                        println(sb, tabs, String.format("%s %s = (%s) %s;", TYPE_GENERIC_PARAMETER, objectField,
                                TYPE_GENERIC_PARAMETER, genericObjectField));
                    } else {
                        // <visitedClass> castedObject = (<visitedClass>) o;
                        println(sb, tabs, String.format("%s %s = (%s) %s;", visitedClass.getSimpleName(),
                                objectField, visitedClass.getSimpleName(), genericObjectField));
                    }

                    // try {
                    println(sb, tabs, "try {");
                    tabs++;
                    variablesInitialized = true;
                }

                // Persist the property only if it has not been persisted yet
                // and (if required) persist if the value is not null.
                //
                // if (preProcessedProperties.contains("<property>")) {
                println(sb, tabs,
                        String.format("if (!%s.contains(\"%s\")) {", preProcessedPropField, propertyName));
                tabs++;
                boolean persistOnlyIfNonNull = propertiesToPersistOnlyIfNonNull.contains(propertyName);
                String propertyField = objectField + "." + e.getKey() + "()";

                if (lastEntryInIfBlock) {
                    niprintln(sb, "");
                }

                if (persistOnlyIfNonNull) {
                    // <getter type> <property> = castedObject.<getter>();
                    println(sb, tabs, String.format("%s %s = %s.%s();", e.getValue().getSimpleName(), propertyName,
                            propertyField));
                    propertyField = propertyName;

                    // if (castedObject.<getter>() != null) {
                    println(sb, tabs, String.format("if (%s != null) {", propertyField));
                    tabs++;
                }

                final String dataTypeField = "dataType";
                // DataType dataType;
                println(sb, tabs, String.format("%s %s;", DataType.class.getSimpleName(), dataTypeField));

                String getPersistedProperty = objectField + "." + e.getKey() + "()";
                if (e.getValue() == Object.class) {
                    // if (castedObject.<getter>() == null) {
                    println(sb, tabs, String.format("if (%s == null) {", getPersistedProperty));
                    tabs++;
                    // dataType = PersisterUtils.getDataType(null);
                    println(sb, tabs, String.format("%s = %s.getDataType(null);", dataTypeField,
                            PersisterUtils.class.getSimpleName()));
                    tabs--;
                    println(sb, tabs, "} else {");
                    tabs++;

                    // dataType = PersisterUtils.getDataType(castedObject.<getter>().getClass());
                    println(sb, tabs, String.format("%s = %s.getDataType(%s.getClass());", dataTypeField,
                            PersisterUtils.class.getSimpleName(), getPersistedProperty));
                    tabs--;
                    println(sb, tabs, "}");
                    importedClassNames.add(PersisterUtils.class.getName());
                } else {
                    // dataType = DataType.<type>;
                    println(sb, tabs, String.format("%s = %s.%s;", dataTypeField, DataType.class.getSimpleName(),
                            PersisterUtils.getDataType(e.getValue()).name()));
                }
                // persister.persistProperty(uuid, "<property>", dataType, converter.convertToBasicType(castedObject.<getter>()));
                println(sb, tabs,
                        String.format("%s.%s(%s, \"%s\", %s, %s.%s(%s));", persisterField,
                                PERSIST_PROPERTY_METHOD_NAME, uuidField, propertyName, dataTypeField,
                                converterField, CONVERT_TO_BASIC_TYPE_METHOD_NAME, getPersistedProperty));
                // preProcessedProperties.add("<property>");
                println(sb, tabs, String.format("%s.add(\"%s\");", preProcessedPropField, propertyName));
                importedClassNames.add(DataType.class.getName());

                if (persistOnlyIfNonNull) {
                    tabs--;
                    println(sb, tabs, "}");
                    lastEntryInIfBlock = true;
                } else {
                    lastEntryInIfBlock = false;
                }
                tabs--;
                println(sb, tabs, "}");
            }

            if (variablesInitialized) {
                tabs--;
                // } catch (Exception e) {
                println(sb, tabs,
                        String.format("} catch (%s %s) {", Exception.class.getSimpleName(), exceptionField));
                tabs++;

                // throw new SPPersistenceException(uuid, e);
                println(sb, tabs, String.format("throw new %s(%s, %s);",
                        SPPersistenceException.class.getSimpleName(), uuidField, exceptionField));
                tabs--;
                println(sb, tabs, "}");
            }
        }

        if (SPObject.class.isAssignableFrom(visitedClass.getSuperclass())) {
            Class<?> superclass = visitedClass.getSuperclass();

            // super.persistObjectProperties(o, persister, converter, preProcessedProperties);
            println(sb, tabs, String.format("super.%s(%s, %s, %s, %s);", PERSIST_OBJECT_PROPERTIES_METHOD_NAME,
                    genericObjectField, persisterField, converterField, preProcessedPropField));
        }

        tabs--;
        println(sb, tabs, "}");

        return sb.toString();
    }

    //-------------- helper methods for dealing with string buffer, there may be a class that already does this

    /**
     * Appends a number of tab characters, a given {@link String}, and a new
     * line character to a {@link StringBuilder}. This method generates a line
     * of Java code.
     * 
     * @param sb
     *            The {@link StringBuilder} to append to.
     * @param tabs
     *            The number of tab characters to indent.
     * @param s
     *            The {@link String} to append.
     */
    private void println(StringBuilder sb, int tabs, String s) {
        print(sb, tabs, s + "\n");
    }

    /**
     * Appends a number of tab characters, and a given {@link String} to a
     * {@link StringBuilder}. This method generates a line or a portion of a
     * line of Java code without adding a new line character at the end.
     * 
     * @param sb
     *            The {@link StringBuilder} to append to.
     * @param tabs
     *            The number of tab characters to indent.
     * @param s
     *            The {@link String} to append.
     */
    private void print(StringBuilder sb, int tabs, String s) {
        sb.append(indent(tabs));
        sb.append(s);
    }

    /**
     * Appends a given {@link String}, and a new line character to a
     * {@link StringBuilder}. This method generates a portion of a line of Java
     * code without indenting.
     * 
     * @param sb
     *            The {@link StringBuilder} to append to.
     * @param s
     *            The {@link String} to append.
     */
    private void niprintln(StringBuilder sb, String s) {
        niprint(sb, s + "\n");
    }

    /**
     * Appends a given {@link String} to a {@link StringBuilder}. This method
     * generates a portion of a line of Java code without indenting or adding a
     * new line character at the end.
     * 
     * @param sb
     *            The {@link StringBuilder} to append to.
     * @param s
     *            The {@link String} to append.
     */
    private void niprint(StringBuilder sb, String s) {
        sb.append(s);
    }

    //-------------- end helper methods
}