/* 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) { return getAST().newSimpleType( 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 = (AbstractList<BodyDeclaration>) getCurrentTypeDeclaration().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); } }