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

Java tutorial

Introduction

Here is the source code for ca.sqlpower.object.annotation.SPClassVisitor.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.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      
    }

}