Java tutorial
/** Copyright 2010 Christian Kstner This file is part of CIDE. CIDE 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, version 3 of the License. CIDE 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 CIDE. If not, see <http://www.gnu.org/licenses/>. See http://www.fosd.de/cide/ for further information. */ package de.ovgu.cide.export.physical.ahead; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; 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.FieldDeclaration; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.SuperMethodInvocation; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import de.ovgu.cide.export.physical.ahead.ast.JakClassRefinement; import de.ovgu.cide.export.physical.ahead.ast.JakCompilationUnit; import de.ovgu.cide.export.physical.internal.Formal; import de.ovgu.cide.export.physical.internal.LocalVariableHelper; import de.ovgu.cide.export.physical.internal.RefactoringColorManager; import de.ovgu.cide.features.IFeature; /** * starts on a TypeDeclaration node on the type to refactor. (parent is * compilation unit) * * note, this does not use method objects. if method objects should be used they * must be created prior to applying this visitor * * @author cKaestner * */ public class JakFeatureRefactorer extends BaseFeatureRefactorer { private JakCompilationUnit targetCompUnit; protected Stack<TypeDeclaration> mainType = new Stack<TypeDeclaration>(); private Set<MethodDeclaration> ignoredMethodsForAroundAdvice = new HashSet<MethodDeclaration>(); public JakFeatureRefactorer(Set<IFeature> derivative, JakCompilationUnit targetCompUnit, RefactoringColorManager colorManager) { super(derivative, colorManager); this.targetCompUnit = targetCompUnit; } @Override public void endVisit(TypeDeclaration node) { mainType.pop(); super.endVisit(node); } /** * refactoring whole methods is easy. */ public boolean visit(TypeDeclaration type) { mainType.push(type); for (MethodDeclaration method : type.getMethods()) { if (derivative.equals(colorManager.getColors(method))) refactorMethod(type, method); } for (FieldDeclaration field : type.getFields()) { if (derivative.equals(colorManager.getColors(field))) refactorField(type, field); } List<BodyDeclaration> bodyDeclarations = type.bodyDeclarations(); for (int idx = bodyDeclarations.size() - 1; idx >= 0; idx--) { if (derivative.equals(colorManager.getColors(bodyDeclarations.get(idx)))) refactorOtherMember(type, bodyDeclarations.get(idx)); } Type superType = type.getSuperclassType(); if (superType != null && derivative.equals(colorManager.getColors(superType))) refactorSuperType(type, superType); List<Type> interfaces = new ArrayList<Type>(type.superInterfaceTypes()); for (Type implementsType : interfaces) { if (derivative.equals(colorManager.getColors(implementsType))) refactorImplementsType(type, implementsType); } return true; } private void refactorImplementsType(TypeDeclaration type, Type implementsType) { type.superInterfaceTypes().remove(implementsType); targetCompUnit.getRefinement().addSuperInterfaceType(implementsType); } private void refactorSuperType(TypeDeclaration type, Type superType) { type.setSuperclassType(null); targetCompUnit.getRefinement().setSuperclassType(superType); } private void refactorOtherMember(TypeDeclaration type, BodyDeclaration membertype) { type.bodyDeclarations().remove(membertype); targetCompUnit.getRefinement().addOtherMember(membertype); } private void refactorField(TypeDeclaration type, FieldDeclaration field) { type.bodyDeclarations().remove(field); targetCompUnit.getRefinement().addFieldIntroductionForType(type, field); } public boolean visit(MethodDeclaration node) { if (!ignoredMethodsForAroundAdvice.contains(node)) if (node.getBody() != null) { boolean success = node.getBody().statements().isEmpty(); if (!success) success = refactorAllStatements(node); if (!success) refactorAroundAdvice(node); } return super.visit(node); } private boolean refactorAllStatements(MethodDeclaration method) { if (!RefactoringUtils.canRefactorAllStatements(method, colorManager, derivative)) return false; AST ast = method.getAST(); MethodDeclaration refinement = (MethodDeclaration) ASTNode.copySubtree(ast, method); List<Statement> baseBody = method.getBody().statements(); List<Statement> refinementBody = refinement.getBody().statements(); Statement exception = RefactoringUtils.findSubtreeRuleException(baseBody, colorManager, derivative); // if an exception to the subtree rule was done if (exception != null) { // move this back into the original method // place a Super call in the old position Statement superCall = createSuperCall(method, ast, false, null); replaceStatement(exception, superCall); } List<Statement> baseStatements = new ArrayList<Statement>(baseBody); baseBody.clear(); refinementBody.clear(); // create a refinement // move all statements to a refinement refinementBody.addAll(baseStatements); if (exception != null) { RefactoringUtils.addStatementOrBlockContent(exception, baseBody); } else { // otherwise add a Super call to the end Statement superCall = createSuperCall(method, ast, true, null); if (superCall != null) refinementBody.add(superCall); } targetCompUnit.getRefinement().addRefinementForMethod(method, refinement); return true; } static void replaceStatement(Statement target, Statement replacement) { ASTNode p = target.getParent(); if (target instanceof Block && !(replacement instanceof Block)) { Block b = replacement.getAST().newBlock(); b.statements().add(replacement); replacement = b; } StructuralPropertyDescriptor prop = target.getLocationInParent(); if (prop.isSimpleProperty() || prop.isChildProperty()) { p.setStructuralProperty(prop, replacement); } else if (prop.isChildListProperty()) { assert false; } } /** * refactoring the first and last statements per method * * conditions: * first statements do not expose variables used later * last * statements to not consume local variables */ protected boolean refactorAroundAdvice(MethodDeclaration node) { Block body = node.getBody(); if (body == null) return false; List<Statement> statements = body.statements(); List<Statement> refactorFirst = RefactoringUtils.findBeforeStatements(node, colorManager, derivative); List<Statement> refactorLast = RefactoringUtils.findAfterStatements(node, colorManager, derivative); boolean canRefactor = RefactoringUtils.canRefactorStatementsBeforeAfter(node, refactorFirst, refactorLast, colorManager, derivative); if (canRefactor) { statements.removeAll(refactorFirst); statements.removeAll(refactorLast); extractAroundMethodRefinement(node, refactorFirst, refactorLast); return true; } return false; } protected void visitMethodDeclParameters(MethodDeclaration method, List<SingleVariableDeclaration> parameters) { for (SingleVariableDeclaration parameter : parameters) { if (derivative.equals(colorManager.getColors(parameter))) { refactorParameterDeclaration(method, parameter); } } } // public boolean visit(MethodInvocation methodInvoc) { // for (Expression arg : (List<Expression>) methodInvoc.arguments()) { // if (derivative.equals(colorManager.getColors(arg))) { // // } // } // // return true; // } /** * removes parameter from method declaration, creates a threadsafe field * instead changes all local access inside the body to field access * * @param method * @param parameter */ private void refactorParameterDeclaration(MethodDeclaration method, SingleVariableDeclaration parameter) { if (method.parameters().remove(parameter)) { FieldDeclaration parameterField = createParamterField(method, parameter); replaceParamterAccess(method, parameter, parameterField); } } private void replaceParamterAccess(final MethodDeclaration method, final SingleVariableDeclaration parameter, FieldDeclaration parameterField) { final IVariableBinding oldParameterBinding = parameter.resolveBinding(); method.getBody().accept(new ASTVisitor() { public boolean visit(SimpleName node) { if (oldParameterBinding.isEqualTo(node.resolveBinding())) node.setIdentifier(getParameterFieldName(method, parameter)); return true; } }); } private FieldDeclaration createParamterField(MethodDeclaration method, SingleVariableDeclaration parameter) { String parameterFieldName = getParameterFieldName(method, parameter); AST ast = parameter.getAST(); VariableDeclarationFragment fragment = ast.newVariableDeclarationFragment(); fragment.setName(ast.newSimpleName(parameterFieldName)); FieldDeclaration parameterField = ast.newFieldDeclaration(fragment); // TODO: use a threadlocal instead of an ordinary variable (is more // difficult to replace in source code, as we have to distinguish // between read and write access // SimpleType threadLocalType = ast.newSimpleType(ast // .newSimpleName("ThreadLocal")); // ParameterizedType parameterizedThreadLocal = ast // .newParameterizedType(threadLocalType); // parameterizedThreadLocal.typeArguments().add(parameter.getType()); // // parameterField.setType(parameterizedThreadLocal); parameterField.setType(parameter.getType()); parameterField.modifiers().addAll(getScopeModifiers(method.modifiers())); targetCompUnit.getRefinement().addField(parameterField); return parameterField; } /** * helper function, filters all modifiers and returns only private public * and protected * * @param modifiers * @return */ private Collection<Modifier> getScopeModifiers(List<IExtendedModifier> modifiers) { Set<Modifier> result = new HashSet<Modifier>(); for (IExtendedModifier modifier : modifiers) if (modifier.isModifier()) { Modifier m = (Modifier) modifier; if (m.isPrivate() || m.isProtected() || m.isPublic()) result.add(m); } return result; } private ThreadLocal<Object> a; private String getParameterFieldName(MethodDeclaration method, SingleVariableDeclaration parameter) { a.get(); return "param_" + method.getName().getIdentifier() + "_" + parameter.getName().getIdentifier(); } private void extractAroundMethodRefinement(MethodDeclaration method, List<Statement> refactorFirst, List<Statement> refactorLast) { AST ast = method.getAST(); MethodDeclaration copiedMethod = (MethodDeclaration) ASTNode.copySubtree(ast, method); Statement superCall = createSuperCall(method, ast, refactorLast.isEmpty(), null); List<Statement> targetBody = copiedMethod.getBody().statements(); targetBody.clear(); targetBody.addAll(refactorFirst); if (superCall != null) targetBody.add(superCall); targetBody.addAll(refactorLast); if (!refactorLast.isEmpty() && !RefactoringUtils.isVoid(method.getReturnType2())) { targetBody.add(createReturnStatement(ast)); } targetCompUnit.getRefinement().addRefinementForMethod(method, copiedMethod); } protected void addStatements(List<Statement> newStatements, List<Statement> target) { for (Statement stmt : newStatements) { ASTNode excluded = RefactoringUtils.getSubtreeRuleExceptionNode(stmt, colorManager, derivative); if (excluded != null) { // move excluded into a new hook method and } target.add(stmt); } } /** * refactore all statements in the middle of a method by introducing hooks * * @param block */ public boolean visit(Block block) { findStatementsSequencesInBlockForRefactoring(block); return super.visit(block); } private void findStatementsSequencesInBlockForRefactoring(Block block) { List<Statement> statements = block.statements(); int idx = statements.size() - 1; List<Statement> statementsToReplace = new ArrayList<Statement>(); // grouping statements from back to front and refactoring groups with // hooks while (idx >= 0) { Statement stmt = statements.get(idx); boolean isColoredStmt = derivative.equals(colorManager.getColors(stmt)); boolean isSubtreeRuleException = false, isNextSubtreeRuleException = false; if (isColoredStmt) { isSubtreeRuleException = RefactoringUtils.isSubtreeRuleException(stmt, colorManager, derivative); if (isSubtreeRuleException && idx > 0) { isNextSubtreeRuleException = derivative.equals(colorManager.getColors(statements.get(idx - 1))) && RefactoringUtils.isSubtreeRuleException(statements.get(idx - 1), colorManager, derivative); ; } statementsToReplace.add(0, stmt); } boolean refactorCollectedStatements = false; if (idx == 0) refactorCollectedStatements = true; // group by colored statements if (!isColoredStmt) refactorCollectedStatements = true; // only one subtree rule exception per block possible if (isSubtreeRuleException && isNextSubtreeRuleException) refactorCollectedStatements = true; if (refactorCollectedStatements && statementsToReplace.size() > 0) { refactorStatementSequenceUsingHook(statementsToReplace, block, idx + (isColoredStmt ? 0 : 1)); statementsToReplace = new ArrayList<Statement>(); } idx--; } assert statementsToReplace.size() == 0; } private void refactorStatementSequenceUsingHook(List<Statement> statementSequence, Block containingBlock, int replacementPosition) { Statement exception = RefactoringUtils.findSubtreeRuleException(statementSequence, colorManager, derivative); JakHookMethodHelper hook = new JakHookMethodHelper(statementSequence, RefactoringUtils.getMethodDeclaration(containingBlock), exception, colorManager); containingBlock.statements().removeAll(statementSequence); Statement hookCall = hook.getHookCall(); containingBlock.statements().add(replacementPosition, hookCall); mainType.peek().bodyDeclarations().add(hook.getHookDeclaration()); JakClassRefinement ref = targetCompUnit.getRefinement(); ref.addRefinementForMethod(RefactoringUtils.getMethodDeclaration(containingBlock), hook.getRefinement()); // color hook method with the same color as the call (if the // call inherits any colors) Set<IFeature> hookColors = colorManager.getInheritedColors(hookCall); if (hookColors.size() > 0) colorManager.setColors(hook.getHookDeclaration(), hookColors); ignoredMethodsForAroundAdvice.add(hook.getHookDeclaration()); } /** * creates a Super call (actually a super call that is replaced by Super * when printing the Jak file with the JakPrettyPrinter). * * @param currentMethod * @param ast * @param withReturn * if true then the result of the super invocation is returned * (return Super...), otherwise it is placed in a local variable * called result * @param formal * local variable to which the result (if any) should be * assigned. if formal==null, then a new variable is created with * the name result (ChK: is there a usecase for this?) * @return */ static Statement createSuperCall(MethodDeclaration currentMethod, AST ast, boolean withReturn, Formal formal) { Statement superCall; if (currentMethod.isConstructor()) { assert !withReturn; /** * super constructor calls are implicit in Jak. so there is no * statement to call a super constructor. */ superCall = null; } else { SuperMethodInvocation superCallExpr = ast.newSuperMethodInvocation(); SuperCallHelper.addSuperLayerCall(superCallExpr); superCallExpr.setName(ast.newSimpleName(currentMethod.getName().getIdentifier())); String types = ""; for (Iterator<SingleVariableDeclaration> iter = currentMethod.parameters().iterator(); iter .hasNext();) { SingleVariableDeclaration param = iter.next(); SimpleName v = ast.newSimpleName(param.getName().getIdentifier()); superCallExpr.arguments().add(v); VariableDeclaration decl = LocalVariableHelper.findVariableDeclaration(param.getName()); if (decl != null) LocalVariableHelper.addLocalVariableAccess(v, decl); types += getTypeString(param.getType()); if (iter.hasNext()) types += ", "; } SuperTypeHelper.cacheTypes(superCallExpr, types); if (RefactoringUtils.isVoid(currentMethod.getReturnType2())) { superCall = ast.newExpressionStatement(superCallExpr); } else { if (withReturn) { ReturnStatement returnStatement = ast.newReturnStatement(); returnStatement.setExpression(superCallExpr); superCall = returnStatement; } else { if (formal == null) { VariableDeclarationFragment returnVariable = ast.newVariableDeclarationFragment(); returnVariable.setName(ast.newSimpleName("result")); VariableDeclarationStatement variableDecl = ast .newVariableDeclarationStatement(returnVariable); variableDecl.setType( (Type) ASTNode.copySubtree(currentMethod.getAST(), currentMethod.getReturnType2())); returnVariable.setInitializer(superCallExpr); superCall = variableDecl; } else { Assignment assign = ast.newAssignment(); SimpleName v = ast.newSimpleName(formal.name); assign.setLeftHandSide(v); LocalVariableHelper.addLocalVariableAccess(v, formal); assign.setRightHandSide(superCallExpr); superCall = ast.newExpressionStatement(assign); } } } } return superCall; } private static String getTypeString(Type type) { ITypeBinding binding = type.resolveBinding(); if (binding != null) return binding.getName(); if (type.isPrimitiveType()) return ((PrimitiveType) type).getPrimitiveTypeCode().toString(); if (type.isSimpleType()) return ((SimpleType) type).getName().toString(); assert false : "unable to resolve type"; return type.toString(); } private void refactorMethod(TypeDeclaration type, MethodDeclaration method) { type.bodyDeclarations().remove(method); targetCompUnit.getRefinement().addMethodIntroductionForType(type, method); } }