| /******************************************************************************* |
| * Copyright (c) 2004 IBM Corporation 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 |
| *******************************************************************************/ |
| package org.eclipse.ajdt.core.codeconversion; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.aspectj.asm.IProgramElement; |
| import org.aspectj.asm.IRelationship; |
| 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.model.AJProjectModelFacade; |
| import org.eclipse.ajdt.core.model.AJProjectModelFactory; |
| import org.eclipse.ajdt.core.model.AJRelationshipManager; |
| import org.eclipse.ajdt.core.model.AJRelationshipType; |
| 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.IType; |
| 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$ |
| |
| private static final String thizString = "thiz"; //$NON-NLS-1$ |
| |
| private final static char[] tjpRefs2 = "org.aspectj.lang.JoinPoint thisJoinPoint; org.aspectj.lang.JoinPoint.StaticPart thisJoinPointStaticPart; org.aspectj.lang.JoinPoint.StaticPart thisEnclosingJoinPointStaticPart;" //$NON-NLS-1$ |
| .toCharArray(); |
| |
| private final static char[] endThrow = new char[] { '(', ':' }; |
| |
| private final static Pattern perClausePattern = Pattern.compile("(percflow)|(percflowbelow)|(pertarget)|(pertypewithin)|(perthis)|(issingleton)"); |
| |
| public 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(); |
| this.usedIdentifiers = new HashSet(); |
| replacements = new ArrayList(5); |
| } |
| |
| public char[] content; |
| |
| private Set typeReferences; |
| |
| private Set 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 replacements; |
| |
| protected Scanner scanner; |
| |
| private boolean inPointcutDesignator; |
| |
| private boolean inAspect; |
| |
| private boolean inAspectDeclaration; |
| |
| private boolean inClassDeclaration; |
| |
| private boolean inInterfaceDeclaration; |
| |
| 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 convert(ConversionOptions options) { |
| this.options = options; |
| boolean insertIntertypeDeclarations = options |
| .isThisJoinPointReferencesEnabled(); |
| boolean addReferencesForOrganizeImports = options |
| .isDummyTypeReferencesForOrganizeImportsEnabled(); |
| boolean isSimulateContextSwitchNecessary = (options.getTargetType() != null); |
| |
| scanner = new Scanner(); |
| scanner.setSource(content); |
| |
| inPointcutDesignator = false; |
| inAspect = false; |
| inAspectDeclaration = false; |
| inClassDeclaration = false; |
| inInterfaceDeclaration = 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 insideFor = false; |
| |
| char[] currentTypeName = null; |
| |
| replacements.clear(); |
| typeReferences.clear(); |
| usedIdentifiers.clear(); |
| |
| int tok; |
| int pos; |
| int typeDeclStart = 0; |
| while (true) { |
| |
| try { |
| tok = scanner.getNextToken(); |
| } catch (InvalidInputException e) { |
| continue; |
| } |
| if (tok == TokenNameEOF) |
| break; |
| |
| switch (tok) { |
| case TokenNameIdentifier: |
| if (!inAspect && !inTypeDeclaration()) |
| break; |
| |
| char[] name = scanner.getCurrentIdentifierSource(); |
| |
| 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 (CharOperation.equals(throwing, name)) |
| consumeRetOrThrow(); |
| else if (CharOperation.equals(returning, name)) |
| consumeRetOrThrow(); |
| else if (inPointcutDesignator |
| && Character.isUpperCase(name[0]) |
| && (content[scanner.getCurrentTokenStartPosition()-1]!='.')) { |
| typeReferences.add(new String(name)); |
| } |
| |
| if (isSimulateContextSwitchNecessary) { |
| usedIdentifiers.add(new String(name)); |
| } |
| break; |
| case TokenNamefor: |
| insideFor=true; |
| break; |
| case TokenNameLPAREN: |
| parenLevel++; |
| break; |
| case TokenNameRPAREN: |
| insideFor=false; |
| parenLevel--; |
| break; |
| case TokenNameCOLON: |
| if (!inAspect) |
| break; |
| if (insideFor) |
| break; |
| if (questionMarkCount > 0) { |
| questionMarkCount--; |
| break; |
| } |
| startPointcutDesignator(); |
| break; |
| |
| case TokenNameQUESTION: |
| questionMarkCount++; |
| break; |
| |
| case TokenNameSEMICOLON: |
| if (inPointcutDesignator) |
| endPointcutDesignator(); |
| break; |
| |
| |
| |
| case TokenNameDOT: |
| if (!inAspect) { |
| break; |
| } else if (inPointcutDesignator) { |
| break; |
| } else if (parenLevel > 0) { |
| // don't want to convert '.' to '$' inside the parameter declaration |
| break; |
| } |
| processPotentialIntertypeDeclaration(); |
| break; |
| |
| case TokenNameLBRACE: |
| if (inPointcutDesignator) { |
| endPointcutDesignator(); |
| //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); |
| } |
| } |
| |
| |
| inAspectDeclaration = false; |
| inClassDeclaration = false; |
| inInterfaceDeclaration = false; |
| currentTypeName = null; |
| |
| // insideBlockCount ++; |
| break; |
| case TokenNameRBRACE: |
| // insideBlockCount ++; |
| if (inPointcutDesignator) { |
| // bug 129367: if we've hit a } here, we must be |
| // in the middle of an unterminated pointcut |
| endPointcutDesignator(); |
| } |
| break; |
| case TokenNameaspect: |
| 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 TokenNameclass: |
| typeDeclStart = pos = scanner.getCurrentTokenStartPosition(); |
| inClassDeclaration = true; |
| break; |
| |
| case TokenNameinterface: // interface and @interface |
| typeDeclStart = pos = scanner.getCurrentTokenStartPosition(); |
| inInterfaceDeclaration = true; |
| break; |
| |
| case TokenNameprivileged: |
| pos = scanner.getCurrentTokenStartPosition(); |
| addReplacement(pos, privileged.length, privileged); |
| break; |
| |
| } |
| |
| } |
| |
| 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(); |
| |
| //System.out.println(new String(content)); |
| return replacements; |
| } |
| |
| /** |
| * |
| * @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 |
| */ |
| protected char[] createImplementExtendsITDs(char[] typeName) { |
| |
| if (unit != null) { |
| IType type = unit.getType(new String(typeName)); |
| if (type.exists()) { |
| try { |
| StringBuffer sb = new StringBuffer(); |
| sb.append(type.isInterface() ? "interface " : "class "); |
| sb.append(typeName); |
| |
| List[] declares = getDeclareExtendsImplements(type); |
| List declareExtends = declares[0]; |
| List declareImplements = declares[1]; |
| if (! type.isInterface()) { |
| 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(); |
| for (int i = 0; i < superInterfaces.length; i++) { |
| declareImplements.add(superInterfaces[i]); |
| } |
| |
| if (declareImplements.size() > 0) { |
| |
| if (type.isInterface()) { |
| sb.append(" " + EXTENDS + " "); |
| } else { |
| sb.append(" " + IMPLEMENTS + " "); |
| } |
| |
| for (Iterator interfaceIter = declareImplements.iterator(); interfaceIter |
| .hasNext();) { |
| String interName = (String) interfaceIter.next(); |
| interName = interName.replace('$', '.'); |
| sb.append(" " + interName); |
| if (interfaceIter.hasNext()) { |
| 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; |
| } |
| |
| |
| |
| |
| /** |
| * @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 declareExtends = new ArrayList(); |
| List declareImplements = new ArrayList(); |
| if (type != null && type.exists()) { |
| AJProjectModelFacade model = AJProjectModelFactory.getInstance().getModelForJavaElement(type); |
| if (model.hasModel()) { |
| List /*IJavaElement*/ rels = model.getRelationshipsForElement(type, AJRelationshipManager.ASPECT_DECLARATIONS); |
| for (Iterator eltIter = rels.iterator(); eltIter.hasNext();) { |
| IJavaElement je = (IJavaElement) eltIter.next(); |
| IProgramElement pe = model.javaElementToProgramElement(je); |
| if (pe.getKind() == IProgramElement.Kind.DECLARE_PARENTS) { |
| List/*String*/ parentTypes = pe.getParentTypes(); |
| String details = pe.getDetails(); |
| if (details.startsWith(EXTENDS)) { |
| declareExtends.addAll(parentTypes); |
| } else if (details.startsWith(IMPLEMENTS)) { |
| declareImplements.add(parentTypes); |
| } |
| } |
| } |
| } |
| } |
| return new List[] { declareExtends, declareImplements }; |
| } |
| |
| protected char[] getInterTypeDecls(char[] currentTypeName) { |
| if (unit != null) { |
| AJProjectModelFacade model = AJProjectModelFactory.getInstance().getModelForJavaElement(unit); |
| if (model.hasModel()) { |
| IType type = unit.getType(new String(currentTypeName)); |
| if (type.exists()) { |
| List /*IJavaElement*/ rels = model.getRelationshipsForElement(type, AJRelationshipManager.ASPECT_DECLARATIONS); |
| StringBuffer sb = new StringBuffer("\n\t"); |
| for (Iterator relIter = rels.iterator(); relIter.hasNext();) { |
| IJavaElement je = (IJavaElement) relIter.next(); |
| 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) { |
| sb.append(declareElt.getCorrespondingType(true) + " " + name + ";\n"); |
| } else if (declareElt.getKind() == IProgramElement.Kind.INTER_TYPE_METHOD || |
| declareElt.getKind() == IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR) { |
| // 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/*String*/ types = declareElt.getParameterTypes(); |
| for (Iterator typeIter = types.iterator(), nameIter = names.iterator(); |
| typeIter.hasNext();) { |
| String paramType = new String((char[]) typeIter.next()); |
| String paramName = (String) nameIter.next(); |
| sb.append(paramType + " " + paramName); |
| if (typeIter.hasNext()) { |
| sb.append(", "); |
| } |
| } |
| sb.append(") { }\n"); |
| } |
| } |
| } |
| return sb.toString().toCharArray(); |
| } |
| } |
| } |
| |
| return new char[0]; |
| } |
| |
| private boolean inTypeDeclaration() { |
| return (inAspectDeclaration || |
| inClassDeclaration || |
| inInterfaceDeclaration); |
| } |
| |
| /** |
| * 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; |
| if ((content[pos] == 't') && (content[pos + 1] == 'h') |
| && (content[pos + 2] == 'i') && (content[pos + 3] == 's') |
| && !Character.isJavaIdentifierPart(content[pos + 4])) |
| len = 4; |
| |
| String ident = findFreeIdentifier(); |
| char[] toInsert = (new String(targetType) + ' ' + ident + ';' + ident + '.') |
| .toCharArray(); |
| addReplacement(pos, len, toInsert); |
| |
| } |
| |
| /** |
| * @return An unused identifier |
| */ |
| private String findFreeIdentifier() { |
| int i = 0; |
| String ident = thizString + i; |
| while (usedIdentifiers.contains(ident)) { |
| i++; |
| ident = thizString + i; |
| } |
| 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 iter = replacements.listIterator(); |
| int offset = 0; |
| while (iter.hasNext()) { |
| Replacement ins = (Replacement) 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; |
| 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); |
| } |
| |
| //identifies intertype declaration of form 'type qualifier.membername' |
| //and replaces every '.' by '$'. |
| //e.g. "int tracing.Circle.x;" -> "int tracing$Circle$x;" |
| private void processPotentialIntertypeDeclaration() { |
| |
| //pos points to the '.' |
| int pos = scanner.getCurrentTokenStartPosition(); |
| |
| //check if valid identifier char on left side of dot |
| //(to sort out construct like '(new Object()).' ) |
| int nonspace1 = findPreviousNonSpace(pos - 1); |
| if (nonspace1 == -1) |
| return; |
| if (!Character.isJavaIdentifierPart(content[nonspace1])) |
| return; |
| |
| //check if there is another java identifier before qualifier, |
| //if no, return (to sort out method calls and the like) |
| int space = findPreviousSpace(nonspace1); |
| if (space == -1) { |
| return; |
| } |
| int nonspace2 = findPreviousNonSpace(space); |
| if (nonspace2 == -1) { |
| return; |
| } |
| if (!Character.isJavaIdentifierPart(content[nonspace2]) && |
| content[nonspace2] != '>') { // could be a parameterized type |
| // but still could be part of a "new" ITD |
| int nextNonSpace = findNextNonSpace(pos+1); |
| if (!hasWordAtPosition("new", nextNonSpace)) { |
| // XXX problem here is that this doesn't account for comments |
| return; |
| } |
| } |
| //check if rightmost part of qualifier starts with Capital letter, |
| //and if yes, assume it is a Class name -> intertype declaration |
| int spaceordot = findPreviousWhitespaceOr(',', nonspace1); |
| if (spaceordot == -1) |
| return; |
| if (Character.isUpperCase(content[spaceordot + 1])) { |
| |
| //assume intertype declaration and replace all '.' by '$' |
| char[] rep = new char[] { '$' }; |
| addReplacement(pos, 1, rep); |
| |
| if (content[spaceordot] == ' ') { |
| String type = new String(content, space + 1, pos - space - 1); |
| boolean validIdentifier = true; |
| for (int i = 0; validIdentifier && (i < type.length()); i++) { |
| char c = type.charAt(i); |
| if (i==0) { |
| if (!Character.isJavaIdentifierStart(c)) { |
| validIdentifier = false; |
| } |
| } else if (!Character.isJavaIdentifierPart(c)) { |
| validIdentifier = false; |
| } |
| } |
| if (validIdentifier) { |
| typeReferences.add(type); |
| } |
| } else { |
| do { |
| addReplacement(spaceordot, 1, rep); |
| spaceordot = findPreviousWhitespaceOr(',', --spaceordot); |
| } while (content[spaceordot] == '.'); |
| } |
| |
| //if requested, add ajc$ in front of intertype declaration |
| //e.g. "public int Circle$x;" -> "public int ajc$Circle$x;" |
| if (options.isAddAjcTagToIntertypesEnabled()) { |
| addReplacement(spaceordot + 1, 0, "ajc$".toCharArray()); //$NON-NLS-1$ |
| } |
| |
| } |
| } |
| |
| 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; |
| |
| //char[] decl = new char[] { ' ', 'x', ';' }; |
| int pos = findLast('}'); |
| if (pos < 0) |
| return; |
| StringBuffer temp = new StringBuffer(typeReferences.size() * 10); |
| Iterator iter = typeReferences.iterator(); |
| int varCount=1; |
| while (iter.hasNext()) { |
| String ref = (String) iter.next(); |
| temp.append(ref); |
| temp.append(" x"); //$NON-NLS-1$ |
| temp.append(varCount++); |
| temp.append(';'); |
| } |
| 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 replacements) { |
| Replacement ins; |
| for (int i = 0; i < replacements.size(); i++) { |
| ins = (Replacement) 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 replacements) { |
| Replacement ins; |
| int offset = 0, i; |
| |
| for (i = 0; i < replacements.size(); i++) { |
| ins = (Replacement) 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 posAfter - offset; |
| } |
| |
| //translates a position from before to after changes |
| public static int translatePositionToAfterChanges(int posBefore, |
| ArrayList replacements) { |
| for (int i = 0; i < replacements.size(); i++) { |
| Replacement ins = (AspectsConvertingParser.Replacement) replacements |
| .get(i); |
| if (ins.posAfter <= posBefore) |
| posBefore += ins.lengthAdded; |
| else |
| return posBefore; |
| } |
| return posBefore; |
| } |
| |
| } |