/*
* 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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
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;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.apt.Filer;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.util.DeclarationVisitors;
/**
* This {@link AnnotationProcessor} processes the annotations in
* {@link SPObject}s to generate {@link SPPersisterHelper} classes to be used in
* a session {@link SPPersister}.
*/
public class SPAnnotationProcessor implements AnnotationProcessor {
/**
* 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 final AnnotationProcessorEnvironment 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";
/**
* Creates a 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.
*/
public SPAnnotationProcessor(AnnotationProcessorEnvironment environment) {
this.environment = environment;
}
public void process() {
Map<Class<? extends SPObject>, SPClassVisitor> visitors = new HashMap<Class<? extends SPObject>, SPClassVisitor>();
for (TypeDeclaration typeDecl : environment.getTypeDeclarations()) {
SPClassVisitor visitor = new SPClassVisitor(typeDecl);
typeDecl.accept(DeclarationVisitors.getDeclarationScanner(DeclarationVisitors.NO_OP, visitor));
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);
}
}
}
/**
* 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 = f.createSourceFile(helperPackage + "." + simpleClassName);
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 = f.createSourceFile(helperPackage + "." + simpleClassName);
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
}