edu.buffalo.cse.green.relationships.RelationshipGenerator.java Source code

Java tutorial

Introduction

Here is the source code for edu.buffalo.cse.green.relationships.RelationshipGenerator.java

Source

/* This file is part of Green.
 *
 * Copyright (C) 2005 The Research Foundation of State University of New York
 * All Rights Under Copyright Reserved, The Research Foundation of S.U.N.Y.
 * 
 * Green is free software, licensed under the terms of the Eclipse
 * Public License, version 1.0.  The license is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */

package edu.buffalo.cse.green.relationships;

import static org.eclipse.jdt.core.dom.ASTNode.VARIABLE_DECLARATION_STATEMENT;
import static org.eclipse.jdt.core.dom.ParameterizedType.TYPE_ARGUMENTS_PROPERTY;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTMatcher;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.text.edits.TextEdit;

import edu.buffalo.cse.green.GreenException;
import edu.buffalo.cse.green.PlugIn;
import edu.buffalo.cse.green.dialogs.wizards.ChooseTypeWizard;
import edu.buffalo.cse.green.editor.model.RelationshipModel;
import edu.buffalo.cse.green.util.IModifiableBuffer;

/**
 * Contains code common to all relationship generators
 * 
 * @author bcmartin
 */
public abstract class RelationshipGenerator extends RelationshipVisitor {
    private IType _sourceType;

    private IType _actualTargetType;

    private IType _declaredTargetType;

    protected int _cardinality = 1;

    public static final String LIST = "java.util.List";
    public static final String ARRAYLIST = "java.util.ArrayList";

    /**
     * Adds the desired import, if necessary.
     * 
     * @param type - The element to import.
     * @return false if the element cannot be imported because the element's
     * simple name is already used; true otherwise.
     */
    protected boolean addImport(IType type) {
        if (getCurrentType().getParent().equals(type.getParent())) {
            return true;
        }

        // import not necessary if both are children of CUs within the same
        // package
        if (getSourceType().getCompilationUnit().equals(getSourceType().getParent())
                && getTargetType().getOpenable().equals(getTargetType().getParent())
                && getSourceType().getPackageFragment().equals(getTargetType().getPackageFragment())) {
            return true;
        }

        return addImport(createQualifiedName(type.getFullyQualifiedName().replace('$', '.')));
    }

    /**
     * Adds the desired import, if possible.
     * 
     * @param qualifiedName - The desired <code>QualifiedName</code>.
     * @return false if the element cannot be imported because the element's
     * simple name is already used; true otherwise.
     */
    private boolean addImport(QualifiedName qualifiedName) {
        List<ImportDeclaration> imports = (AbstractList<ImportDeclaration>) getCompilationUnit().imports();

        // ensure the import is necessary and non-conflicting
        for (ImportDeclaration declaration : imports) {
            // do not contend with on-demand imports
            if (declaration.isOnDemand()) {
                continue;
            }

            // see if the import has already been added
            if (new ASTMatcher().match((QualifiedName) declaration.getName(), qualifiedName)) {
                return true;
            }

            /* see if the element referred to by the declaration has the same
             * simple name as the import we are trying to create (problem)
             */

            if (declaration.resolveBinding() != null) { // not recently added
                String elementName = declaration.resolveBinding().getJavaElement().getElementName();

                if (elementName.equals(qualifiedName.getName().toString())) {
                    return false;
                }
            }
        }

        // create the import declaration
        ImportDeclaration i = getAST().newImportDeclaration();
        i.setName(qualifiedName);

        // add the import declaration
        imports.add(i);

        return true;
    }

    /**
     * @param fullyQualifiedString - The String.
     * @return A qualified name representing the given fully qualified String. 
     */
    protected QualifiedName createQualifiedName(String fullyQualifiedString) {
        AST ast = getAST();

        if (fullyQualifiedString.indexOf(".") == -1) {
            GreenException.illegalOperation("String must be fully qualified");
        }

        int prevIndex = fullyQualifiedString.indexOf(".");
        String qual = fullyQualifiedString.substring(0, prevIndex);
        Name qualifier = ast.newSimpleName(qual);

        while (true) {
            qual = fullyQualifiedString.substring(prevIndex + 1);
            int index = qual.indexOf(".");

            if (index == -1) {
                break;
            }

            index += prevIndex;
            qual = fullyQualifiedString.substring(prevIndex + 1, index + 1);
            qualifier = ast.newQualifiedName(qualifier, ast.newSimpleName(qual));
            prevIndex = index + 1;
        }

        return ast.newQualifiedName(qualifier, ast.newSimpleName(qual));
    }

    /**
     * @see edu.buffalo.cse.green.relationships.RelationshipVisitor#run(org.eclipse.jdt.core.dom.CompilationUnit, edu.buffalo.cse.green.relationships.RelationshipCache)
     */
    protected void run(CompilationUnit cu, RelationshipCache cache) {
        try {
            if (_sourceType.isBinary()) {
                GreenException.illegalOperation(GreenException.GRERR_REL_SOURCE_BINARY);
            }

            ICompilationUnit iCU = (ICompilationUnit) getSourceType().getAncestor(IJavaElement.COMPILATION_UNIT);

            cu.recordModifications();
            cu.accept(this);

            IDocument sourceDoc = new IModifiableBuffer(iCU.getBuffer());
            TextEdit textEdit = cu.rewrite(sourceDoc, null);
            textEdit.apply(sourceDoc);

            if (!iCU.isConsistent()) {
                iCU.save(PlugIn.getEmptyProgressMonitor(), true);
            }

            organizeImports(_sourceType);
        } catch (BadLocationException e) {
            e.printStackTrace();
        } catch (JavaModelException e) {
            e.printStackTrace();
        }
    }

    /**
     * @return The source type of the generated relationship.
     */
    protected IType getSourceType() {
        return _sourceType;
    }

    /**
     * @return The target type of the generated relationship.
     */
    protected IType getTargetType() {
        return _declaredTargetType;
    }

    /**
     * Convenience method for removing the last segment of a name.
     * 
     * @param name - The name.
     * @return The name's qualifier (the part other than the simple name).
     */
    public static Name getQualifier(Name name) {
        if (name.isSimpleName()) {
            return null;
        }
        QualifiedName qualName = (QualifiedName) name;
        return qualName.getQualifier();
    }

    /**
     * Sets the model used for this generator.
     * 
     * @param rModel - The model.
     * @return True if successful, false otherwise.
     */
    public boolean setRelationship(RelationshipModel rModel) {
        _sourceType = rModel.getSourceType();
        _declaredTargetType = rModel.getTargetType();

        if (needChooseTypeDialog()) {
            ChooseTypeWizard wizard = new ChooseTypeWizard(getTargetType());
            WizardDialog dialog = new WizardDialog(PlugIn.getDefaultShell(), wizard);
            dialog.setMinimumPageSize(300, 500);
            dialog.create();
            int res = dialog.open();

            if (res == WizardDialog.CANCEL) {
                return false;
            }

            setActualTargetType(wizard.getSelectedType());
        }

        return true;
    }

    /**
     * @return true if a choose type dialog is needed, false otherwise.
     */
    protected abstract boolean needChooseTypeDialog();

    /**
     * @return true if a default constructor is needed, false otherwise.
     */
    protected abstract boolean needConstructor();

    /**
     * @return true if the relationship can potentially be a collection.
     */
    public boolean supportsCardinality() {
        return false;
    }

    /**
     * Sets the cardinality of the relationship.
     * 
     * @param cardinality - The cardinality.
     */
    public void setCardinality(int cardinality) {
        if (!supportsCardinality()) {
            GreenException.illegalOperation("Cannot set the cardinality " + "of a non-cardinal relationship type");
        }

        _cardinality = cardinality;
    }

    /**
     * Sets the source type of the relationship.
     * 
     * @param selectedType - The type.
     */
    public void setSourceType(IType selectedType) {
        _sourceType = selectedType;
    }

    /**
     * Sets the target type of the relationship.
     * 
     * @param selectedType - The type.
     */
    public void setTargetType(IType selectedType) {
        _declaredTargetType = selectedType;
    }

    /**
     * @param type - The type.
     * @return A reference to the given <code>IType</code>. 
     */
    protected Type createTypeReference(IType type) {
        boolean success = addImport(type);

        if (success) {
            //         System.out.println(">>>> "+type.getElementName()+" <<<<");
            /*
             * >>>> IAdapterB <<<<
             * >>>> IAdapterB <<<<
             * >>>> IAdapterB <<<<
             * >>>>  <<<<               <--- what's going on here?
             */
            return getAST().newSimpleType(
                    // will this work for an anonymous class?
                    getAST().newSimpleName(type.getElementName()));
        } else {
            return getAST().newSimpleType(createQualifiedName(type.getFullyQualifiedName()));
        }
    }

    /**
     * @param name - The type's name.
     * @return A reference to the given name.
     */
    private Type createTypeReference(QualifiedName name) {
        // create a type (for reference to the target type)
        AST ast = getAST();

        // add an import for the desired Java element if necessary
        boolean success = addImport(name);

        if (success) {
            // type can be referred to by its simple name
            return ast.newSimpleType(ast.newSimpleName(name.getName().toString()));
        } else {
            // type must use a qualified name
            return ast.newSimpleType(name);
        }
    }

    /**
     * Called to handle the traversal of a block.
     * 
     * @param node - The node to traverse.
     * @return True if processing should continue, false otherwise.
     */
    protected abstract boolean process(Block node);

    /**
     * @return True if blocks should be traversed, false otherwise.
     */
    protected abstract boolean doVisitBlocks();

    /**
     * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.Block)
     */
    public final boolean visit(Block node) {
        if (doVisitBlocks()) {
            for (Statement stmt : (AbstractList<Statement>) node.statements()) {
                if (stmt.getNodeType() == VARIABLE_DECLARATION_STATEMENT) {
                    VariableDeclarationStatement vds = (VariableDeclarationStatement) stmt;
                    List<VariableDeclarationFragment> vdfs = (AbstractList<VariableDeclarationFragment>) vds
                            .fragments();

                    for (VariableDeclarationFragment vdf : vdfs) {
                        getLocalDeclarations().add(vdf.getName().getIdentifier());
                    }
                }
            }

            process(node);
        }

        return true;
    }

    /**
     * @see org.eclipse.jdt.core.dom.ASTVisitor#endVisit(org.eclipse.jdt.core.dom.Block)
     */
    public final void endVisit(Block node) {
        getLocalDeclarations().clear();
    }

    /**
     * @param targetType - The declared type of the variable.
     * @param name - The name of the variable.
     * @param value - The value to assign to the variable.
     * @return A variable declaration. 
     */
    protected VariableDeclarationStatement createNormalVariableDeclaration(IType targetType, String name,
            Expression value) {
        return createVariableDeclaration(targetType, name, value, false);
    }

    /**
     * @param targetType - The declared type of the variable.
     * @param name - The name of the variable.
     * @return A generic variable declaration.
     */
    protected VariableDeclarationStatement createGenericVariableDeclaration(IType targetType, String name) {
        addImport(createQualifiedName(ARRAYLIST));

        ClassInstanceCreation cic = getAST().newClassInstanceCreation();
        cic.setType(createParameterizedTypeReference(ARRAYLIST, createTypeReference(targetType)));

        return createVariableDeclaration(targetType, name, cic, true);
    }

    /**
     * @param collection - The qualified name of the collection's type.
     * @param targetType - The type parameter.
     * @return A reference to the parameterized type.
     */
    protected Type createParameterizedTypeReference(String collection, Type targetType) {
        // set the parameter
        ParameterizedType pType = getAST()
                .newParameterizedType(createTypeReference(createQualifiedName(collection)));

        // set the collection type
        pType.setType(createTypeReference(createQualifiedName(collection)));

        List<Type> types = (AbstractList<Type>) (List) pType.getStructuralProperty(TYPE_ARGUMENTS_PROPERTY);
        types.add(targetType);

        return pType;
    }

    /**
     * @param targetType - The variable's declared type.
     * @param name - The name of the variable.
     * @param value - The value to assign to the variable.
     * @param isGeneric - Whether or not the variable represents a generic
     * variable declaration.
     * @return A variable declaration.
     */
    private VariableDeclarationStatement createVariableDeclaration(IType targetType, String name, Expression value,
            boolean isGeneric) {
        VariableDeclarationFragment vdf = getAST().newVariableDeclarationFragment();
        vdf.setInitializer(value);
        vdf.setName(getAST().newSimpleName(name));

        VariableDeclarationStatement vds = getAST().newVariableDeclarationStatement(vdf);

        if (isGeneric) {
            vds.setType(createParameterizedTypeReference(LIST, createTypeReference(targetType)));
        } else {
            vds.setType(createTypeReference(targetType));
        }

        return vds;
    }

    /**
     * @param type - The type.
     * @return A representation of an instantiation of the type.
     */
    protected ClassInstanceCreation createInvocation(Type type) {
        ClassInstanceCreation cic = getAST().newClassInstanceCreation();
        cic.setType(type);

        return cic;
    }

    /**
     * @param type - The type.
     * @return A representation of a parameterized instnatiation of the type.
     */
    protected ClassInstanceCreation createParameterizedInvocation(Type type) {
        return createInvocation(createParameterizedTypeReference(ARRAYLIST, type));
    }

    /**
     * @return The basic variable name used for this generator.
     */
    protected String getBaseVariableName() {
        return getTargetType().getElementName().toLowerCase().charAt(0)
                + getTargetType().getElementName().substring(1);
    }

    /**
     * @return A list of all the used variable names.
     */
    private List<String> generateVariableList() {
        List<String> vars = new ArrayList<String>();
        vars.addAll(getParameterDeclarations());
        vars.addAll(getLocalDeclarations());
        vars.addAll(getFieldNames());

        return vars;
    }

    /**
     * @param base - The base variable name.
     * @return A unique variable name generated using the base.
     */
    protected String generateVariableName(String base) {
        List<String> variables = generateVariableList();
        String varName = base;
        int x = 2;

        while (variables.contains(varName)) {
            varName = base + x;
            x++;
        }

        return varName;
    }

    /**
     * Adds a declaration for the given field
     * 
     * @param type - The declared type of the field
     * @param name - The name of the field
     * @return False if the field already exists, true otherwise
     */
    protected boolean addField(Type type, String name) {
        if (isFieldDeclared(name))
            return false;

        List<BodyDeclaration> decs = getCurrentTypeInfo().bodyDeclarations();
        VariableDeclarationFragment vdf = getAST().newVariableDeclarationFragment();
        FieldDeclaration dec = getAST().newFieldDeclaration(vdf);
        List<Modifier> modifiers = (AbstractList<Modifier>) dec.modifiers();
        modifiers.add(getAST().newModifier(Modifier.ModifierKeyword.PRIVATE_KEYWORD));

        dec.setType(type);
        vdf.setName(getAST().newSimpleName(name));

        decs.add(0, dec);
        getFields().add(0, dec);

        return true;
    }

    /**
     * Creates a new field assignment and adds a field declaration if needed
     * 
     * @param name - The name of the field
     * @param value - The value on the RHS of the assignment
     * @return
     */
    protected Assignment createAssignment(String name, Expression value) {
        Assignment assignment = getAST().newAssignment();
        Expression lhs;

        SimpleName field = getAST().newSimpleName(name);

        if (getParameterDeclarations().contains(name)) {
            FieldAccess exp = getAST().newFieldAccess();
            ThisExpression thisExp = getAST().newThisExpression();
            exp.setExpression(thisExp);
            exp.setName(field);
            lhs = exp;
        } else {
            lhs = field;
        }

        assignment.setLeftHandSide(lhs);
        assignment.setRightHandSide(value);

        return assignment;
    }

    /**
     * @return True if the type declaration matches the source type, false
     * otherwise.
     */
    protected boolean correctTypeToGenerate() {
        return getSourceType().equals(getCurrentType());
    }

    /**
     * Adds a parameterized parameter to the given method.
     * 
     * @param method - The method.
     * @param type - The type of the parameter.
     * @param paramName - The name of the parameter.
     */
    protected void addParameterizedParameter(MethodDeclaration method, IType type, String paramName) {
        addParameter(method, createParameterizedTypeReference(LIST, createTypeReference(type)), paramName);
    }

    /**
     * Adds a parameter to the given method.
     * 
     * @param method - The method.
     * @param type - The type of the parameter.
     * @param paramName - The name of the parameter.
     */
    protected void addNormalParameter(MethodDeclaration method, IType type, String paramName) {
        addParameter(method, createTypeReference(type), paramName);
    }

    /**
     * Adds a parameter to the given method.
     * 
     * @param method - The method.
     * @param type - The type of the parameter.
     * @param paramName - The name of the parameter.
     */
    private void addParameter(MethodDeclaration method, Type type, String paramName) {
        List<SingleVariableDeclaration> params = (AbstractList<SingleVariableDeclaration>) method.parameters();
        SingleVariableDeclaration svd = getAST().newSingleVariableDeclaration();
        svd.setType(type);
        svd.setName(getAST().newSimpleName(paramName));
        params.add(svd);
    }

    /**
     * Sets the actual type of the target of the relationship (as opposed to the
     * declared type).
     * 
     * @param type - The actual type.
     */
    private void setActualTargetType(IType type) {
        _actualTargetType = type;
    }

    /**
     * @return The actual type of the target of the relationship.
     */
    protected IType getActualTargetType() {
        if (_actualTargetType == null) {
            return _declaredTargetType;
        } else {
            return _actualTargetType;
        }
    }

    /**
     * @param text - The text.
     * @return A simple name representing the text.
     */
    protected SimpleName name(String text) {
        return getAST().newSimpleName(text);
    }

    /**
     * @param variable - The variable to invoke the method on.
     * @param method - The method to invoke.
     * @param arguments - The arguments to pass to the method.
     * @return A method invocation statement.
     */
    protected ExpressionStatement createMethodInvocation(String variable, String method,
            List<Expression> arguments) {
        MethodInvocation m = getAST().newMethodInvocation();
        m.setExpression(name(variable));
        m.setName(name(method));

        List<Expression> args = (AbstractList<Expression>) (List) m.arguments();
        args.addAll(arguments);

        return getAST().newExpressionStatement(m);
    }
}