Java tutorial
/******************************************************************************* * Copyright (c) 2004, 2010 IBM Corporation SpringSource and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Luzius Meisser - initial implementation * Andrew Eisenberg - adapted for use with JDT Weaving *******************************************************************************/ package org.eclipse.ajdt.core.codeconversion; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import org.aspectj.asm.IProgramElement; import org.aspectj.asm.IProgramElement.Accessibility; import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation; import org.aspectj.org.eclipse.jdt.core.compiler.InvalidInputException; import org.aspectj.org.eclipse.jdt.internal.compiler.parser.Scanner; import org.aspectj.org.eclipse.jdt.internal.compiler.parser.TerminalTokens; import org.eclipse.ajdt.core.AspectJPlugin; import org.eclipse.ajdt.core.javaelements.AspectElement; import org.eclipse.ajdt.core.model.AJProjectModelFacade; import org.eclipse.ajdt.core.model.AJProjectModelFactory; import org.eclipse.ajdt.core.model.AJRelationshipManager; import org.eclipse.ajdt.internal.core.ras.NoFFDC; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IParent; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeParameter; import org.eclipse.jdt.core.JavaModelException; /** * The purpose of this parser is to convert AspectJ code into similar Java code * which allows us to reuse for example for jdt formatting or comment * generation. * * Depending on the ConversionOptions it gets called with, it does: - replace * the keyword "aspect" by "class " - replace all the '.'s in intertype * declarations by an '$'s to make them look like an ordinary declarations. - * erase the keywords "returning", "throwing", "privileged", "issingleton" e.g.: * "after() throwing (Exception e)" -> "after( Exception e)" - erase pointcut * designators (includes "percflow" & co.) - add dummy references to all erased * class references to end of buffer to make "organize imports" work correctly - * add a reference to the target class inside intertype method declarations to * simulate the context switch necessary to get proper code completion. * (A detailed description of how code completion works in AJDT can be found in * bug 74419.) * * Restrictions: - class names inside pointcut designators must begin with a * capital letter to be recognised as such * * * @author Luzius Meisser */ public class AspectsConvertingParser implements TerminalTokens, NoFFDC { private static final String IMPLEMENTS = "implements"; private static final String EXTENDS = "extends"; private static final char[] throwing = "throwing".toCharArray(); //$NON-NLS-1$ private static final char[] returning = "returning".toCharArray(); //$NON-NLS-1$ private static final char[] percflow = "percflow".toCharArray(); //$NON-NLS-1$ private static final char[] percflowbelow = "percflowbelow".toCharArray(); //$NON-NLS-1$ private static final char[] perthis = "perthis".toCharArray(); //$NON-NLS-1$ private static final char[] pertarget = "pertarget".toCharArray(); //$NON-NLS-1$ private static final char[] issingleton = "issingleton".toCharArray(); //$NON-NLS-1$ private static final char[] pertypewithin = "pertypewithin".toCharArray(); //$NON-NLS-1$ private static final char[] classs = "class ".toCharArray(); //$NON-NLS-1$ private static final char[] privileged = " ".toCharArray(); //$NON-NLS-1$ public static final String ITD_INSERTED_IDENTIFIER = "___ITD_INSERTED_IDENTIFIER___"; //$NON-NLS-1$ // used to replace declare declarations private static final char[] intt = "int ".toCharArray(); //$NON-NLS-1$ private final static char[] tjpRefs2 = "\n\torg.aspectj.lang.JoinPoint thisJoinPoint;\n\torg.aspectj.lang.JoinPoint.StaticPart thisJoinPointStaticPart;\n\torg.aspectj.lang.JoinPoint.StaticPart thisEnclosingJoinPointStaticPart;\n" //$NON-NLS-1$ .toCharArray(); private final static char[] endThrow = new char[] { '(', ':' }; public static class Replacement { //the position in the original char[] public int posBefore; //the position in the new char[], or -1 if not yet applied public int posAfter; //the number of chars that get replaced public int length; //the content to be inserted public char[] text; //the number of additional chars (lengthAdded == text.length - length) public int lengthAdded; public Replacement(int pos, int length, char[] text) { this.posBefore = pos; this.posAfter = -1; this.length = length; this.text = text; lengthAdded = text.length - length; } } public AspectsConvertingParser(char[] content) { this.content = content; this.typeReferences = new HashSet<String>(); this.usedIdentifiers = new HashSet<String>(); replacements = new ArrayList<Replacement>(5); } public char[] content; private Set<String> typeReferences; private Set<String> usedIdentifiers; private ConversionOptions options; /** * can be null. used for determining ITDs */ private ICompilationUnit unit; //list of replacements //by convetion: sorted by posBefore in ascending order private ArrayList<Replacement> replacements; protected Scanner scanner; /** * keeps track of being after the ':' of a pointcut declaration */ private boolean inPointcutDesignator; /** * keeps track of being in an aspect body */ private boolean inAspect; /** * keeps track of being in the declaration of an aspect * (ie- before the first '{') */ private boolean inAspectDeclaration; private boolean inClassDeclaration; private boolean inInterfaceDeclaration; private boolean inEnumDeclaration; private Set<String> knownTypeParameters; /** * keeps track of being in the right hand side of a declaration * * Note- thos will *not* catch complex RHS assignments where the RHS contains an inner type * ie- inRHS will evaluate to false after the first ';' of a complex assignment * What I really need is an RHS stack, but that is complicated and I will only add if necessary */ private boolean inRHS; private int posColon; public void setUnit(ICompilationUnit unit) { this.unit = unit; } //returns a list of Insertions to let the client now what has been inserted //into the buffer so he can translate positions from the old into the new //buffer public ArrayList<Replacement> convert(ConversionOptions options) { this.options = options; boolean insertIntertypeDeclarations = options.isThisJoinPointReferencesEnabled(); boolean addReferencesForOrganizeImports = options.isDummyTypeReferencesForOrganizeImportsEnabled(); boolean isSimulateContextSwitchNecessary = (options.getTargetType() != null); Stack<Boolean> inAspectBodyStack = new Stack<Boolean>(); Stack<Boolean> inTypeBodyStack = new Stack<Boolean>(); scanner = new Scanner(); scanner.setSource(content); inPointcutDesignator = false; inAspect = false; inAspectDeclaration = false; inClassDeclaration = false; inInterfaceDeclaration = false; inEnumDeclaration = false; inRHS = false; // Bug 93248: Count question marks so as to ignore colons that are part of conditional statements int questionMarkCount = 0; // count paren levels so that we know if we are in a intertype declaration name or not int parenLevel = 0; // Bug 110751: Ignore colons that are part of enhanced "for" loop in Java 5 boolean inFor = false; // Bug 258685: case statements should not be confused with pointcuts boolean inCase = false; // bug 265977 the aspect keyword is OK if it is in a package declaration boolean inPackageDecl = false; // bug 273691 class does not signify a start of a class declation if // it is after a dot boolean afterDot = false; // Bug 282948: keep track of type params because question marks inside of // type params do not signify the start of a ternary conditional statement // (ie- question mark count) int typeParamDepth = 0; // Bug 384422 keep track of being after commas and parens so that we can distinguish between the 'throwing' and 'returning' keywards // and annotation members boolean afterOpenParen = false; boolean afterComma = false; char[] currentTypeName = null; replacements.clear(); typeReferences.clear(); usedIdentifiers.clear(); int tok = 1; int prevTok = -1; int pos; int typeDeclStart = 0; char[] text = null; char[] prevText = null; while (true) { prevTok = tok; prevText = text; try { tok = scanner.getNextToken(); } catch (InvalidInputException e) { continue; } if (tok == TokenNameEOF) break; text = scanner.getCurrentIdentifierSource(); switch (tok) { case TokenNameIdentifier: if (!inAspect && !inTypeDeclaration()) break; // FIXADE Hmmm...here we should be including enclosing types char[] name = text; if (inTypeDeclaration() && !inPointcutDesignator) { // only do this if we are not adding ITDs if (inAspectDeclaration && !insertIntertypeDeclarations) { if (CharOperation.equals(percflow, name)) { startPointcutDesignator(); } else if (CharOperation.equals(percflowbelow, name)) { startPointcutDesignator(); } else if (CharOperation.equals(perthis, name)) { startPointcutDesignator(); } else if (CharOperation.equals(pertarget, name)) { startPointcutDesignator(); } else if (CharOperation.equals(issingleton, name)) { startPointcutDesignator(); } else if (CharOperation.equals(pertypewithin, name)) { startPointcutDesignator(); } } // store the type name if not already found if (currentTypeName == null) { currentTypeName = name; } } if (!afterComma && !afterOpenParen && (CharOperation.equals(throwing, name) || CharOperation.equals(returning, name))) { consumeRetOrThrow(); } else if (inPointcutDesignator && Character.isUpperCase(name[0]) // Assume all types start with upercase && (content[scanner.getCurrentTokenStartPosition() - 1] != '.') // can ignore if looks fully qualified && (content[scanner.getCurrentTokenStartPosition() - 1] != '*')) // can ignore if looks like a wild core { typeReferences.add(String.valueOf(name)); } if (isSimulateContextSwitchNecessary) { usedIdentifiers.add(new String(name)); } break; case TokenNamefor: inFor = true; break; case TokenNameLPAREN: parenLevel++; inCase = false; afterOpenParen = true; break; case TokenNameCOMMA: afterComma = true; break; case TokenNameRPAREN: inFor = false; inCase = false; parenLevel--; // Bug 301268: just in case there is a variable with a capital first letter before a '<' // reset the typeParamDepth typeParamDepth = 0; break; case TokenNameCOLON: if (inFor) { break; } else if (questionMarkCount > 0) { questionMarkCount--; break; } else if (inCase) { inCase = false; break; } startPointcutDesignator(); break; case TokenNameEQUAL: if (parenLevel == 0) { // can have problems if inside an annotation, there is an '='. // do not set flag in this case. OK to ignore because we do not // process ITDs when we are inside parens anyway. inRHS = true; } break; case TokenNameQUESTION: // bug 282948: only update question mark count if not in type parameter if (typeParamDepth == 0) { questionMarkCount++; } break; case TokenNameSEMICOLON: if (inPointcutDesignator) { endPointcutDesignator(); if (options.isKeepPointcuts()) { addReplacement(scanner.getCurrentTokenStartPosition(), 1, new char[] { '}' }); } } inRHS = false; // may be triggering this too early in case this is part of a complex assignment inPackageDecl = false; break; case TokenNamedefault: case TokenNamecase: inCase = true; break; case TokenNameDOT: afterDot = true; if (!inAspect) { break; } else if (inPointcutDesignator) { break; } else if (parenLevel > 0) { // don't want to convert '.' to '$' inside the parameter declaration break; } if (!inRHS && !inAspectBodyStack.empty() && inAspectBodyStack.peek() == Boolean.TRUE) { processPotentialIntertypeDeclaration(); } break; case TokenNameLBRACE: if (inPointcutDesignator && parenLevel == 0) { // Bug 296044: make sure that lbrace is not inside of a declare declaration endPointcutDesignator(); if (options.isKeepPointcuts()) { addReplacement(scanner.getCurrentTokenStartPosition(), 1, new char[] { ' ' }); } //must be start of advice body -> insert tjp reference if (insertIntertypeDeclarations && !inTypeDeclaration()) { addReplacement(scanner.getCurrentTokenStartPosition() + 1, 0, tjpRefs2); } } // determine if we should add intertype declarations if (insertIntertypeDeclarations && inTypeDeclaration()) { char[] implementsExtends = createImplementExtendsITDs(currentTypeName); if (implementsExtends != null) { addReplacement(typeDeclStart, scanner.getCurrentTokenStartPosition() - typeDeclStart, implementsExtends); } else { // not able to add ITDs since no compilation unit available // may have to replace "aspect" with "class " since we didn't do that before if (hasWordAtPosition("aspect", typeDeclStart)) { addReplacement(typeDeclStart, classs.length, classs); } } char[] interTypeDecls = getInterTypeDecls(currentTypeName); if (interTypeDecls.length > 0) { addReplacement(scanner.getCurrentTokenStartPosition() + 1, 0, interTypeDecls); } } if (addReferencesForOrganizeImports) { // must determine what the type parameters are so that we don't add those references storeTypeParameters(currentTypeName); } if (inTypeDeclaration()) { inTypeBodyStack.push(Boolean.TRUE); } else { inTypeBodyStack.push(Boolean.FALSE); } if (inAspectDeclaration) { inAspectDeclaration = false; inAspectBodyStack.push(Boolean.TRUE); } else { inAspectBodyStack.push(Boolean.FALSE); } inCase = false; inClassDeclaration = false; inInterfaceDeclaration = false; inEnumDeclaration = false; currentTypeName = null; break; case TokenNameafter: // Bug 318132: ensure that the debugger does not raise exceptions when stepping through aspects with advice if (!inTypeBodyStack.isEmpty() && inTypeBodyStack.peek()) { addReplacement(scanner.getCurrentTokenStartPosition(), "after".length(), "int a".toCharArray()); } break; case TokenNamebefore: // Bug 318132: ensure that the debugger does not raise exceptions when stepping through aspects with advice if (!inTypeBodyStack.isEmpty() && inTypeBodyStack.peek()) { addReplacement(scanner.getCurrentTokenStartPosition(), "before".length(), "void b".toCharArray()); } break; case TokenNameRBRACE: if (inPointcutDesignator && parenLevel == 0) { // Bug 296044: make sure that lbrace is not inside of a declare declaration // bug 129367: if we've hit a } here, we must be // in the middle of an unterminated pointcut endPointcutDesignator(); } if (!inAspectBodyStack.empty()) { inAspectBodyStack.pop(); } if (!inTypeBodyStack.empty()) { inTypeBodyStack.pop(); } inCase = false; break; case TokenNameaspect: if (inPackageDecl) { // aspect keyword is OK in package decls break; } inAspect = true; inAspectDeclaration = true; typeDeclStart = pos = scanner.getCurrentTokenStartPosition(); // only do this if not inserting ITDs // if inserting ITDs, this replacement occurs in createImplementExtendsITDs() if (!insertIntertypeDeclarations) { addReplacement(pos, classs.length, classs); } break; case TokenNamedeclare: // check to see if we need to remove declare declarations // so that import references aren't confused if (addReferencesForOrganizeImports) { if (!inAspectBodyStack.isEmpty() && inAspectBodyStack.peek().equals(Boolean.TRUE)) { addReplacement(scanner.getCurrentTokenStartPosition(), intt.length, intt); } } break; case TokenNameclass: if (!afterDot) { // bug 273691 typeDeclStart = pos = scanner.getCurrentTokenStartPosition(); inClassDeclaration = true; } break; case TokenNameinterface: // interface and @interface typeDeclStart = pos = scanner.getCurrentTokenStartPosition(); inInterfaceDeclaration = true; break; case TokenNameenum: // interface and @interface typeDeclStart = pos = scanner.getCurrentTokenStartPosition(); inEnumDeclaration = true; break; case TokenNameprivileged: pos = scanner.getCurrentTokenStartPosition(); addReplacement(pos, privileged.length, privileged); break; case TokenNamepackage: inPackageDecl = true; break; case TokenNameLESS: // bug 282948 check to see if inside type parameter // bug 301268 type parameters can also occur in local variable declarations // if the previous token looks like a type name (ie- identifier that starts with caps) // also bump up typeParamDepth. Note that this will still fail // when there is a var name that starts with caps on the LHS of a '<' comparison if ((!inTypeBodyStack.isEmpty() && inTypeBodyStack.peek() == Boolean.TRUE) || tokenLooksLikeTypeName(prevTok, prevText)) { typeParamDepth++; } break; case TokenNameGREATER: if (typeParamDepth > 0) { typeParamDepth--; } break; case TokenNameRIGHT_SHIFT: if (typeParamDepth > 0) { typeParamDepth -= 2; } break; case TokenNameUNSIGNED_RIGHT_SHIFT: if (typeParamDepth > 0) { typeParamDepth -= 3; } break; case TokenNameAT: // peek at the next token and see if it is // part of a declare annotation. If so, // remove the '@' if (isDeclareAnnotationStart(pos = scanner.getCurrentTokenStartPosition())) { addReplacement(pos, 1, "$".toCharArray()); } break; } if (tok != TokenNameDOT) { afterDot = false; } if (tok != TokenNameLPAREN) { afterOpenParen = false; } if (tok != TokenNameCOMMA) { afterComma = false; } } if (inPointcutDesignator) { // bug 129367: if we've hit the end of the buffer, we must // be in the middle of an unterminated pointcut endPointcutDesignator(); } if (addReferencesForOrganizeImports) { addReferences(); } if (isSimulateContextSwitchNecessary) { simulateContextSwitch(options.getCodeCompletePosition(), options.getTargetType()); } applyReplacements(); // uncomment to see the transformed source code // System.out.println("----------\nConversion:"); // System.out.println(String.valueOf(content)); // System.out.println("----------"); return replacements; } /** * @param i * @return true if this is the start of @type, @field, @method, or @constructor */ private boolean isDeclareAnnotationStart(int i) { if (content[i] != '@') { return false; } i++; switch (content[i]) { case 't': return content.length >= i + "type".length() && content[++i] == 'y' && content[++i] == 'p' && content[++i] == 'e'; case 'f': return content.length >= i + "field".length() && content[++i] == 'i' && content[++i] == 'e' && content[++i] == 'l' && content[++i] == 'd'; case 'm': return content.length >= i + "method".length() && content[++i] == 'e' && content[++i] == 't' && content[++i] == 'h' && content[++i] == 'o' && content[++i] == 'd'; case 'c': return content.length >= i + "constructor".length() && content[++i] == 'o' && content[++i] == 'n' && content[++i] == 's' && content[++i] == 't' && content[++i] == 'r' && content[++i] == 'u' && content[++i] == 'c' && content[++i] == 't' && content[++i] == 'o' && content[++i] == 'r'; } return false; } /** * @param currentTypeName */ private void storeTypeParameters(char[] typeName) { if (unit != null && typeName != null && typeName.length > 0) { IType type = getHandle(String.valueOf(typeName)); if (type.exists() && type instanceof AspectElement) { try { ITypeParameter[] typeParameters = type.getTypeParameters(); if (typeParameters != null && typeParameters.length > 0) { if (knownTypeParameters == null) { knownTypeParameters = new HashSet<String>(); } for (ITypeParameter parameter : typeParameters) { knownTypeParameters.add(parameter.getElementName()); } } } catch (JavaModelException e) { } } } } private boolean tokenLooksLikeTypeName(int token, char[] text) { return token == TokenNameIdentifier && text != null && text.length > 0 && Character.isUpperCase(text[0]); } /** * @param typeName name of the type * @return new type declaration string to replace the original * that contains all of the types that are declared parents of this one * returns null if could not find the super types and super interfaces */ public char[] createImplementExtendsITDs(char[] typeName) { if (unit != null && typeName != null && typeName.length > 0) { IType type = getHandle(String.valueOf(typeName)); if (type.exists()) { List<String>[] declares = getDeclareExtendsImplements(type); if (declares[0].size() == 0 && declares[1].size() == 0) { // nothing to do return null; } try { StringBuffer sb = new StringBuffer(); sb.append(type.isInterface() ? "interface " : "class "); sb.append(typeName); ITypeParameter[] tParams = type.getTypeParameters(); if (tParams != null && tParams.length > 0) { sb.append(" <"); for (int i = 0; i < tParams.length; i++) { if (i > 0) { sb.append(", "); } sb.append(tParams[i].getSource()); } sb.append("> "); } List<String> declareExtends = declares[0]; List<String> declareImplements = declares[1]; if (type.isClass()) { String superClass = type.getSuperclassName(); if (declareExtends.size() > 0) { superClass = (String) declareExtends.get(0); superClass = superClass.replace('$', '.'); } if (superClass != null) { sb.append(" " + EXTENDS + " " + superClass); } } String[] superInterfaces = type.getSuperInterfaceNames(); List<String> interfaceParents = type.isClass() ? declareImplements : declareExtends; for (int i = 0; i < superInterfaces.length; i++) { interfaceParents.add(superInterfaces[i]); } if (interfaceParents.size() > 0) { if (type.isClass()) { sb.append(" " + IMPLEMENTS); } else { sb.append(" " + EXTENDS); } for (Iterator<String> interfaceIter = interfaceParents.iterator(); interfaceIter .hasNext();) { String interName = interfaceIter.next(); interName = interName.replace('$', '.'); sb.append(" " + interName); if (interfaceIter.hasNext()) { sb.append(","); } } } sb.append(" "); return sb.toString().toCharArray(); } catch (JavaModelException e) { AspectJPlugin.getDefault().getLog() .log(new Status(Status.ERROR, AspectJPlugin.PLUGIN_ID, e.getMessage(), e)); return null; } } } return null; } // copied from ITDInserter...make a utility method? private IType getHandle(String typeName) { try { IType type = getHandleFromChild(typeName, unit); if (type != null) { return type; } } catch (JavaModelException e) { } // this type may not exist return unit.getType(new String(typeName)); } private IType getHandleFromChild(String typeName, IParent parent) throws JavaModelException { IJavaElement[] children = parent.getChildren(); for (int i = 0; i < children.length; i++) { if (children[i].getElementType() == IJavaElement.TYPE && typeName.equals(children[i].getElementName())) { return (IType) children[i]; } } for (int i = 0; i < children.length; i++) { if (children[i].getElementType() == IJavaElement.TYPE) { IType type = getHandleFromChild(typeName, (IParent) children[i]); if (type != null) { return type; } } } return null; } /** * @param type type to look for declare extends on * @return list of all declare extends that apply to this type * in fully qualified strings */ protected List<String>[] getDeclareExtendsImplements(IType type) { List<String> declareExtends = new ArrayList<String>(); List<String> declareImplements = new ArrayList<String>(); if (type != null && type.exists()) { AJProjectModelFacade model = AJProjectModelFactory.getInstance().getModelForJavaElement(type); if (model.hasModel()) { List<IJavaElement> rels = model.getRelationshipsForElement(type, AJRelationshipManager.ASPECT_DECLARATIONS); for (IJavaElement je : rels) { IProgramElement pe = model.javaElementToProgramElement(je); List<String> parentTypes = null; if (pe.getKind() == IProgramElement.Kind.DECLARE_PARENTS) { parentTypes = pe.getParentTypes(); } else if (pe.getKind() == IProgramElement.Kind.ASPECT) { // could be a concrete aspect that instantiates a declare parents relationship from an abstact aspect Map<String, List<String>> parents = pe.getDeclareParentsMap(); if (parents != null) { parentTypes = parents.get(type.getFullyQualifiedName()); } } if (parentTypes != null) { IJavaProject project = type.getJavaProject(); // bug 273914---must determine if these are interfaces or classes for (String parentType : parentTypes) { IType parentTypeElt; try { parentTypeElt = project.findType(parentType); } catch (JavaModelException e) { parentTypeElt = null; } boolean parentIsClass; boolean typeIsClass; try { parentIsClass = parentTypeElt == null || parentTypeElt.isClass(); typeIsClass = type.isClass(); } catch (JavaModelException e) { parentIsClass = true; typeIsClass = true; } if (parentIsClass && typeIsClass) { declareExtends.add(parentType); } else if (!parentIsClass && typeIsClass) { declareImplements.add(parentType); } else if (!parentIsClass && !typeIsClass) { declareExtends.add(parentType); } else if (parentIsClass && !typeIsClass) { // error, but handle gracefully declareExtends.add(parentType); } } } } } } return new List[] { declareExtends, declareImplements }; } /** * returns reasonable text for all ITDs and ITITs * @param currentTypeName * @return */ protected char[] getInterTypeDecls(char[] currentTypeName) { if (unit != null && currentTypeName != null && currentTypeName.length > 0) { AJProjectModelFacade model = AJProjectModelFactory.getInstance().getModelForJavaElement(unit); if (model.hasModel()) { IType type = getHandle(new String(currentTypeName)); if (type.exists()) { List<IJavaElement> rels = model.getRelationshipsForElement(type, AJRelationshipManager.ASPECT_DECLARATIONS); StringBuffer sb = new StringBuffer("\n\t"); for (IJavaElement je : rels) { IProgramElement declareElt = model.javaElementToProgramElement(je); if (declareElt != null && declareElt.getParent() != null && declareElt.getKind().isInterTypeMember()) { // checks to see if this element is valid // should be fully qualified type and simple name int lastDot = declareElt.getName().lastIndexOf('.'); String name = declareElt.getName().substring(lastDot + 1); if (declareElt.getKind() == IProgramElement.Kind.INTER_TYPE_FIELD) { createITDFieldText(sb, declareElt, name); } else if (declareElt.getKind() == IProgramElement.Kind.INTER_TYPE_METHOD || declareElt.getKind() == IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR) { createITDMethodText(currentTypeName, sb, declareElt, name); } } else if (declareElt.getKind() == IProgramElement.Kind.CLASS) { // Intertype Inner type // add the declaration and all fields/methhods createITITText(sb, declareElt); } } return sb.toString().toCharArray(); } } } return new char[0]; } /** * @param sb * @param declareElt */ protected void createITITText(StringBuffer sb, IProgramElement declareElt) { sb.append("\n\tstatic class ").append(declareElt.getName()).append(" {\n"); List<IProgramElement> children = declareElt.getChildren(); for (IProgramElement child : children) { sb.append("\t\tpublic static "); sb.append(child.getCorrespondingType(true) + " "); sb.append(child.getName()); if (child.getKind() == IProgramElement.Kind.FIELD) { sb.append(";\n"); } else { sb.append("("); List<String> names = child.getParameterNames(); List<char[]> types = child.getParameterTypes(); if (types != null && names != null) { for (Iterator<?> typeIter = types.iterator(), nameIter = names.iterator(); typeIter .hasNext();) { String paramType = String.valueOf((char[]) typeIter.next()); String paramName = (String) nameIter.next(); sb.append(paramType + " " + paramName); if (typeIter.hasNext()) { sb.append(", "); } } } sb.append(") { }\n"); } } sb.append("\t}\n"); } /** * @param currentTypeName * @param sb * @param declareElt * @param name */ protected void createITDMethodText(char[] currentTypeName, StringBuffer sb, IProgramElement declareElt, String name) { sb.append(getAccessibilityString(declareElt)); for (IProgramElement.Modifiers modifier : declareElt.getModifiers()) { sb.append(modifier + " "); } // need to add a return statement? if (declareElt.getKind() == IProgramElement.Kind.INTER_TYPE_METHOD) { sb.append(declareElt.getCorrespondingType(true) + " " + name); } else { sb.append(currentTypeName); } sb.append("("); List<String> names = declareElt.getParameterNames(); List<char[]> types = declareElt.getParameterTypes(); if (types != null && names != null) { for (Iterator<?> typeIter = types.iterator(), nameIter = names.iterator(); typeIter.hasNext();) { String paramType = String.valueOf((char[]) typeIter.next()); String paramName = (String) nameIter.next(); sb.append(paramType + " " + paramName); if (typeIter.hasNext()) { sb.append(", "); } } } sb.append(") { }\n"); } /** * @param sb * @param declareElt * @param name */ protected void createITDFieldText(StringBuffer sb, IProgramElement declareElt, String name) { for (IProgramElement.Modifiers modifier : declareElt.getModifiers()) { sb.append(modifier + " "); } sb.append(declareElt.getCorrespondingType(true) + " " + name + ";\n\t"); } private String getAccessibilityString(IProgramElement declareElt) { return (declareElt.getAccessibility() != Accessibility.PACKAGE ? declareElt.getAccessibility().toString() : "") + " "; } private boolean inTypeDeclaration() { return (inAspectDeclaration || inClassDeclaration || inInterfaceDeclaration || inEnumDeclaration); } /** * Inserts a reference to targetType at the given position. Thanks to this, * we can simulate the context switch necessary in intertype method * declarations. * * Transformations: - Insertion of local variable 'TargetType thiz' (or, if * thiz is already used, a number is added to thiz to make it unique) at * start of method mody * - if code completion on code like 'this.methodcall().more...', 'this * gets replaced by thiz * - if code completion on code like 'methodcall().more...', 'thiz.' gets * added in front. * * How the correct place for insertion is found: - * * */ private void simulateContextSwitch(int position, char[] targetType) { int pos = findInsertionPosition(position - 1) + 1; //if code completion on 'this' -> overwrite the this keyword int len = 0; boolean dotRequired = true; if ((content[pos] == 't') && (content[pos + 1] == 'h') && (content[pos + 2] == 'i') && (content[pos + 3] == 's') && !Character.isJavaIdentifierPart(content[pos + 4])) { len = 4; dotRequired = false; } String contextSwitchIdentifier = findFreeIdentifier(); char[] toInsert = (new String(targetType) + ' ' + contextSwitchIdentifier + "; " + contextSwitchIdentifier + (dotRequired ? "." : "")).toCharArray(); addReplacement(pos, len, toInsert); } /** * @return An unused identifier */ private String findFreeIdentifier() { int i = 0; String ident = ITD_INSERTED_IDENTIFIER + i; while (usedIdentifiers.contains(ident)) { i++; ident = ITD_INSERTED_IDENTIFIER + i; } usedIdentifiers.add(ident); return ident; } /** * @param pos - * a code position * @return the position that defines the context of the current one at the * highest level * * e.g. ' this.doSomthing().get' with pos on the last 't' returns the * position of the char before the first 't' */ private int findInsertionPosition(int pos) { char ch = content[pos]; int currentPos = pos; if (Character.isWhitespace(ch)) { currentPos = findPreviousNonSpace(pos); if (currentPos == -1) return pos; ch = content[currentPos]; if (ch == '.') return findInsertionPosition(--currentPos); return pos; } if (Character.isJavaIdentifierPart(ch)) { while (Character.isJavaIdentifierPart(ch)) { currentPos--; ch = content[currentPos]; } return findInsertionPosition(currentPos); } if (ch == '.') { return findInsertionPosition(--pos); } if (ch == ')') { currentPos--; int bracketCounter = 1; while (currentPos >= 0) { ch = content[currentPos]; if (bracketCounter == 0) break; if (ch == ')') bracketCounter++; if (ch == '(') { bracketCounter--; if (bracketCounter < 0) return -1; } currentPos--; } return findInsertionPosition(currentPos); } return pos; } private void applyReplacements() { Iterator<Replacement> iter = replacements.listIterator(); int offset = 0; while (iter.hasNext()) { Replacement ins = iter.next(); ins.posAfter = ins.posBefore + offset; replace(ins.posAfter, ins.length, ins.text); offset += ins.lengthAdded; } } private void replace(int pos, int origLength, char[] text) { if (origLength != text.length) { int lengthDiff = text.length - origLength; int oldEnd = pos + origLength; int newEnd = pos + text.length; char[] temp = new char[content.length + lengthDiff]; System.arraycopy(content, 0, temp, 0, pos); System.arraycopy(content, oldEnd, temp, newEnd, content.length - oldEnd); content = temp; } System.arraycopy(text, 0, content, pos, text.length); } private void startPointcutDesignator() { if (inPointcutDesignator) return; inPointcutDesignator = true; posColon = scanner.getCurrentTokenStartPosition(); } /** * */ private void endPointcutDesignator() { inPointcutDesignator = false; if (options.isKeepPointcuts()) { addReplacement(posColon, 1, new char[] { '{' }); } else { int posSemi = scanner.getCurrentTokenStartPosition(); int len = posSemi - posColon; char[] empty = new char[len]; for (int i = 0; i < empty.length; i++) { empty[i] = ' '; } addReplacement(posColon, len, empty); } } // Doesn't handle comments private void processPotentialIntertypeDeclaration() { // find the start of the declaration // by searching backwards //pos points to the final '.' int pos = scanner.getCurrentTokenStartPosition(); int iter = pos; iter--; while (iter >= 0) { // handles type parameter if (content[iter] == '>') { int genericsDepth = 1; iter--; while (iter >= 0 && genericsDepth > 0) { if (content[iter] == '>') { genericsDepth++; } else if (content[iter] == '<') { genericsDepth--; } iter--; } } else if (content[iter] == '<') { // we are looking at a qualified type within // a type parameter...return return; } if (Character.isWhitespace(content[iter])) { iter--; } else if (Character.isJavaIdentifierPart(content[iter])) { // seems like we are in an ITD break; } else { return; } } if (iter < 0) { return; } // now nameStart refers to the end of the name just before the last '.' // eg- it refers to 'p' in // p1.p2.Flop <? extends Baz>.doSomething() // next continue iterating back to find the start of the word. // if upper case, then assume it is an ITD while (iter >= 0) { if (Character.isJavaIdentifierPart(content[iter])) { iter--; } else { break; } } if (iter < 0 || !Character.isUpperCase(content[iter + 1])) { return; } int nameStart = iter + 1; // continue iterating back to find the last part of the qualification outer: while (true) { // find the previous '.' while (iter >= 0) { if (Character.isWhitespace(content[iter])) { iter--; } else if (content[iter] == '.') { nameStart = iter; break; } else { // something else was found // we are done break outer; } } iter--; // find end of previous name while (iter >= 0) { if (Character.isWhitespace(content[iter])) { iter--; } else if (Character.isJavaIdentifierPart(content[iter])) { nameStart = iter; break; } else { break outer; } } // find previous start of name iter--; while (iter >= 0) { if (Character.isJavaIdentifierPart(content[iter])) { iter--; } else { nameStart = iter + 1; break; } } } // last thing to do is to eat up whitespace after the '.' int nameEnd = pos + 1; while (nameEnd < content.length) { if (!Character.isWhitespace(content[nameEnd])) { break; } nameEnd++; } char[] itdName = new char[nameEnd - nameStart]; System.arraycopy(content, nameStart, itdName, 0, itdName.length); // now that we have the name, store the type name (and generics), if not fully qualified char[][] splits = CharOperation.splitOn('.', itdName); if (splits.length == 2) { typeReferences.add(String.valueOf(splits[0])); } // now that we have the entire length of the name, replace everything that is not // a valid Java identifier part with '$' for (int i = 0; i < itdName.length; i++) { if (!Character.isJavaIdentifierPart(itdName[i])) { itdName[i] = '$'; } } //if requested, add ajc$ in front of intertype declaration //e.g. "public int Circle$x;" -> "public int ajc$Circle$x;" if (options.isAddAjcTagToIntertypesEnabled()) { addReplacement(nameStart, 0, "ajc$".toCharArray()); //$NON-NLS-1$ } addReplacement(nameStart, itdName.length, itdName); } private boolean hasWordAtPosition(String string, int pos) { char[] word = string.toCharArray(); for (int i = 0; i < word.length; i++) { if (word[i] != content[pos + i]) { return false; } } return true; } public int findPrevious(char ch, int pos) { while (pos >= 0) { if (content[pos] == ch) return pos; pos--; } return -1; } public int findPreviousWhitespaceOr(char ch, int pos) { while (pos >= 0) { if (content[pos] == ch || Character.isWhitespace(content[pos])) { return pos; } pos--; } return -1; } public int findPrevious(char[] chs, int pos) { while (pos >= 0) { for (int i = 0; i < chs.length; i++) { if (content[pos] == chs[i]) return pos; } pos--; } return -1; } public int findPreviousSpace(int pos) { while (pos >= 0) { if (Character.isWhitespace(content[pos])) return pos; pos--; } return -1; } public int findPreviousNonSpace(int pos) { while (pos >= 0) { if (!Character.isWhitespace(content[pos])) return pos; pos--; } return -1; } public int findNextNonSpace(int pos) { while (pos < content.length) { if (!Character.isWhitespace(content[pos])) return pos; pos++; } return -1; } public int findNext(char[] chs, int pos) { while (pos < content.length) { for (int i = 0; i < chs.length; i++) { if (content[pos] == chs[i]) return pos; } pos++; } return -1; } private void consumeRetOrThrow() { int pos = scanner.getCurrentTokenStartPosition(); char[] content = scanner.source; int end = findNext(endThrow, pos); if (end == -1) return; char[] temp = null; if (content[end] == endThrow[0]) { pos = findPrevious(')', pos); if (pos == -1) return; int advicebracket = findPrevious('(', pos); if (advicebracket == -1) return; temp = new char[end - pos + 1]; if (bracketsContainSomething(advicebracket) && bracketsContainSomething(end)) temp[0] = ','; else temp[0] = ' '; for (int i = 1; i < temp.length; i++) { temp[i] = ' '; } } else { temp = new char[end - pos]; for (int i = 0; i < temp.length; i++) { temp[i] = ' '; } } addReplacement(pos, temp.length, temp); } /** * @param end * @return */ private boolean bracketsContainSomething(int start) { while (++start < content.length) { if (content[start] == ')') return false; if (Character.isJavaIdentifierPart(content[start])) return true; } return false; } private int findLast(char ch) { int pos = content.length; while (--pos >= 0) { if (content[pos] == ch) break; } return pos; } //adds references to all used type -> organize imports will work private void addReferences() { if (typeReferences == null) { return; } int pos = findLast('}'); if (pos < 0) return; StringBuffer temp = new StringBuffer(typeReferences.size() * 10); Iterator<String> iter = typeReferences.iterator(); while (iter.hasNext()) { String ref = (String) iter.next(); if (knownTypeParameters != null && knownTypeParameters.contains(ref)) { // don't add type parameters since they do not need to be imported continue; } temp.append(ref).append(" ").append(findFreeIdentifier()); //$NON-NLS-1$ temp.append(";\n"); } char[] decls = new char[temp.length()]; temp.getChars(0, decls.length, decls, 0); addReplacement(pos, 0, decls); } //adds a replacement to list //pre: list sorted, post: list sorted private void addReplacement(int pos, int length, char[] text) { int last = replacements.size() - 1; while (last >= 0) { if (((Replacement) replacements.get(last)).posBefore < pos) break; last--; } replacements.add(last + 1, new Replacement(pos, length, text)); } public static boolean conflictsWithAJEdit(int offset, int length, ArrayList<Replacement> replacements) { Replacement ins; for (int i = 0; i < replacements.size(); i++) { ins = replacements.get(i); if ((offset >= ins.posAfter) && (offset < ins.posAfter + ins.length)) { return true; } if ((offset < ins.posAfter) && (offset + length > ins.posAfter)) { return true; } } return false; } //translates a position from after to before changes //if the char at that position did not exist before, it returns the // position before the inserted area public static int translatePositionToBeforeChanges(int posAfter, ArrayList<Replacement> replacements) { Replacement ins; int offset = 0, i; for (i = 0; i < replacements.size(); i++) { ins = replacements.get(i); if (ins.posAfter > posAfter) break; offset += ins.lengthAdded; } if (i > 0) { ins = (Replacement) replacements.get(i - 1); if (ins.posAfter + ins.text.length > posAfter) { //diff must be > 0 int diff = posAfter - ins.posAfter; if (diff > ins.length) //we are in inserted area -> return pos directly before // that area offset += diff - ins.length; } } return Math.max(posAfter - offset, 0); } //translates a position from before to after changes public static int translatePositionToAfterChanges(int posBefore, ArrayList<Replacement> replacements) { for (int i = 0; i < replacements.size(); i++) { Replacement ins = replacements.get(i); if (ins.posAfter <= posBefore) posBefore += ins.lengthAdded; else return posBefore; } return posBefore; } }