/*
* 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.util.ArrayList;
import java.util.Arrays;
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 ca.sqlpower.dao.SPPersister;
import ca.sqlpower.dao.helper.SPPersisterHelper;
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.google.common.collect.Multimaps;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.AnnotationTypeElementDeclaration;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.ConstructorDeclaration;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.declaration.EnumConstantDeclaration;
import com.sun.mirror.declaration.EnumDeclaration;
import com.sun.mirror.declaration.ExecutableDeclaration;
import com.sun.mirror.declaration.FieldDeclaration;
import com.sun.mirror.declaration.InterfaceDeclaration;
import com.sun.mirror.declaration.MemberDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.PackageDeclaration;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.declaration.TypeParameterDeclaration;
import com.sun.mirror.type.ClassType;
import com.sun.mirror.type.InterfaceType;
import com.sun.mirror.type.PrimitiveType;
import com.sun.mirror.type.ReferenceType;
import com.sun.mirror.type.TypeMirror;
import com.sun.mirror.util.DeclarationVisitor;
/**
* This {@link DeclarationVisitor} is used to visit {@link SPObject} classes
* annotated with {@link Persistable}. For each constructor and method, this
* visitor looks for the {@link Constructor}, {@link ConstructorParameter},
* {@link Accessor} and {@link Mutator} annotations and keeps track of data
* required by the {@link SPAnnotationProcessor} to generate
* {@link SPPersisterHelper} classes.
*/
public class SPClassVisitor implements DeclarationVisitor {
/**
* @see #isValid()
*/
private boolean valid = true;
/**
* @see #getVisitedClass()
*/
private Class<? extends SPObject> visitedClass;
/**
* @see #getConstructorParameters()
*/
private List<ConstructorParameterObject> constructorParameters = new ArrayList<ConstructorParameterObject>();
/**
* @see #getPropertiesToAccess()
*/
private Map<String, Class<?>> propertiesToAccess = new HashMap<String, Class<?>>();
/**
* @see #getAccessorAdditionalInfo()
*/
private Multimap<String, String> accessorAdditionalInfo = LinkedHashMultimap.create();
/**
* @see #getPropertiesToMutate()
*/
private Map<String, Class<?>> propertiesToMutate = new HashMap<String, Class<?>>();
/**
* @see #getMutatorExtraParameters()
*/
private Multimap<String, MutatorParameterObject> mutatorExtraParameters = LinkedHashMultimap.create();
/**
* @see #getMutatorThrownTypes()
*/
private Multimap<String, Class<? extends Exception>> mutatorThrownTypes = HashMultimap.create();
/**
* @see #getConstructorImports()
*/
private Set<String> constructorImports = new HashSet<String>();
/**
* @see #getMutatorImports()
*/
private Multimap<String, String> mutatorImports = HashMultimap.create();
/**
* @see #propertiesToPersistOnlyIfNonNull
*/
private Set<String> propertiesToPersistOnlyIfNonNull = new HashSet<String>();
/**
* If true then an annotated constructor has been recently found for the current
* class being visited. We must only have one constructor per class.
*/
private boolean constructorFound = false;
/**
* The type of object this visitor will walk through. If the type has inner classes
* they will not be considered.
*/
private final TypeDeclaration typeDecl;
public SPClassVisitor(TypeDeclaration typeDecl) {
this.typeDecl = typeDecl;
}
/**
* Returns whether the visited class along with all its annotated elements is valid.
*/
public boolean isValid() {
return valid;
}
/**
* Returns the {@link SPObject} class this {@link DeclarationVisitor} is
* visiting to parse annotations.
*/
public Class<? extends SPObject> getVisitedClass() {
return visitedClass;
}
/**
* Returns the {@link List} of constructor arguments that are required to
* create a new {@link SPObject} of type {@link #visitedClass}. The order of
* this list is guaranteed.
*/
public List<ConstructorParameterObject> getConstructorParameters() {
return Collections.unmodifiableList(constructorParameters);
}
/**
* Returns the {@link Map} of JavaBean getter method names that access
* persistable properties, mapped to their property types.
*/
public Map<String, Class<?>> getPropertiesToAccess() {
return Collections.unmodifiableMap(propertiesToAccess);
}
/**
* Returns the {@link Multimap} of JavaBean getter method names that access
* persistable properties, mapped to additional property names that the
* session {@link SPPersister} requires to convert the accessor's returned
* value into a basic persistable type. The order of this {@link Multimap}
* is guaranteed.
*
* @see Accessor#additionalInfo()
*/
public Multimap<String, String> getAccessorAdditionalInfo() {
return Multimaps.unmodifiableMultimap(accessorAdditionalInfo);
}
/**
* Returns the {@link Map} of JavaBean setter method names that mutate
* persistable properties, mapped to their property types.
*/
public Map<String, Class<?>> getPropertiesToMutate() {
return Collections.unmodifiableMap(propertiesToMutate);
}
/**
* Returns the {@link Multimap} of JavaBean setter method names that mutate
* persistable properties, mapped to {@link MutatorParameterObject}s that
* contain values for an {@link SPPersister} to use. The order of this
* {@link Multimap} is guaranteed.
*/
public Multimap<String, MutatorParameterObject> getMutatorExtraParameters() {
return Multimaps.unmodifiableMultimap(mutatorExtraParameters);
}
/**
* Returns the {@link Multimap} of JavaBean setter method names that mutate
* persistable properties, mapped to the method's thrown types.
*/
public Multimap<String, Class<? extends Exception>> getMutatorThrownTypes() {
return Multimaps.unmodifiableMultimap(mutatorThrownTypes);
}
/**
* Returns the {@link Set} of imports that are required for an
* {@link SPPersisterHelper} to use that deals with {@link SPObject}s of
* type {@link #visitedClass} which includes dependencies of the
* {@link ConstructorParameter} annotated constructor parameters.
*/
public Set<String> getConstructorImports() {
return Collections.unmodifiableSet(constructorImports);
}
/**
* Returns the {@link Multimap} of {@link Mutator} annotated setter methods to
* required imports needed to generate {@link SPPersisterHelper}s that deal
* with {@link SPObject}s of type {@link #visitedClass}, which include
* thrown exception types.
*/
public Multimap<String, String> getMutatorImports() {
return mutatorImports;
}
/**
* Returns the {@link Set} of persistable properties that can only be
* persisted if its value is not null.
*/
public Set<String> getPropertiesToPersistOnlyIfNonNull() {
return Collections.unmodifiableSet(propertiesToPersistOnlyIfNonNull);
}
/**
* Stores the class reference of a {@link Persistable} {@link SPObject} for
* use in annotation processing in the {@link SPAnnotationProcessor}. The
* processor takes this information to generate {@link SPPersisterHelper}s.
*
* @param d
* The {@link ClassDeclaration} of the class to visit.
*/
public void visitClassDeclaration(ClassDeclaration d) {
for (TypeDeclaration nestedType : typeDecl.getNestedTypes()) {
if (nestedType.getQualifiedName().equals(d.getQualifiedName())) {
return;
}
}
if (d.getAnnotation(Persistable.class) != null) {
if (d.getAnnotation(Persistable.class).isTransient()) {
valid = false;
return;
}
try {
String qualifiedName =
SPAnnotationProcessorUtils.convertTypeDeclarationToQualifiedName(d);
visitedClass = (Class<? extends SPObject>) Class.forName(qualifiedName);
if (java.lang.reflect.Modifier.isPrivate(visitedClass.getModifiers())) {
valid = false;
}
} catch (ClassNotFoundException e) {
valid = false;
e.printStackTrace();
}
}
}
/**
* Stores information about constructors annotated with {@link Constructor},
* particularly with the {@link ConstructorParameter} annotated parameters
* and their required imports. The {@link SPAnnotationProcessor} takes this
* information and generates
* {@link SPPersisterHelper#commitObject(ca.sqlpower.dao.PersistedSPObject, Multimap, List, ca.sqlpower.dao.helper.SPPersisterHelperFactory)}
* and
* {@link SPPersisterHelper#persistObject(SPObject, int, SPPersister, ca.sqlpower.dao.session.SessionPersisterSuperConverter)}
* methods.
*
* @param d
* The {@link ConstructorDeclaration} of the constructor to
* visit.
*/
public void visitConstructorDeclaration(ConstructorDeclaration d) {
if (!constructorFound && d.getAnnotation(Constructor.class) != null
&& d.getSimpleName().equals(typeDecl.getSimpleName())) {
for (ParameterDeclaration pd : d.getParameters()) {
ConstructorParameter cp = pd.getAnnotation(ConstructorParameter.class);
if (cp != null) {
try {
TypeMirror type = pd.getType();
Class<?> c = SPAnnotationProcessorUtils.convertTypeMirrorToClass(type);
ParameterType property = cp.parameterType();
String name;
if (property.equals(ParameterType.PROPERTY)) {
name = cp.propertyName();
} else {
name = pd.getSimpleName();
}
if (type instanceof PrimitiveType) {
constructorParameters.add(
new ConstructorParameterObject(property, c, name));
} else if (type instanceof ClassType || type instanceof InterfaceType) {
constructorParameters.add(
new ConstructorParameterObject(property, c, name));
constructorImports.add(c.getName());
}
} catch (ClassNotFoundException e) {
valid = false;
e.printStackTrace();
}
}
}
constructorFound = true;
}
}
/**
* Stores information about getter and setter methods annotated with
* {@link Accessor} and {@link Mutator}. This includes thrown exceptions,
* setter parameters annotated with {@link MutatorParameter}, and required
* imports. The {@link SPAnnotationProcessor} takes this information and
* generates
* {@link SPPersisterHelper#commitProperty(SPObject, String, Object, ca.sqlpower.dao.session.SessionPersisterSuperConverter)}
* and
* {@link SPPersisterHelper#findProperty(SPObject, String, ca.sqlpower.dao.session.SessionPersisterSuperConverter)}
* methods.
*
* @param d
* The {@link MethodDeclaration} of the method to visit.
*/
public void visitMethodDeclaration(MethodDeclaration d) {
Accessor accessorAnnotation = d.getAnnotation(Accessor.class);
Mutator mutatorAnnotation = d.getAnnotation(Mutator.class);
Transient transientAnnotation = d.getAnnotation(Transient.class);
TypeMirror type = null;
if (!d.getDeclaringType().equals(typeDecl)) return;
if (accessorAnnotation != null && transientAnnotation == null) {
type = d.getReturnType();
} else if (mutatorAnnotation != null && transientAnnotation == null) {
type = d.getParameters().iterator().next().getType();
} else {
return;
}
String methodName = d.getSimpleName();
Class<?> c = null;
try {
c = SPAnnotationProcessorUtils.convertTypeMirrorToClass(type);
if (!propertiesToAccess.containsKey(methodName) && accessorAnnotation != null) {
propertiesToAccess.put(methodName, c);
if (accessorAnnotation.persistOnlyIfNonNull()) {
propertiesToPersistOnlyIfNonNull.add(
SPAnnotationProcessorUtils.convertMethodToProperty(methodName));
}
accessorAdditionalInfo.putAll(
methodName, Arrays.asList(accessorAnnotation.additionalInfo()));
} else if (!propertiesToMutate.containsKey(methodName) && mutatorAnnotation != null) {
for (ReferenceType refType : d.getThrownTypes()) {
Class<? extends Exception> thrownType =
(Class<? extends Exception>) Class.forName(refType.toString());
mutatorThrownTypes.put(methodName, thrownType);
mutatorImports.put(methodName, thrownType.getName());
}
propertiesToMutate.put(methodName, c);
mutatorImports.put(methodName, c.getName());
for (ParameterDeclaration pd : d.getParameters()) {
MutatorParameter mutatorParameterAnnotation =
pd.getAnnotation(MutatorParameter.class);
if (mutatorParameterAnnotation != null) {
Class<?> extraParamType =
SPAnnotationProcessorUtils.convertTypeMirrorToClass(pd.getType());
mutatorExtraParameters.put(methodName,
new MutatorParameterObject(
extraParamType,
pd.getSimpleName(),
mutatorParameterAnnotation.value()));
mutatorImports.put(methodName, extraParamType.getName());
}
}
}
} catch (ClassNotFoundException e) {
valid = false;
e.printStackTrace();
}
}
public void visitAnnotationTypeDeclaration(AnnotationTypeDeclaration d) {
// no-op
}
public void visitAnnotationTypeElementDeclaration(AnnotationTypeElementDeclaration d) {
// no-op
}
public void visitDeclaration(Declaration d) {
// no-op
}
public void visitEnumConstantDeclaration(EnumConstantDeclaration d) {
// no-op
}
public void visitEnumDeclaration(EnumDeclaration d) {
// no-op
}
public void visitExecutableDeclaration(ExecutableDeclaration d) {
// no-op
}
public void visitFieldDeclaration(FieldDeclaration d) {
// no-op
}
public void visitInterfaceDeclaration(InterfaceDeclaration d) {
// no-op
}
public void visitMemberDeclaration(MemberDeclaration d) {
// no-op
}
public void visitPackageDeclaration(PackageDeclaration d) {
// no-op
}
public void visitParameterDeclaration(ParameterDeclaration d) {
// no-op
}
public void visitTypeDeclaration(TypeDeclaration d) {
// no-op
}
public void visitTypeParameterDeclaration(TypeParameterDeclaration d) {
// no-op
}
}