diff options
author | Andy Clement <aclement@pivotal.io> | 2019-01-23 19:36:37 -0800 |
---|---|---|
committer | Andy Clement <aclement@pivotal.io> | 2019-01-23 19:36:37 -0800 |
commit | d9cd0d44e8c42f06e13b033ae1cc269d3e7f7c29 (patch) | |
tree | 821258f70f7f28e3e96faa82379708a038c84128 /asm/src/main | |
parent | 9803f22ec4337779057f4ec2ace35f2d6483d6dd (diff) | |
download | aspectj-d9cd0d44e8c42f06e13b033ae1cc269d3e7f7c29.tar.gz aspectj-d9cd0d44e8c42f06e13b033ae1cc269d3e7f7c29.zip |
mavenizing asm module - wip
Diffstat (limited to 'asm/src/main')
17 files changed, 5097 insertions, 0 deletions
diff --git a/asm/src/main/java/org/aspectj/asm/AsmManager.java b/asm/src/main/java/org/aspectj/asm/AsmManager.java new file mode 100644 index 000000000..2ab34c862 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/AsmManager.java @@ -0,0 +1,1305 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * 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: + * Mik Kersten initial implementation + * Andy Clement incremental support and switch on/off state + * ******************************************************************/ + +package org.aspectj.asm; + +import java.io.BufferedWriter; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.aspectj.asm.internal.AspectJElementHierarchy; +import org.aspectj.asm.internal.HandleProviderDelimiter; +import org.aspectj.asm.internal.JDTLikeHandleProvider; +import org.aspectj.asm.internal.RelationshipMap; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.util.IStructureModel; + +/** + * The Abstract Structure Model (ASM) represents the containment hierarchy and crosscutting structure map for AspectJ programs. It + * is used by IDE views such as the document outline, and by other tools such as ajdoc to show both AspectJ declarations and + * crosscutting links, such as which advice affects which join point shadows. + * + * @author Mik Kersten + * @author Andy Clement + */ +public class AsmManager implements IStructureModel { + + // For testing ONLY + public static boolean recordingLastActiveStructureModel = true; + public static AsmManager lastActiveStructureModel; + public static boolean forceSingletonBehaviour = false; + + // SECRETAPI asc pull the secret options together into a system API you lazy fool + public static boolean attemptIncrementalModelRepairs = false; + // Dumping the model is expensive + public static boolean dumpModelPostBuild = false; + // For offline debugging, you can now ask for the AsmManager to + // dump the model - see the method setReporting() + private static boolean dumpModel = false; + private static boolean dumpRelationships = false; + private static boolean dumpDeltaProcessing = false; + private static IModelFilter modelFilter = null; + private static String dumpFilename = ""; + private static boolean reporting = false; + + private static boolean completingTypeBindings = false; + + private final List<IHierarchyListener> structureListeners = new ArrayList<IHierarchyListener>(); + + // The model is 'manipulated' by the AjBuildManager.setupModel() code which + // trashes all the + // fields when setting up a new model for a batch build. + // Due to the requirements of incremental compilation we need to tie some of + // the info + // below to the AjState for a compilation and recover it if switching + // between projects. + protected IHierarchy hierarchy; + + /* + * Map from String > String - it maps absolute paths for inpath dirs/jars to workspace relative paths suitable for handle + * inclusion + */ + protected Map<File, String> inpathMap; + private IRelationshipMap mapper; + private IElementHandleProvider handleProvider; + + private final CanonicalFilePathMap canonicalFilePathMap = new CanonicalFilePathMap(); + // Record the Set<File> for which the model has been modified during the + // last incremental build + private final Set<File> lastBuildChanges = new HashSet<File>(); + + // Record the Set<File> of aspects that wove the files listed in lastBuildChanges + final Set<File> aspectsWeavingInLastBuild = new HashSet<File>(); + + // static { + // setReporting("c:/model.nfo",true,true,true,true); + // } + + private AsmManager() { + } + + public static AsmManager createNewStructureModel(Map<File, String> inpathMap) { + if (forceSingletonBehaviour && lastActiveStructureModel != null) { + return lastActiveStructureModel; + } + AsmManager asm = new AsmManager(); + asm.inpathMap = inpathMap; + asm.hierarchy = new AspectJElementHierarchy(asm); + asm.mapper = new RelationshipMap(); + asm.handleProvider = new JDTLikeHandleProvider(asm); + // call initialize on the handleProvider when we create a new ASM + // to give handleProviders the chance to reset any state + asm.handleProvider.initialize(); + asm.resetDeltaProcessing(); + setLastActiveStructureModel(asm); + return asm; + } + + public IHierarchy getHierarchy() { + return hierarchy; + } + + public IRelationshipMap getRelationshipMap() { + return mapper; + } + + public void fireModelUpdated() { + notifyListeners(); + if (dumpModelPostBuild && hierarchy.getConfigFile() != null) { + writeStructureModel(hierarchy.getConfigFile()); + } + } + + /** + * Constructs map each time it's called. + */ + public HashMap<Integer, List<IProgramElement>> getInlineAnnotations(String sourceFile, boolean showSubMember, + boolean showMemberAndType) { + + if (!hierarchy.isValid()) { + return null; + } + + HashMap<Integer, List<IProgramElement>> annotations = new HashMap<Integer, List<IProgramElement>>(); + IProgramElement node = hierarchy.findElementForSourceFile(sourceFile); + if (node == IHierarchy.NO_STRUCTURE) { + return null; + } else { + IProgramElement fileNode = node; + ArrayList<IProgramElement> peNodes = new ArrayList<IProgramElement>(); + getAllStructureChildren(fileNode, peNodes, showSubMember, showMemberAndType); + for (Iterator<IProgramElement> it = peNodes.iterator(); it.hasNext();) { + IProgramElement peNode = it.next(); + List<IProgramElement> entries = new ArrayList<IProgramElement>(); + entries.add(peNode); + ISourceLocation sourceLoc = peNode.getSourceLocation(); + if (null != sourceLoc) { + Integer hash = new Integer(sourceLoc.getLine()); + List<IProgramElement> existingEntry = annotations.get(hash); + if (existingEntry != null) { + entries.addAll(existingEntry); + } + annotations.put(hash, entries); + } + } + return annotations; + } + } + + private void getAllStructureChildren(IProgramElement node, List<IProgramElement> result, boolean showSubMember, + boolean showMemberAndType) { + List<IProgramElement> children = node.getChildren(); + if (node.getChildren() == null) { + return; + } + for (IProgramElement next : children) { + List<IRelationship> rels = mapper.get(next); + if (next != null + && ((next.getKind() == IProgramElement.Kind.CODE && showSubMember) || (next.getKind() != IProgramElement.Kind.CODE && showMemberAndType)) + && rels != null && rels.size() > 0) { + result.add(next); + } + getAllStructureChildren(next, result, showSubMember, showMemberAndType); + } + } + + public void addListener(IHierarchyListener listener) { + structureListeners.add(listener); + } + + public void removeStructureListener(IHierarchyListener listener) { + structureListeners.remove(listener); + } + + // this shouldn't be needed - but none of the people that add listeners + // in the test suite ever remove them. AMC added this to be called in + // setup() so that the test cases would cease leaking listeners and go + // back to executing at a reasonable speed. + public void removeAllListeners() { + structureListeners.clear(); + } + + private void notifyListeners() { + for (IHierarchyListener listener : structureListeners) { + listener.elementsUpdated(hierarchy); + } + } + + public IElementHandleProvider getHandleProvider() { + return handleProvider; + } + + public void setHandleProvider(IElementHandleProvider handleProvider) { + this.handleProvider = handleProvider; + } + + public void writeStructureModel(String configFilePath) { + try { + String filePath = genExternFilePath(configFilePath); + FileOutputStream fos = new FileOutputStream(filePath); + ObjectOutputStream s = new ObjectOutputStream(fos); + s.writeObject(hierarchy); // Store the program element tree + s.writeObject(mapper); // Store the relationships + s.flush(); + fos.flush(); + fos.close(); + s.close(); + } catch (IOException e) { + // System.err.println("AsmManager: Unable to write structure model: " + // +configFilePath+" because of:"); + // e.printStackTrace(); + } + } + + /** + * @param configFilePath path to an ".lst" file + */ + public void readStructureModel(String configFilePath) { + boolean hierarchyReadOK = false; + try { + if (configFilePath == null) { + hierarchy.setRoot(IHierarchy.NO_STRUCTURE); + } else { + String filePath = genExternFilePath(configFilePath); + FileInputStream in = new FileInputStream(filePath); + ObjectInputStream s = new ObjectInputStream(in); + hierarchy = (AspectJElementHierarchy) s.readObject(); + ((AspectJElementHierarchy) hierarchy).setAsmManager(this); + hierarchyReadOK = true; + mapper = (RelationshipMap) s.readObject(); + s.close(); + } + } catch (FileNotFoundException fnfe) { + // That is OK + hierarchy.setRoot(IHierarchy.NO_STRUCTURE); + } catch (EOFException eofe) { + // Might be an old format sym file that is missing its relationships + if (!hierarchyReadOK) { + System.err.println("AsmManager: Unable to read structure model: " + configFilePath + " because of:"); + eofe.printStackTrace(); + hierarchy.setRoot(IHierarchy.NO_STRUCTURE); + } + } catch (Exception e) { + // System.err.println("AsmManager: Unable to read structure model: "+ + // configFilePath+" because of:"); + // e.printStackTrace(); + hierarchy.setRoot(IHierarchy.NO_STRUCTURE); + } finally { + notifyListeners(); + } + } + + private String genExternFilePath(String configFilePath) { + // sometimes don't have ".lst" + if (configFilePath.lastIndexOf(".lst") != -1) { + configFilePath = configFilePath.substring(0, configFilePath.lastIndexOf(".lst")); + } + return configFilePath + ".ajsym"; + } + + public String getCanonicalFilePath(File f) { + return canonicalFilePathMap.get(f); + } + + public CanonicalFilePathMap getCanonicalFilePathMap() { + return canonicalFilePathMap; + } + + private static class CanonicalFilePathMap { + private static final int MAX_SIZE = 4000; + + private final Map<String, String> pathMap = new HashMap<String, String>(20); + + // // guards to ensure correctness and liveness + // private boolean cacheInUse = false; + // private boolean stopRequested = false; + // + // private synchronized boolean isCacheInUse() { + // return cacheInUse; + // } + // + // private synchronized void setCacheInUse(boolean val) { + // cacheInUse = val; + // if (val) { + // notifyAll(); + // } + // } + // + // private synchronized boolean isStopRequested() { + // return stopRequested; + // } + // + // private synchronized void requestStop() { + // stopRequested = true; + // } + // + // /** + // * Begin prepopulating the map by adding an entry from + // * file.getPath -> file.getCanonicalPath for each file in + // * the list. Do this on a background thread. + // * @param files + // */ + // public void prepopulate(final List files) { + // stopRequested = false; + // setCacheInUse(false); + // if (pathMap.size() > MAX_SIZE) { + // pathMap.clear(); + // } + // new Thread() { + // public void run() { + // System.out.println("Starting cache population: " + + // System.currentTimeMillis()); + // Iterator it = files.iterator(); + // while (!isStopRequested() && it.hasNext()) { + // File f = (File)it.next(); + // if (pathMap.get(f.getPath()) == null) { + // // may reuse cache across compiles from ides... + // try { + // pathMap.put(f.getPath(),f.getCanonicalPath()); + // } catch (IOException ex) { + // pathMap.put(f.getPath(),f.getPath()); + // } + // } + // } + // System.out.println("Cached " + files.size()); + // setCacheInUse(true); + // System.out.println("Cache populated: " + System.currentTimeMillis()); + // } + // }.start(); + // } + // + // /** + // * Stop pre-populating the cache - our customers are ready to use it. + // * If there are any cache misses from this point on, we'll populate + // the + // * cache as we go. + // * The handover is done this way to ensure that only one thread is + // ever + // * accessing the cache, and that we minimize synchronization. + // */ + // public synchronized void handover() { + // if (!isCacheInUse()) { + // requestStop(); + // try { + // while (!isCacheInUse()) wait(); + // } catch (InterruptedException intEx) { } // just continue + // } + // } + + public String get(File f) { + // if (!cacheInUse) { // unsynchronized test - should never be + // parallel + // // threads at this point + // throw new IllegalStateException( + // "Must take ownership of cache before using by calling " + + // "handover()"); + // } + String ret = pathMap.get(f.getPath()); + if (ret == null) { + try { + ret = f.getCanonicalPath(); + } catch (IOException ioEx) { + ret = f.getPath(); + } + pathMap.put(f.getPath(), ret); + if (pathMap.size() > MAX_SIZE) { + pathMap.clear(); + } + } + return ret; + } + } + + // SECRETAPI + public static void setReporting(String filename, boolean dModel, boolean dRels, boolean dDeltaProcessing, boolean deletefile) { + reporting = true; + dumpModel = dModel; + dumpRelationships = dRels; + dumpDeltaProcessing = dDeltaProcessing; + if (deletefile) { + new File(filename).delete(); + } + dumpFilename = filename; + } + + public static void setReporting(String filename, boolean dModel, boolean dRels, boolean dDeltaProcessing, boolean deletefile, + IModelFilter aFilter) { + setReporting(filename, dModel, dRels, dDeltaProcessing, deletefile); + modelFilter = aFilter; + } + + public static boolean isReporting() { + return reporting; + } + + public static void setDontReport() { + reporting = false; + dumpDeltaProcessing = false; + dumpModel = false; + dumpRelationships = false; + } + + // NB. If the format of this report changes then the model tests + // (@see org.aspectj.systemtest.model.ModelTestCase) will fail in + // their comparison. The tests are assuming that both the model + // and relationship map are reported and as a consequence single + // testcases test that both the model and relationship map are correct. + public void reportModelInfo(String reasonForReport) { + if (!dumpModel && !dumpRelationships) { + return; + } + try { + FileWriter fw = new FileWriter(dumpFilename, true); + BufferedWriter bw = new BufferedWriter(fw); + if (dumpModel) { + bw.write("=== MODEL STATUS REPORT ========= " + reasonForReport + "\n"); + dumptree(bw, hierarchy.getRoot(), 0); + + bw.write("=== END OF MODEL REPORT =========\n"); + } + if (dumpRelationships) { + bw.write("=== RELATIONSHIPS REPORT ========= " + reasonForReport + "\n"); + dumprels(bw); + bw.write("=== END OF RELATIONSHIPS REPORT ==\n"); + } + Properties p = summarizeModel().getProperties(); + Enumeration<Object> pkeyenum = p.keys(); + bw.write("=== Properties of the model and relationships map =====\n"); + while (pkeyenum.hasMoreElements()) { + String pkey = (String) pkeyenum.nextElement(); + bw.write(pkey + "=" + p.getProperty(pkey) + "\n"); + } + bw.flush(); + fw.close(); + } catch (IOException e) { + System.err.println("InternalError: Unable to report model information:"); + e.printStackTrace(); + } + } + + public static void dumptree(Writer w, IProgramElement node, int indent) throws IOException { + for (int i = 0; i < indent; i++) { + w.write(" "); + } + String loc = ""; + if (node != null) { + if (node.getSourceLocation() != null) { + loc = node.getSourceLocation().toString(); + if (modelFilter != null) { + loc = modelFilter.processFilelocation(loc); + } + } + } + w.write(node + " [" + (node == null ? "null" : node.getKind().toString()) + "] " + loc + "\n"); + if (node != null) { + for (IProgramElement child : node.getChildren()) { + dumptree(w, child, indent + 2); + } + } + } + + public static void dumptree(IProgramElement node, int indent) throws IOException { + for (int i = 0; i < indent; i++) { + System.out.print(" "); + } + String loc = ""; + if (node != null) { + if (node.getSourceLocation() != null) { + loc = node.getSourceLocation().toString(); + } + } + System.out.println(node + " [" + (node == null ? "null" : node.getKind().toString()) + "] " + loc); + if (node != null) { + for (IProgramElement child : node.getChildren()) { + dumptree(child, indent + 2); + } + } + } + + public void dumprels(Writer w) throws IOException { + int ctr = 1; + Set<String> entries = mapper.getEntries(); + for (String hid : entries) { + List<IRelationship> rels = mapper.get(hid); + for (IRelationship ir : rels) { + List<String> targets = ir.getTargets(); + for (String thid : targets) { + StringBuffer sb = new StringBuffer(); + if (modelFilter == null || modelFilter.wantsHandleIds()) { + sb.append("Hid:" + (ctr++) + ":"); + } + sb.append("(targets=" + targets.size() + ") " + hid + " (" + ir.getName() + ") " + thid + "\n"); + w.write(sb.toString()); + } + } + } + } + + private void dumprelsStderr(String key) { + System.err.println("Relationships dump follows: " + key); + int ctr = 1; + Set<String> entries = mapper.getEntries(); + for (String hid : entries) { + for (IRelationship ir : mapper.get(hid)) { + List<String> targets = ir.getTargets(); + for (String thid : targets) { + System.err.println("Hid:" + (ctr++) + ":(targets=" + targets.size() + ") " + hid + " (" + ir.getName() + ") " + + thid); + } + } + } + System.err.println("End of relationships dump for: " + key); + } + + // ===================== DELTA PROCESSING CODE ============== start + // ==========// + + /** + * Removes the hierarchy structure for the specified files from the structure model. Returns true if it deleted anything + */ + public boolean removeStructureModelForFiles(Writer fw, Collection<File> files) throws IOException { + + boolean modelModified = false; + + Set<String> deletedNodes = new HashSet<String>(); + for (File fileForCompilation : files) { + String correctedPath = getCanonicalFilePath(fileForCompilation); + IProgramElement progElem = (IProgramElement) hierarchy.findInFileMap(correctedPath); + if (progElem != null) { + // Found it, let's remove it + if (dumpDeltaProcessing) { + fw.write("Deleting " + progElem + " node for file " + fileForCompilation + "\n"); + } + removeNode(progElem); + lastBuildChanges.add(fileForCompilation); + deletedNodes.add(getCanonicalFilePath(progElem.getSourceLocation().getSourceFile())); + if (!hierarchy.removeFromFileMap(correctedPath)) { + throw new RuntimeException("Whilst repairing model, couldn't remove entry for file: " + correctedPath + + " from the filemap"); + } + modelModified = true; + } + } + if (modelModified) { + hierarchy.updateHandleMap(deletedNodes); + } + return modelModified; + } + + public void processDelta(Collection<File> files_tobecompiled, Set<File> files_added, Set<File> files_deleted) { + + try { + Writer fw = null; + + // Are we recording this ? + if (dumpDeltaProcessing) { + FileWriter filew = new FileWriter(dumpFilename, true); + fw = new BufferedWriter(filew); + fw.write("=== Processing delta changes for the model ===\n"); + fw.write("Files for compilation:#" + files_tobecompiled.size() + ":" + files_tobecompiled + "\n"); + fw.write("Files added :#" + files_added.size() + ":" + files_added + "\n"); + fw.write("Files deleted :#" + files_deleted.size() + ":" + files_deleted + "\n"); + } + + long stime = System.currentTimeMillis(); + + // Let's remove all the files that are deleted on this compile + removeStructureModelForFiles(fw, files_deleted); + long etime1 = System.currentTimeMillis(); // etime1-stime = time to + // fix up the model + + repairRelationships(fw); + long etime2 = System.currentTimeMillis(); // etime2-stime = time to + // repair the + // relationship map + + removeStructureModelForFiles(fw, files_tobecompiled); + + if (dumpDeltaProcessing) { + fw.write("===== Delta Processing timing ==========\n"); + fw.write("Hierarchy=" + (etime1 - stime) + "ms Relationshipmap=" + (etime2 - etime1) + "ms\n"); + fw.write("===== Traversal ========================\n"); + // fw.write("Source handles processed="+srchandlecounter+"\n"); + // fw.write("Target handles processed="+tgthandlecounter+"\n"); + fw.write("========================================\n"); + fw.flush(); + fw.close(); + + } + reportModelInfo("After delta processing"); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private String getTypeNameFromHandle(String handle, Map<String, String> cache) { + try { + String typename = cache.get(handle); + if (typename != null) { + return typename; + } + // inpath handle - but for which type? + // let's do it the slow way, we can optimize this with a cache perhaps + int hasPackage = handle.indexOf(HandleProviderDelimiter.PACKAGEFRAGMENT.getDelimiter()); + int typeLocation = handle.indexOf(HandleProviderDelimiter.TYPE.getDelimiter()); + if (typeLocation == -1) { + typeLocation = handle.indexOf(HandleProviderDelimiter.ASPECT_TYPE.getDelimiter()); + } + if (typeLocation == -1) { + // unexpected - time to give up + return ""; + } + StringBuffer qualifiedTypeNameFromHandle = new StringBuffer(); + if (hasPackage != -1) { + int classfileLoc = handle.indexOf(HandleProviderDelimiter.CLASSFILE.getDelimiter(), hasPackage); + qualifiedTypeNameFromHandle.append(handle.substring(hasPackage + 1, classfileLoc)); + qualifiedTypeNameFromHandle.append('.'); + } + qualifiedTypeNameFromHandle.append(handle.substring(typeLocation + 1)); + typename = qualifiedTypeNameFromHandle.toString(); + cache.put(handle, typename); + return typename; + } catch (StringIndexOutOfBoundsException sioobe) { + // debug for 330170 + System.err.println("Handle processing problem, the handle is: " + handle); + sioobe.printStackTrace(System.err); + return ""; + } + } + + /** + * two kinds of relationships + * + * A affects B B affectedBy A + * + * Both of these relationships are added when 'B' is modified. Concrete examples are 'advises/advisedby' or + * 'annotates/annotatedby'. + * + * What we need to do is when 'B' is going to be woven, remove all relationships that may reoccur when it is woven. So - remove + * 'affects' relationships where the target is 'B', remove all 'affectedBy' relationships where the source is 'B'. + * + */ + public void removeRelationshipsTargettingThisType(String typename) { + boolean debug = false; + if (debug) { + System.err.println(">>removeRelationshipsTargettingThisType " + typename); + } + String pkg = null; + String type = typename; + int lastSep = typename.lastIndexOf('.'); + if (lastSep != -1) { + pkg = typename.substring(0, lastSep); + type = typename.substring(lastSep + 1); + } + boolean didsomething = false; + IProgramElement typeNode = hierarchy.findElementForType(pkg, type); + + // Reasons for that being null: + // 1. the file has fundamental errors and so doesn't exist in the model + // (-proceedOnError probably forced us to weave) + if (typeNode == null) { + return; + } + + Set<String> sourcesToRemove = new HashSet<String>(); + Map<String, String> handleToTypenameCache = new HashMap<String, String>(); + // Iterate over the source handles in the relationships map, the aim + // here is to remove any 'affected by' + // relationships where the source of the relationship is the specified + // type (since it will be readded + // when the type is woven) + Set<String> sourcehandlesSet = mapper.getEntries(); + List<IRelationship> relationshipsToRemove = new ArrayList<IRelationship>(); + for (String hid : sourcehandlesSet) { + if (isPhantomHandle(hid)) { + // inpath handle - but for which type? + // TODO promote cache for reuse during one whole model update + if (!getTypeNameFromHandle(hid, handleToTypenameCache).equals(typename)) { + continue; + } + } + IProgramElement sourceElement = hierarchy.getElement(hid); + if (sourceElement == null || sameType(hid, sourceElement, typeNode)) { + // worth continuing as there may be a relationship to remove + relationshipsToRemove.clear(); + List<IRelationship> relationships = mapper.get(hid); + for (IRelationship relationship : relationships) { + if (relationship.getKind() == IRelationship.Kind.USES_POINTCUT) { + continue; // these relationships are added at compile + } + // time, argh + if (relationship.isAffects()) { + continue; // we want 'affected by' relationships - (e.g. + } + // advised by) + relationshipsToRemove.add(relationship); // all the relationships can + // be removed, regardless of + // the target(s) + } + // Now, were any relationships emptied during that processing + // and so need removing for this source handle + if (relationshipsToRemove.size() > 0) { + didsomething = true; + if (relationshipsToRemove.size() == relationships.size()) { + sourcesToRemove.add(hid); + } else { + for (int i = 0; i < relationshipsToRemove.size(); i++) { + relationships.remove(relationshipsToRemove.get(i)); + } + } + } + } + } + // Remove sources that have no valid relationships any more + for (String hid : sourcesToRemove) { + // System.err.println( + // " source handle: all relationships have gone for "+hid); + mapper.removeAll(hid); + IProgramElement ipe = hierarchy.getElement(hid); + if (ipe != null) { + // If the relationship was hanging off a 'code' node, delete it. + if (ipe.getKind().equals(IProgramElement.Kind.CODE)) { + if (debug) { + System.err.println(" source handle: it was code node, removing that as well... code=" + ipe + " parent=" + + ipe.getParent()); + } + removeSingleNode(ipe); + } + } + } + + if (debug) { + dumprelsStderr("after processing 'affectedby'"); + } + if (didsomething) { // did we do anything? + sourcesToRemove.clear(); + // removing 'affects' relationships + if (debug) { + dumprelsStderr("before processing 'affects'"); + } + // Iterate over the source handles in the relationships map + sourcehandlesSet = mapper.getEntries(); + for (String hid : sourcehandlesSet) { + relationshipsToRemove.clear(); + List<IRelationship> relationships = mapper.get(hid); + for (IRelationship rel : relationships) { + if (rel.getKind() == IRelationship.Kind.USES_POINTCUT) { + continue; // these relationships are added at compile + } + // time, argh + if (!rel.isAffects()) { + continue; + } + List<String> targets = rel.getTargets(); + List<String> targetsToRemove = new ArrayList<String>(); + + // find targets that target the type we are interested in, + // they need removing + for (String targethid : targets) { + if (isPhantomHandle(hid) && !getTypeNameFromHandle(hid, handleToTypenameCache).equals(typename)) { + continue; + } + // Does this point to the same type? + IProgramElement existingTarget = hierarchy.getElement(targethid); + if (existingTarget == null || sameType(targethid, existingTarget, typeNode)) { + targetsToRemove.add(targethid); + } + } + + if (targetsToRemove.size() != 0) { + if (targetsToRemove.size() == targets.size()) { + relationshipsToRemove.add(rel); + } else { + // Remove all the targets that are no longer valid + for (String togo : targetsToRemove) { + targets.remove(togo); + } + } + } + } + // Now, were any relationships emptied during that processing + // and so need removing for this source handle + if (relationshipsToRemove.size() > 0) { + // Are we removing *all* of the relationships for this + // source handle? + if (relationshipsToRemove.size() == relationships.size()) { + sourcesToRemove.add(hid); + } else { + for (int i = 0; i < relationshipsToRemove.size(); i++) { + relationships.remove(relationshipsToRemove.get(i)); + } + } + } + } + // Remove sources that have no valid relationships any more + for (String hid : sourcesToRemove) { + // System.err.println( + // " source handle: all relationships have gone for "+hid); + mapper.removeAll(hid); + IProgramElement ipe = hierarchy.getElement(hid); + if (ipe != null) { + // If the relationship was hanging off a 'code' node, delete + // it. + if (ipe.getKind().equals(IProgramElement.Kind.CODE)) { + if (debug) { + System.err.println(" source handle: it was code node, removing that as well... code=" + ipe + + " parent=" + ipe.getParent()); + } + removeSingleNode(ipe); + } + } + } + if (debug) { + dumprelsStderr("after processing 'affects'"); + } + } + + if (debug) { + System.err.println("<<removeRelationshipsTargettingThisFile"); + } + } + + /** + * Return true if the target element is in the type specified. + */ + private boolean sameType(String hid, IProgramElement target, IProgramElement type) { + IProgramElement containingType = target; + if (target == null) { + throw new RuntimeException("target can't be null!"); + } + if (type == null) { + throw new RuntimeException("type can't be null!"); + } + if (target.getKind().isSourceFile() || target.getKind().isFile()) { // isFile() covers pr263487 + // @AJ aspect with broken relationship endpoint - we couldn't find + // the real + // endpoint (the declare parents or ITD or similar) so defaulted to + // the + // first line of the source file... + + // FRAGILE + // Let's assume the worst, and that it is the same type if the + // source files + // are the same. This will break for multiple top level types in a + // file... + if (target.getSourceLocation() == null) { + return false; // these four possibilities should really be FIXED + } + // so we don't have this situation + if (type.getSourceLocation() == null) { + return false; + } + if (target.getSourceLocation().getSourceFile() == null) { + return false; + } + if (type.getSourceLocation().getSourceFile() == null) { + return false; + } + return (target.getSourceLocation().getSourceFile().equals(type.getSourceLocation().getSourceFile())); + } + try { + while (!containingType.getKind().isType()) { + containingType = containingType.getParent(); + } + } catch (Throwable t) { + // Example: + // java.lang.RuntimeException: Exception whilst walking up from target X.class kind=(file) + // hid=(=importProb/binaries<x(X.class) + throw new RuntimeException("Exception whilst walking up from target " + target.toLabelString() + " kind=(" + + target.getKind() + ") hid=(" + target.getHandleIdentifier() + ")", t); + } + return (type.equals(containingType)); + } + + /** + * @param handle a JDT like handle, following the form described in AsmRelationshipProvider.findOrFakeUpNode + * @return true if the handle contains ';' - the char indicating that it is a phantom handle + */ + private boolean isPhantomHandle(String handle) { + int phantomMarker = handle.indexOf(HandleProviderDelimiter.PHANTOM.getDelimiter()); + return phantomMarker != -1 + && handle.charAt(phantomMarker - 1) == HandleProviderDelimiter.PACKAGEFRAGMENTROOT.getDelimiter(); + } + + /** + * Go through all the relationships in the model, if any endpoints no longer exist (the node it points to has been deleted from + * the model) then delete the relationship. + */ + private void repairRelationships(Writer fw) { + try { + // IHierarchy model = AsmManager.getDefault().getHierarchy(); + // TODO Speed this code up by making this assumption: + // the only piece of the handle that is interesting is the file + // name. We are working at file granularity, if the + // file does not exist (i.e. its not in the filemap) then any handle + // inside that file cannot exist. + if (dumpDeltaProcessing) { + fw.write("Repairing relationships map:\n"); + } + + // Now sort out the relationships map + // IRelationshipMap irm = AsmManager.getDefault().getRelationshipMap(); + Set<String> sourcesToRemove = new HashSet<String>(); + Set<String> nonExistingHandles = new HashSet<String>(); // Cache of handles that we + // *know* are invalid +// int srchandlecounter = 0; +// int tgthandlecounter = 0; + + // Iterate over the source handles in the relationships map + Set<String> keyset = mapper.getEntries(); // These are source handles + for (String hid : keyset) { +// srchandlecounter++; + + // Do we already know this handle points to nowhere? + if (nonExistingHandles.contains(hid)) { + sourcesToRemove.add(hid); + } else if (!isPhantomHandle(hid)) { + // We better check if it actually exists + IProgramElement existingElement = hierarchy.getElement(hid); + if (dumpDeltaProcessing) { + fw.write("Looking for handle [" + hid + "] in model, found: " + existingElement + "\n"); + } + // Did we find it? + if (existingElement == null) { + // No, so delete this relationship + sourcesToRemove.add(hid); + nonExistingHandles.add(hid); // Speed up a bit you swine + } else { + // Ok, so the source is valid, what about the targets? + List<IRelationship> relationships = mapper.get(hid); + List<IRelationship> relationshipsToRemove = new ArrayList<IRelationship>(); + // Iterate through the relationships against this source + // handle + for (Iterator<IRelationship> reliter = relationships.iterator(); reliter.hasNext();) { + IRelationship rel = reliter.next(); + List<String> targets = rel.getTargets(); + List<String> targetsToRemove = new ArrayList<String>(); + + // Iterate through the targets for this relationship + for (Iterator<String> targetIter = targets.iterator(); targetIter.hasNext();) { + String targethid = targetIter.next(); +// tgthandlecounter++; + // Do we already know it doesn't exist? + if (nonExistingHandles.contains(targethid)) { + if (dumpDeltaProcessing) { + fw.write("Target handle [" + targethid + "] for srchid[" + hid + "]rel[" + rel.getName() + + "] does not exist\n"); + } + targetsToRemove.add(targethid); + } else if (!isPhantomHandle(targethid)) { + // We better check + IProgramElement existingTarget = hierarchy.getElement(targethid); + if (existingTarget == null) { + if (dumpDeltaProcessing) { + fw.write("Target handle [" + targethid + "] for srchid[" + hid + "]rel[" + + rel.getName() + "] does not exist\n"); + } + targetsToRemove.add(targethid); + nonExistingHandles.add(targethid); + } + } + } + + // Do we have some targets that need removing? + if (targetsToRemove.size() != 0) { + // Are we removing *all* of the targets for this + // relationship (i.e. removing the relationship) + if (targetsToRemove.size() == targets.size()) { + if (dumpDeltaProcessing) { + fw.write("No targets remain for srchid[" + hid + "] rel[" + rel.getName() + + "]: removing it\n"); + } + relationshipsToRemove.add(rel); + } else { + // Remove all the targets that are no longer + // valid + for (String togo : targetsToRemove) { + targets.remove(togo); + } + // Should have already been caught above, + // but lets double check ... + if (targets.size() == 0) { + if (dumpDeltaProcessing) { + fw.write("No targets remain for srchid[" + hid + "] rel[" + rel.getName() + + "]: removing it\n"); + } + relationshipsToRemove.add(rel); // TODO + // Should + // only + // remove + // this + // relationship + // for + // the + // srchid + // ? + } + } + } + } + // Now, were any relationships emptied during that + // processing and so need removing for this source + // handle + if (relationshipsToRemove.size() > 0) { + // Are we removing *all* of the relationships for + // this source handle? + if (relationshipsToRemove.size() == relationships.size()) { + // We know they are all going to go, so just + // delete the source handle. + sourcesToRemove.add(hid); + } else { + // MEMORY LEAK - we don't remove the + // relationships !! + for (int i = 0; i < relationshipsToRemove.size(); i++) { + IRelationship irel = relationshipsToRemove.get(i); + verifyAssumption(mapper.remove(hid, irel), "Failed to remove relationship " + irel.getName() + + " for shid " + hid); + } + List<IRelationship> rels = mapper.get(hid); + if (rels == null || rels.size() == 0) { + sourcesToRemove.add(hid); + } + } + } + } + } + } + // Remove sources that have no valid relationships any more + for (Iterator<String> srciter = sourcesToRemove.iterator(); srciter.hasNext();) { + String hid = srciter.next(); + mapper.removeAll(hid); + IProgramElement ipe = hierarchy.getElement(hid); + if (ipe != null) { + // If the relationship was hanging off a 'code' node, delete + // it. + if (ipe.getKind().equals(IProgramElement.Kind.CODE)) { + // System.err.println("Deleting code node"); + removeSingleNode(ipe); + } + } + } + } catch (IOException ioe) { + System.err.println("Failed to repair relationships:"); + ioe.printStackTrace(); + } + } + + /** + * Removes a specified program element from the structure model. We go to the parent of the program element, ask for all its + * children and remove the node we want to delete from the list of children. + */ + private void removeSingleNode(IProgramElement progElem) { + if (progElem == null) { + throw new IllegalStateException("AsmManager.removeNode(): programElement unexpectedly null"); + } + boolean deleteOK = false; + IProgramElement parent = progElem.getParent(); + List<IProgramElement> kids = parent.getChildren(); + for (int i = 0, max = kids.size(); i < max; i++) { + if (kids.get(i).equals(progElem)) { + kids.remove(i); + deleteOK = true; + break; + } + } + if (!deleteOK) { + System.err.println("unexpectedly failed to delete node from model. hid=" + progElem.getHandleIdentifier()); + } + } + + /** + * Removes a specified program element from the structure model. Two processing stages: + * <p> + * First: We go to the parent of the program element, ask for all its children and remove the node we want to delete from the + * list of children. + * <p> + * Second:We check if that parent has any other children. If it has no other children and it is either a CODE node or a PACKAGE + * node, we delete it too. + */ + private void removeNode(IProgramElement progElem) { + + // StringBuffer flightrecorder = new StringBuffer(); + try { + // flightrecorder.append("In removeNode, about to chuck away: "+ + // progElem+"\n"); + if (progElem == null) { + throw new IllegalStateException("AsmManager.removeNode(): programElement unexpectedly null"); + } + // boolean deleteOK = false; + IProgramElement parent = progElem.getParent(); + // flightrecorder.append("Parent of it is "+parent+"\n"); + List<IProgramElement> kids = parent.getChildren(); + // flightrecorder.append("Which has "+kids.size()+" kids\n"); + for (int i = 0; i < kids.size(); i++) { + // flightrecorder.append("Comparing with "+kids.get(i)+"\n"); + if (kids.get(i).equals(progElem)) { + kids.remove(i); + // flightrecorder.append("Removing it\n"); + // deleteOK=true; + break; + } + } + // verifyAssumption(deleteOK,flightrecorder.toString()); + // Are there any kids left for this node? + if (parent.getChildren().size() == 0 + && parent.getParent() != null + && (parent.getKind().equals(IProgramElement.Kind.CODE) || parent.getKind().equals(IProgramElement.Kind.PACKAGE))) { + // This node is on its own, we should trim it too *as long as + // its not a structural node* which we currently check by + // making sure its a code node + // We should trim if it + // System.err.println("Deleting parent:"+parent); + removeNode(parent); + } + } catch (NullPointerException npe) { + // Occurred when commenting out other 2 ras classes in wsif?? + // reproducable? + // System.err.println(flightrecorder.toString()); + npe.printStackTrace(); + } + } + + public static void verifyAssumption(boolean b, String info) { + if (!b) { + System.err.println("=========== ASSERTION IS NOT TRUE =========v"); + System.err.println(info); + Thread.dumpStack(); + System.err.println("=========== ASSERTION IS NOT TRUE =========^"); + throw new RuntimeException("Assertion is false"); + } + } + + public static void verifyAssumption(boolean b) { + if (!b) { + Thread.dumpStack(); + throw new RuntimeException("Assertion is false"); + } + } + + // ===================== DELTA PROCESSING CODE ============== end + // ==========// + + /** + * A ModelInfo object captures basic information about the structure model. It is used for testing and producing debug info. + */ + public static class ModelInfo { + private final Hashtable<String, Integer> nodeTypeCount = new Hashtable<String, Integer>(); + private final Properties extraProperties = new Properties(); + + private ModelInfo(IHierarchy hierarchy, IRelationshipMap relationshipMap) { + IProgramElement ipe = hierarchy.getRoot(); + walkModel(ipe); + recordStat("FileMapSize", new Integer(hierarchy.getFileMapEntrySet().size()).toString()); + recordStat("RelationshipMapSize", new Integer(relationshipMap.getEntries().size()).toString()); + } + + private void walkModel(IProgramElement ipe) { + countNode(ipe); + for (IProgramElement child : ipe.getChildren()) { + walkModel(child); + } + } + + private void countNode(IProgramElement ipe) { + String node = ipe.getKind().toString(); + Integer ctr = nodeTypeCount.get(node); + if (ctr == null) { + nodeTypeCount.put(node, new Integer(1)); + } else { + ctr = new Integer(ctr.intValue() + 1); + nodeTypeCount.put(node, ctr); + } + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("Model node summary:\n"); + Enumeration<String> nodeKeys = nodeTypeCount.keys(); + while (nodeKeys.hasMoreElements()) { + String key = nodeKeys.nextElement(); + Integer ct = nodeTypeCount.get(key); + sb.append(key + "=" + ct + "\n"); + } + sb.append("Model stats:\n"); + Enumeration<Object> ks = extraProperties.keys(); + while (ks.hasMoreElements()) { + String k = (String) ks.nextElement(); + String v = extraProperties.getProperty(k); + sb.append(k + "=" + v + "\n"); + } + return sb.toString(); + } + + public Properties getProperties() { + Properties p = new Properties(); + for (Map.Entry<String, Integer> entry : nodeTypeCount.entrySet()) { + p.setProperty(entry.getKey(), entry.getValue().toString()); + } + p.putAll(extraProperties); + return p; + } + + public void recordStat(String string, String string2) { + extraProperties.setProperty(string, string2); + } + + } + + public ModelInfo summarizeModel() { + return new ModelInfo(getHierarchy(), getRelationshipMap()); + } + + /** + * Set to indicate whether we are currently building a structure model, should be set up front. + */ + // public static void setCreatingModel(boolean b) { + // creatingModel = b; + // } + // + // /** + // * returns true if we are currently generating a structure model, enables guarding of expensive operations on an empty/null + // * model. + // */ + // public static boolean isCreatingModel() { + // return creatingModel; + // } + public static void setCompletingTypeBindings(boolean b) { + completingTypeBindings = b; + } + + public static boolean isCompletingTypeBindings() { + return completingTypeBindings; + } + + // public void setRelationshipMap(IRelationshipMap irm) { + // mapper = irm; + // } + // + // public void setHierarchy(IHierarchy ih) { + // hierarchy = ih; + // } + + public void resetDeltaProcessing() { + lastBuildChanges.clear(); + aspectsWeavingInLastBuild.clear(); + } + + /** + * @return the Set of files for which the structure model was modified (they may have been removed or otherwise rebuilt). Set is + * empty for a full build. + */ + public Set<File> getModelChangesOnLastBuild() { + return lastBuildChanges; + } + + /** + * @return the Set of aspects that wove files on the last build (either incremental or full build) + */ + public Set<File> getAspectsWeavingFilesOnLastBuild() { + return aspectsWeavingInLastBuild; + } + + public void addAspectInEffectThisBuild(File f) { + aspectsWeavingInLastBuild.add(f); + } + + public static void setLastActiveStructureModel(AsmManager structureModel) { + if (recordingLastActiveStructureModel) { + lastActiveStructureModel = structureModel; + } + } + + public String getHandleElementForInpath(String binaryPath) { + return inpathMap.get(new File(binaryPath)); + } +} diff --git a/asm/src/main/java/org/aspectj/asm/HierarchyWalker.java b/asm/src/main/java/org/aspectj/asm/HierarchyWalker.java new file mode 100644 index 000000000..7629f7c68 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/HierarchyWalker.java @@ -0,0 +1,37 @@ +/* ******************************************************************* + * Copyright (c) 2003,2010 Contributors + * 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: + * Mik Kersten initial implementation + * Andy Clement + * ******************************************************************/ + +package org.aspectj.asm; + +/** + * @author Mik Kersten + * @author Andy Clement + */ +public abstract class HierarchyWalker { + + public HierarchyWalker() { + } + + protected void preProcess(IProgramElement node) { + } + + protected void postProcess(IProgramElement node) { + } + + public IProgramElement process(IProgramElement node) { + preProcess(node); + node.walk(this); + postProcess(node); + return node; + } +} diff --git a/asm/src/main/java/org/aspectj/asm/IElementHandleProvider.java b/asm/src/main/java/org/aspectj/asm/IElementHandleProvider.java new file mode 100644 index 000000000..c071f0337 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/IElementHandleProvider.java @@ -0,0 +1,65 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * 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: + * Mik Kersten initial implementation + * ******************************************************************/ + +package org.aspectj.asm; + +import java.io.File; + +import org.aspectj.bridge.ISourceLocation; + +/** + * Adapter used to uniquely identify program element handles. Can be implemented and overridden in @see{AsmManager} in order to + * provide IDE-specific mechanisms of identifying elements. For example, AJDT uses workspace-relative paths that are understood by + * its JavaCore class. + * + * @author Mik Kersten + */ +public interface IElementHandleProvider { + + /** + * @return a String uniquely identifying this element + */ + public String createHandleIdentifier(ISourceLocation location); + + /** + * @return a String uniquely identifying this element + */ + public String createHandleIdentifier(File sourceFile, int line, int column, int offset); + + /** + * @return a String uniquely identifying this element + */ + public String createHandleIdentifier(IProgramElement ipe); + + /** + * NOTE: this is necessary for the current implementation to look up nodes, but we may want to consider removing it. + * + * @return a String corresponding to the + */ + public String getFileForHandle(String handle); + + /** + * NOTE: this is necessary for the current implementation to look up nodes, but we may want to consider removing it. + * + * @return the line number corresponding to this handel + */ + public int getLineNumberForHandle(String handle); + + public int getOffSetForHandle(String handle); + + /** + * Initializes handle provider state. + * + * The initializer is invoked when a new ASM is created on a full build. + */ + public void initialize(); +} diff --git a/asm/src/main/java/org/aspectj/asm/IHierarchy.java b/asm/src/main/java/org/aspectj/asm/IHierarchy.java new file mode 100644 index 000000000..6674cdaea --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/IHierarchy.java @@ -0,0 +1,127 @@ +/* ******************************************************************* + * Copyright (c) 2003,2010 Contributors + * 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: + * Mik Kersten initial implementation + * Andy Clement + * ******************************************************************/ +package org.aspectj.asm; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.aspectj.asm.internal.ProgramElement; +import org.aspectj.bridge.ISourceLocation; + +/** + * @author Mik Kersten + * @author Andy Clement + */ +public interface IHierarchy extends Serializable { + + public static final IProgramElement NO_STRUCTURE = new ProgramElement(null, "<build to view structure>", + IProgramElement.Kind.ERROR, null); + + public IProgramElement getElement(String handle); + + public IProgramElement getRoot(); + + public void setRoot(IProgramElement root); + + public void addToFileMap(String canonicalFilePath, IProgramElement compilationUnitProgramElement); + + public boolean removeFromFileMap(String canonicalFilePath); + + public void setFileMap(HashMap<String, IProgramElement> fileMap); + + public Object findInFileMap(Object key); + + public Set<Map.Entry<String, IProgramElement>> getFileMapEntrySet(); + + public boolean isValid(); + + public IProgramElement findElementForHandle(String handle); + + public IProgramElement findElementForHandleOrCreate(String handle, boolean create); + + /** + * Returns the first match + * + * @param parent + * @param kind not null + * @return null if not found + */ + public IProgramElement findElementForSignature(IProgramElement parent, IProgramElement.Kind kind, String signature); + + /** + * Returns the first match + * + * @param parent + * @param kind not null + * @return null if not found + */ + public IProgramElement findElementForLabel(IProgramElement parent, IProgramElement.Kind kind, String label); + + /** + * @param packageName if null default package is searched + * @param className can't be null + */ + public IProgramElement findElementForType(String packageName, String typeName); + + /** + * @param sourceFilePath modified to '/' delimited path for consistency + * @return a new structure node for the file if it was not found in the model + */ + public IProgramElement findElementForSourceFile(String sourceFile); + + /** + * TODO: discriminate columns + */ + public IProgramElement findElementForSourceLine(ISourceLocation location); + + /** + * Never returns null + * + * @param sourceFilePath canonicalized path for consistency + * @param lineNumber if 0 or 1 the corresponding file node will be returned + * @return a new structure node for the file if it was not found in the model + */ + public IProgramElement findElementForSourceLine(String sourceFilePath, int lineNumber); + + public IProgramElement findElementForOffSet(String sourceFilePath, int lineNumber, int offSet); + + public String getConfigFile(); + + public void setConfigFile(String configFile); + + public void flushTypeMap(); + + public void flushHandleMap(); + + public void updateHandleMap(Set<String> deletedFiles); + + /** + * For a specified node, check if any of the children more accurately represent the specified line. + * + * @param node where to start looking + * @param lineno the line number + * @return any closer match below 'node' or null if nothing is a more accurate match + */ + public IProgramElement findCloserMatchForLineNumber(IProgramElement node, int lineno); + + /** + * Discover the node representing a particular source file. + * + * @param node where in the model to start looking (usually the root on the initial call) + * @param sourcefilePath the source file being searched for + * @return the node representing that source file or null if it cannot be found + */ + public IProgramElement findNodeForSourceFile(IProgramElement node, String sourcefilePath); +}
\ No newline at end of file diff --git a/asm/src/main/java/org/aspectj/asm/IHierarchyListener.java b/asm/src/main/java/org/aspectj/asm/IHierarchyListener.java new file mode 100644 index 000000000..12bf724da --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/IHierarchyListener.java @@ -0,0 +1,25 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * 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: + * Mik Kersten initial implementation + * ******************************************************************/ + +package org.aspectj.asm; + +import java.util.EventListener; + +/** + * Compiler listeners get notified of structure model update events. + * + * @author Mik Kersten + */ +public interface IHierarchyListener extends EventListener { + + public void elementsUpdated(IHierarchy rootNode); +} diff --git a/asm/src/main/java/org/aspectj/asm/IModelFilter.java b/asm/src/main/java/org/aspectj/asm/IModelFilter.java new file mode 100644 index 000000000..cfae6e7b7 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/IModelFilter.java @@ -0,0 +1,32 @@ +/* ******************************************************************* + * Copyright (c) 2006 Contributors + * 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: + * Andy Clement IBM initial implementation + * ******************************************************************/ +package org.aspectj.asm; + +/** + * When dumping the model out (for debugging/testing), various parts of it can be passed through this filter. Currently it is used + * to ensure the source locations we dump out are independent of sandbox directory. + * + * @author Andy Clement + */ +public interface IModelFilter { + + /** + * Called when about to dump out an absolute file location, enabling it to be altered (eg. + * c:/temp/ajcsSandbox/foo/ajctemp.12323/<BLAH> could become TEST_SANDBOX/<BLAH> + */ + String processFilelocation(String loc); + + /** + * When the relationship map is dumped, lines are prefixed with a handle ID. Return true if you want these, false if you do not. + */ + boolean wantsHandleIds(); +} diff --git a/asm/src/main/java/org/aspectj/asm/IProgramElement.java b/asm/src/main/java/org/aspectj/asm/IProgramElement.java new file mode 100644 index 000000000..19e6d95ac --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/IProgramElement.java @@ -0,0 +1,456 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * 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: + * Mik Kersten initial implementation + * ******************************************************************/ + +package org.aspectj.asm; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; + +/** + * Represents program elements in the AspectJ containment hierarchy. + * + * @author Mik Kersten + */ +public interface IProgramElement extends Serializable { + + public List<IProgramElement> getChildren(); + + public void setChildren(List<IProgramElement> children); + + public void addChild(IProgramElement child); + + public boolean removeChild(IProgramElement child); + + // Extra stuff + // Could be just a string but may prove more useful as an object in the long + // run ... + public static class ExtraInformation implements Serializable { + private static final long serialVersionUID = -3880735494840820638L; + private String extraInfo; + + public ExtraInformation() { + extraInfo = ""; + } + + public void setExtraAdviceInformation(String string) { + extraInfo = string; + } + + public String getExtraAdviceInformation() { + return extraInfo; + } + + public String toString() { + return "ExtraInformation: [" + extraInfo + "]"; + } + } + + public void setExtraInfo(ExtraInformation info); + + public ExtraInformation getExtraInfo(); + + public IProgramElement getParent(); + + public void setParent(IProgramElement parent); + + public void setParentTypes(List<String> parentTypes); + + public List<String> getParentTypes(); + + public String getName(); + + public void setName(String name); + + public String getDetails(); + + public void setDetails(String details); + + public IProgramElement.Kind getKind(); + + public void setKind(Kind kind); + + public List<IProgramElement.Modifiers> getModifiers(); + + public void setModifiers(int i); + + public Accessibility getAccessibility(); + + public String getDeclaringType(); // TODO: remove (Emacs uses it) + + public String getPackageName(); + + /** + * @param method + * return types or field types + */ + public void setCorrespondingType(String returnType); + + /** + * This correponds to both method return types and field types. + */ + public String getCorrespondingType(); + + public String getCorrespondingType(boolean getFullyQualifiedType); + + public String toSignatureString(); + + public String toSignatureString(boolean getFullyQualifiedArgTypes); + + public void setRunnable(boolean value); + + public boolean isRunnable(); + + public boolean isImplementor(); + + public void setImplementor(boolean value); + + public boolean isOverrider(); + + public void setOverrider(boolean value); + + public IMessage getMessage(); + + public void setMessage(IMessage message); + + public ISourceLocation getSourceLocation(); + + public void setSourceLocation(ISourceLocation sourceLocation); + + public String toString(); + + /** + * @return the javadoc comment for this program element, null if not available + */ + public String getFormalComment(); + + public void setFormalComment(String comment); + + /** + * Includes information about the origin of the node. + */ + public String toLinkLabelString(); + + public String toLinkLabelString(boolean getFullyQualifiedArgTypes); + + /** + * Includes name, parameter types (if any) and details (if any). + */ + public String toLabelString(); + + public String toLabelString(boolean getFullyQualifiedArgTypes); + + public List<String> getParameterNames(); + + public void setParameterNames(List<String> list); + + public List<char[]> getParameterSignatures(); + + public List<String> getParameterSignaturesSourceRefs(); + + public void setParameterSignatures(List<char[]> list, List<String> paramSourceRefs); + + public List<char[]> getParameterTypes(); + + /** + * The format of the string handle is not specified, but is stable across compilation sessions. + * + * @return a string representation of this element + */ + public String getHandleIdentifier(); + + public String getHandleIdentifier(boolean create); + + public void setHandleIdentifier(String handle); + + /** + * @return a string representation of this node and all of its children (recursive) + */ + public String toLongString(); + + public String getBytecodeName(); + + public String getBytecodeSignature(); + + public void setBytecodeName(String bytecodeName); + + public void setBytecodeSignature(String bytecodeSignature); + + /** + * @return the full signature of this element, as it appears in the source + */ + public String getSourceSignature(); + + public void setSourceSignature(String string); + + public IProgramElement walk(HierarchyWalker walker); + + public AsmManager getModel(); + + public int getRawModifiers(); + + /** + * Uses "typesafe enum" pattern. + */ + public static class Modifiers implements Serializable { + + private static final long serialVersionUID = -8279300899976607927L; + + public static final Modifiers STATIC = new Modifiers("static", 0x0008); + public static final Modifiers FINAL = new Modifiers("final", 0x0010); + public static final Modifiers ABSTRACT = new Modifiers("abstract", 0x0400); + public static final Modifiers SYNCHRONIZED = new Modifiers("synchronized", 0x0020); + public static final Modifiers VOLATILE = new Modifiers("volatile", 0x0040); + public static final Modifiers STRICTFP = new Modifiers("strictfp", 0x0800); + public static final Modifiers TRANSIENT = new Modifiers("transient", 0x0080); + public static final Modifiers NATIVE = new Modifiers("native", 0x0100); + public static final Modifiers[] ALL = { STATIC, FINAL, ABSTRACT, SYNCHRONIZED, VOLATILE, STRICTFP, TRANSIENT, NATIVE }; + private final String name; + private final int bit; + + private Modifiers(String name, int bit) { + this.name = name; + this.bit = bit; + } + + public String toString() { + return name; + } + + public int getBit() { + return bit; + } + + // The 4 declarations below are necessary for serialization + private static int nextOrdinal = 0; + private final int ordinal = nextOrdinal++; + + private Object readResolve() throws ObjectStreamException { + return ALL[ordinal]; + } + } + + /** + * Uses "typesafe enum" pattern. + */ + public static class Accessibility implements Serializable { + + private static final long serialVersionUID = 5371838588180918519L; + + public static final Accessibility PUBLIC = new Accessibility("public"); + public static final Accessibility PACKAGE = new Accessibility("package"); + public static final Accessibility PROTECTED = new Accessibility("protected"); + public static final Accessibility PRIVATE = new Accessibility("private"); + public static final Accessibility PRIVILEGED = new Accessibility("privileged"); + public static final Accessibility[] ALL = { PUBLIC, PACKAGE, PROTECTED, PRIVATE, PRIVILEGED }; + private final String name; + + private Accessibility(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + // The 4 declarations below are necessary for serialization + private static int nextOrdinal = 0; + private final int ordinal = nextOrdinal++; + + private Object readResolve() throws ObjectStreamException { + return ALL[ordinal]; + } + } + + /** + * Uses "typesafe enum" pattern. + */ + public static class Kind implements Serializable { + + private static final long serialVersionUID = -1963553877479266124L; + + public static final Kind PROJECT = new Kind("project"); + public static final Kind PACKAGE = new Kind("package"); + public static final Kind FILE = new Kind("file"); + public static final Kind FILE_JAVA = new Kind("java source file"); + public static final Kind FILE_ASPECTJ = new Kind("aspect source file"); + public static final Kind FILE_LST = new Kind("build configuration file"); + public static final Kind IMPORT_REFERENCE = new Kind("import reference"); + public static final Kind CLASS = new Kind("class"); + public static final Kind INTERFACE = new Kind("interface"); + public static final Kind ASPECT = new Kind("aspect"); + public static final Kind ENUM = new Kind("enum"); + public static final Kind ENUM_VALUE = new Kind("enumvalue"); + public static final Kind ANNOTATION = new Kind("annotation"); + public static final Kind INITIALIZER = new Kind("initializer"); + public static final Kind INTER_TYPE_FIELD = new Kind("inter-type field"); + public static final Kind INTER_TYPE_METHOD = new Kind("inter-type method"); + public static final Kind INTER_TYPE_CONSTRUCTOR = new Kind("inter-type constructor"); + public static final Kind INTER_TYPE_PARENT = new Kind("inter-type parent"); + public static final Kind CONSTRUCTOR = new Kind("constructor"); + public static final Kind METHOD = new Kind("method"); + public static final Kind FIELD = new Kind("field"); + public static final Kind POINTCUT = new Kind("pointcut"); + public static final Kind ADVICE = new Kind("advice"); + public static final Kind DECLARE_PARENTS = new Kind("declare parents"); + public static final Kind DECLARE_WARNING = new Kind("declare warning"); + public static final Kind DECLARE_ERROR = new Kind("declare error"); + public static final Kind DECLARE_SOFT = new Kind("declare soft"); + public static final Kind DECLARE_PRECEDENCE = new Kind("declare precedence"); + public static final Kind CODE = new Kind("code"); + public static final Kind ERROR = new Kind("error"); + public static final Kind DECLARE_ANNOTATION_AT_CONSTRUCTOR = new Kind("declare @constructor"); + public static final Kind DECLARE_ANNOTATION_AT_FIELD = new Kind("declare @field"); + public static final Kind DECLARE_ANNOTATION_AT_METHOD = new Kind("declare @method"); + public static final Kind DECLARE_ANNOTATION_AT_TYPE = new Kind("declare @type"); + public static final Kind SOURCE_FOLDER = new Kind("source folder"); + public static final Kind PACKAGE_DECLARATION = new Kind("package declaration"); + + public static final Kind[] ALL = { PROJECT, PACKAGE, FILE, FILE_JAVA, FILE_ASPECTJ, FILE_LST, IMPORT_REFERENCE, CLASS, + INTERFACE, ASPECT, ENUM, ENUM_VALUE, ANNOTATION, INITIALIZER, INTER_TYPE_FIELD, INTER_TYPE_METHOD, + INTER_TYPE_CONSTRUCTOR, INTER_TYPE_PARENT, CONSTRUCTOR, METHOD, FIELD, POINTCUT, ADVICE, DECLARE_PARENTS, + DECLARE_WARNING, DECLARE_ERROR, DECLARE_SOFT, DECLARE_PRECEDENCE, CODE, ERROR, DECLARE_ANNOTATION_AT_CONSTRUCTOR, + DECLARE_ANNOTATION_AT_FIELD, DECLARE_ANNOTATION_AT_METHOD, DECLARE_ANNOTATION_AT_TYPE, SOURCE_FOLDER, + PACKAGE_DECLARATION + + }; + + public static Kind getKindForString(String kindString) { + for (int i = 0; i < ALL.length; i++) { + if (ALL[i].toString().equals(kindString)) { + return ALL[i]; + } + } + return ERROR; + } + + private final String name; + + private Kind(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + public static List<Kind> getNonAJMemberKinds() { + List<Kind> list = new ArrayList<Kind>(); + list.add(METHOD); + list.add(ENUM_VALUE); + list.add(FIELD); + list.add(CONSTRUCTOR); + return list; + } + + public boolean isMember() { + return this == FIELD || this == METHOD || this == CONSTRUCTOR || this == POINTCUT || this == ADVICE + || this == ENUM_VALUE; + } + + public boolean isInterTypeMember() { + return this == INTER_TYPE_CONSTRUCTOR || this == INTER_TYPE_FIELD || this == INTER_TYPE_METHOD; + } + + public boolean isType() { + return this == CLASS || this == INTERFACE || this == ASPECT || this == ANNOTATION || this == ENUM; + } + + public boolean isSourceFile() { + return this == FILE_ASPECTJ || this == FILE_JAVA; + } + + public boolean isFile() { + return this == FILE; + } + + public boolean isDeclare() { + return name.startsWith("declare"); + } + + public boolean isDeclareAnnotation() { + return name.startsWith("declare @"); + } + + public boolean isDeclareParents() { + return name.startsWith("declare parents"); + } + + public boolean isDeclareSoft() { + return name.startsWith("declare soft"); + } + + public boolean isDeclareWarning() { + return name.startsWith("declare warning"); + } + + public boolean isDeclareError() { + return name.startsWith("declare error"); + } + + public boolean isDeclarePrecedence() { + return name.startsWith("declare precedence"); + } + + // The 4 declarations below are necessary for serialization + private static int nextOrdinal = 0; + private final int ordinal = nextOrdinal++; + + private Object readResolve() throws ObjectStreamException { + return ALL[ordinal]; + } + + public boolean isPackageDeclaration() { + return this == PACKAGE_DECLARATION; + } + + } + + public void setAnnotationStyleDeclaration(boolean b); + + public boolean isAnnotationStyleDeclaration(); + + /** + * @param fullyQualifiedannotationType + * the annotation type, eg. p.q.r.Foo + */ + public void setAnnotationType(String fullyQualifiedannotationType); + + /** + * @return the fully qualified annotation type, eg. p.q.r.Foo + */ + public String getAnnotationType(); + + public String[] getRemovedAnnotationTypes(); + + public Map<String, List<String>> getDeclareParentsMap(); + + public void setDeclareParentsMap(Map<String, List<String>> newmap); + + public void addFullyQualifiedName(String fqname); + + public String getFullyQualifiedName(); + + public void setAnnotationRemover(boolean isRemover); + + public boolean isAnnotationRemover(); + + /** + * @return the return type of a method or type of a field in signature form (e.g. Ljava/lang/String;) + */ + public String getCorrespondingTypeSignature(); +}
\ No newline at end of file diff --git a/asm/src/main/java/org/aspectj/asm/IRelationship.java b/asm/src/main/java/org/aspectj/asm/IRelationship.java new file mode 100644 index 000000000..86633cf36 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/IRelationship.java @@ -0,0 +1,103 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * 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: + * Mik Kersten initial implementation + * ******************************************************************/ +package org.aspectj.asm; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.List; + +/** + * A relationship has a name (e.g. 'declare warning') and some set of affected targets. + * + * @author Mik Kersten + * @author Andy Clement + */ +public interface IRelationship extends Serializable { + + public String getName(); + + public Kind getKind(); + + public void addTarget(String handle); + + public List<String> getTargets(); + + public String getSourceHandle(); + + public boolean hasRuntimeTest(); + + public boolean isAffects(); + + public static class Kind implements Serializable { // typesafe enum + + private static final long serialVersionUID = -2691351740214705220L; + + public static final Kind DECLARE_WARNING = new Kind("declare warning"); + public static final Kind DECLARE_ERROR = new Kind("declare error"); + public static final Kind ADVICE_AROUND = new Kind("around advice"); + public static final Kind ADVICE_AFTERRETURNING = new Kind("after returning advice"); + public static final Kind ADVICE_AFTERTHROWING = new Kind("after throwing advice"); + public static final Kind ADVICE_AFTER = new Kind("after advice"); + public static final Kind ADVICE_BEFORE = new Kind("before advice"); + public static final Kind ADVICE = new Kind("advice"); + public static final Kind DECLARE = new Kind("declare"); + public static final Kind DECLARE_INTER_TYPE = new Kind("inter-type declaration"); + public static final Kind USES_POINTCUT = new Kind("uses pointcut"); + public static final Kind DECLARE_SOFT = new Kind("declare soft"); + + public static final Kind[] ALL = { DECLARE_WARNING, DECLARE_ERROR, ADVICE_AROUND, ADVICE_AFTERRETURNING, + ADVICE_AFTERTHROWING, ADVICE_AFTER, ADVICE_BEFORE, ADVICE, DECLARE, DECLARE_INTER_TYPE, USES_POINTCUT, DECLARE_SOFT }; + + private final String name; + + public boolean isDeclareKind() { + return this == DECLARE_WARNING || this == DECLARE_ERROR || this == DECLARE || this == DECLARE_INTER_TYPE + || this == DECLARE_SOFT; + } + + public String getName() { + return name; + } + + /** + * Return the Kind of the relationship that is passed in by name. + * + * @param stringFormOfRelationshipKind the relationship name, eg. 'declare warning', 'declare error', etc. + * @return the Kind instance + */ + public static Kind getKindFor(String stringFormOfRelationshipKind) { + for (int i = 0; i < ALL.length; i++) { + if (ALL[i].name.equals(stringFormOfRelationshipKind)) { + return ALL[i]; + } + } + return null; + } + + private Kind(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + // The 4 declarations below are necessary for serialization + private static int nextOrdinal = 0; + private final int ordinal = nextOrdinal++; + + private Object readResolve() throws ObjectStreamException { + return ALL[ordinal]; + } + } + +} diff --git a/asm/src/main/java/org/aspectj/asm/IRelationshipMap.java b/asm/src/main/java/org/aspectj/asm/IRelationshipMap.java new file mode 100644 index 000000000..e4159dce8 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/IRelationshipMap.java @@ -0,0 +1,87 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * 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: + * Mik Kersten initial implementation + * ******************************************************************/ + +package org.aspectj.asm; + +import java.io.Serializable; +import java.util.List; +import java.util.Set; + +/** + * Maps from a program element handles to a list of relationships between that element and other program elements. Each element in + * the list or relationships is uniquely identified by a kind and a relationship name. For example, the advice affecting a + * particular shadow (e.g. method call) can be retrieved by calling <CODE>get</CODE> on the handle for that method. Symmetrically + * the method call shadows that an advice affects can be retrieved. + * <p> + * + * <p> + * The elements can be stored and looked up as IProgramElement(s), in which cases the element corresponding to the handle is looked + * up in the containment hierarchy. + * + * <p> + * put/get methods taking IProgramElement as a parameter are for convenience only. They work identically to calling their + * counterparts with IProgramElement.getIdentifierHandle() + * + * @author Mik Kersten + * @author Andy Clement + */ +public interface IRelationshipMap extends Serializable { + + /** + * @return list of relationships or null if the source element has no relationships + */ + public List<IRelationship> get(IProgramElement sourceProgramElement); + + /** + * @return list of relationships or null if the source element has no relationships + */ + public List<IRelationship> get(String sourceHandle); + + /** + * Return a relationship matching the kind and name for the given element. + * + * @return null if the relationship is not found. + */ + public IRelationship get(IProgramElement source, IRelationship.Kind kind, String relationshipName, boolean runtimeTest, + boolean createIfMissing); + + /** + * Return a relationship matching the kind and name for the given element. + * + * @return null if the relationship is not found. + */ + public IRelationship get(IProgramElement source, IRelationship.Kind kind, String relationshipName); + + /** + * Return a relationship matching the kind and name for the given element. Creates the relationship if not found. + * + * @return null if the relationship is not found. + */ + public IRelationship get(String source, IRelationship.Kind kind, String relationshipName, boolean runtimeTest, + boolean createIfMissing); + + public void put(IProgramElement source, IRelationship relationship); + + public void put(String handle, IRelationship relationship); + + public boolean remove(String handle, IRelationship relationship); + + public void removeAll(String source); + + /** + * Clear all of the relationships in the map. + */ + public void clear(); + + public Set<String> getEntries(); + +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/AspectJElementHierarchy.java b/asm/src/main/java/org/aspectj/asm/internal/AspectJElementHierarchy.java new file mode 100644 index 000000000..6019964c6 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/AspectJElementHierarchy.java @@ -0,0 +1,697 @@ +/* ******************************************************************* + * Copyright (c) 2003 Contributors. + * 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: + * Mik Kersten initial implementation + * Andy Clement Extensions for better IDE representation + * ******************************************************************/ + +package org.aspectj.asm.internal; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.aspectj.asm.AsmManager; +import org.aspectj.asm.IHierarchy; +import org.aspectj.asm.IProgramElement; +import org.aspectj.bridge.ISourceLocation; +import org.aspectj.bridge.SourceLocation; + +/** + * @author Mik Kersten + * @author Andy Clement + */ +public class AspectJElementHierarchy implements IHierarchy { + + private static final long serialVersionUID = 6462734311117048620L; + + private transient AsmManager asm; + protected IProgramElement root = null; + protected String configFile = null; + + // Access to the handleMap and typeMap are now synchronized - at least the find methods and the updateHandleMap function + // see pr305788 + private Map<String, IProgramElement> fileMap = null; + private Map<String, IProgramElement> handleMap = new HashMap<String, IProgramElement>(); + private Map<String, IProgramElement> typeMap = null; + + public AspectJElementHierarchy(AsmManager asm) { + this.asm = asm; + } + + public IProgramElement getElement(String handle) { + return findElementForHandleOrCreate(handle, false); + } + + public void setAsmManager(AsmManager asm) { // used when deserializing + this.asm = asm; + } + + public IProgramElement getRoot() { + return root; + } + + public String toSummaryString() { + StringBuilder s = new StringBuilder(); + s.append("FileMap has " + fileMap.size() + " entries\n"); + s.append("HandleMap has " + handleMap.size() + " entries\n"); + s.append("TypeMap has " + handleMap.size() + " entries\n"); + s.append("FileMap:\n"); + for (Map.Entry<String, IProgramElement> fileMapEntry : fileMap.entrySet()) { + s.append(fileMapEntry).append("\n"); + } + s.append("TypeMap:\n"); + for (Map.Entry<String, IProgramElement> typeMapEntry : typeMap.entrySet()) { + s.append(typeMapEntry).append("\n"); + } + s.append("HandleMap:\n"); + for (Map.Entry<String, IProgramElement> handleMapEntry : handleMap.entrySet()) { + s.append(handleMapEntry).append("\n"); + } + return s.toString(); + } + + public void setRoot(IProgramElement root) { + this.root = root; + handleMap = new HashMap<String, IProgramElement>(); + typeMap = new HashMap<String, IProgramElement>(); + } + + public void addToFileMap(String key, IProgramElement value) { + fileMap.put(key, value); + } + + public boolean removeFromFileMap(String canonicalFilePath) { + return fileMap.remove(canonicalFilePath) != null; + } + + public void setFileMap(HashMap<String, IProgramElement> fileMap) { + this.fileMap = fileMap; + } + + public Object findInFileMap(Object key) { + return fileMap.get(key); + } + + public Set<Map.Entry<String, IProgramElement>> getFileMapEntrySet() { + return fileMap.entrySet(); + } + + public boolean isValid() { + return root != null && fileMap != null; + } + + /** + * Returns the first match + * + * @param parent + * @param kind not null + * @return null if not found + */ + public IProgramElement findElementForSignature(IProgramElement parent, IProgramElement.Kind kind, String signature) { + for (IProgramElement node : parent.getChildren()) { + if (node.getKind() == kind && signature.equals(node.toSignatureString())) { + return node; + } else { + IProgramElement childSearch = findElementForSignature(node, kind, signature); + if (childSearch != null) { + return childSearch; + } + } + } + return null; + } + + public IProgramElement findElementForLabel(IProgramElement parent, IProgramElement.Kind kind, String label) { + for (IProgramElement node : parent.getChildren()) { + if (node.getKind() == kind && label.equals(node.toLabelString())) { + return node; + } else { + IProgramElement childSearch = findElementForLabel(node, kind, label); + if (childSearch != null) { + return childSearch; + } + } + } + return null; + } + + /** + * Find the entry in the model that represents a particular type. + * + * @param packageName the package in which the type is declared or null for the default package + * @param typeName the name of the type + * @return the IProgramElement representing the type, or null if not found + */ + public IProgramElement findElementForType(String packageName, String typeName) { + + synchronized (this) { + // Build a cache key and check the cache + StringBuilder keyb = (packageName == null) ? new StringBuilder() : new StringBuilder(packageName); + keyb.append(".").append(typeName); + String key = keyb.toString(); + IProgramElement cachedValue = typeMap.get(key); + if (cachedValue != null) { + return cachedValue; + } + + List<IProgramElement> packageNodes = findMatchingPackages(packageName); + + for (IProgramElement pkg : packageNodes) { + // this searches each file for a class + for (IProgramElement fileNode : pkg.getChildren()) { + IProgramElement cNode = findClassInNodes(fileNode.getChildren(), typeName, typeName); + if (cNode != null) { + typeMap.put(key, cNode); + return cNode; + } + } + } + } + return null; + + // IProgramElement packageNode = null; + // if (packageName == null) { + // packageNode = root; + // } else { + // if (root == null) + // return null; + // List kids = root.getChildren(); + // if (kids == null) { + // return null; + // } + // for (Iterator it = kids.iterator(); it.hasNext() && packageNode == null;) { + // IProgramElement node = (IProgramElement) it.next(); + // if (packageName.equals(node.getName())) { + // packageNode = node; + // } + // } + // if (packageNode == null) { + // return null; + // } + // } + + // // this searches each file for a class + // for (Iterator it = packageNode.getChildren().iterator(); it.hasNext();) { + // IProgramElement fileNode = (IProgramElement) it.next(); + // IProgramElement cNode = findClassInNodes(fileNode.getChildren(), typeName, typeName); + // if (cNode != null) { + // typeMap.put(key, cNode); + // return cNode; + // } + // } + // return null; + } + + /** + * Look for any package nodes matching the specified package name. There may be multiple in the case where the types within a + * package are split across source folders. + * + * @param packagename the packagename being searched for + * @return a list of package nodes that match that name + */ + public List<IProgramElement> findMatchingPackages(String packagename) { + List<IProgramElement> children = root.getChildren(); + // The children might be source folders or packages + if (children.size() == 0) { + return Collections.emptyList(); + } + if ((children.get(0)).getKind() == IProgramElement.Kind.SOURCE_FOLDER) { + String searchPackageName = (packagename == null ? "" : packagename); // default package means match on "" + // dealing with source folders + List<IProgramElement> matchingPackageNodes = new ArrayList<IProgramElement>(); + for (IProgramElement sourceFolder : children) { + List<IProgramElement> possiblePackageNodes = sourceFolder.getChildren(); + for (IProgramElement possiblePackageNode : possiblePackageNodes) { + if (possiblePackageNode.getKind() == IProgramElement.Kind.PACKAGE) { + if (possiblePackageNode.getName().equals(searchPackageName)) { + matchingPackageNodes.add(possiblePackageNode); + } + } + } + } + // 'binaries' will be checked automatically by the code above as it is represented as a SOURCE_FOLDER + return matchingPackageNodes; + } else { + // dealing directly with packages below the root, no source folders. Therefore at most one + // thing to return in the list + if (packagename == null) { + // default package + List<IProgramElement> result = new ArrayList<IProgramElement>(); + result.add(root); + return result; + } + List<IProgramElement> result = new ArrayList<IProgramElement>(); + for (IProgramElement possiblePackage : children) { + if (possiblePackage.getKind() == IProgramElement.Kind.PACKAGE && possiblePackage.getName().equals(packagename)) { + result.add(possiblePackage); + } + if (possiblePackage.getKind() == IProgramElement.Kind.SOURCE_FOLDER) { // might be 'binaries' + if (possiblePackage.getName().equals("binaries")) { + for (IProgramElement possiblePackage2 : possiblePackage.getChildren()) { + if (possiblePackage2.getKind() == IProgramElement.Kind.PACKAGE + && possiblePackage2.getName().equals(packagename)) { + result.add(possiblePackage2); + break; // ok to break here, can't be another entry under binaries + } + } + } + } + } + if (result.isEmpty()) { + return Collections.emptyList(); + } else { + return result; + } + } + } + + private IProgramElement findClassInNodes(Collection<IProgramElement> nodes, String name, String typeName) { + String baseName; + String innerName; + int dollar = name.indexOf('$'); + if (dollar == -1) { + baseName = name; + innerName = null; + } else { + baseName = name.substring(0, dollar); + innerName = name.substring(dollar + 1); + } + + for (IProgramElement classNode : nodes) { + if (!classNode.getKind().isType()) { + List<IProgramElement> kids = classNode.getChildren(); + if (kids != null && !kids.isEmpty()) { + IProgramElement node = findClassInNodes(kids, name, typeName); + if (node != null) { + return node; + } + } + } else { + if (baseName.equals(classNode.getName())) { + if (innerName == null) { + return classNode; + } else { + return findClassInNodes(classNode.getChildren(), innerName, typeName); + } + } else if (name.equals(classNode.getName())) { + return classNode; + } else if (typeName.equals(classNode.getBytecodeSignature())) { + return classNode; + } else if (classNode.getChildren() != null && !classNode.getChildren().isEmpty()) { + IProgramElement node = findClassInNodes(classNode.getChildren(), name, typeName); + if (node != null) { + return node; + } + } + } + } + return null; + } + + /** + * @param sourceFilePath modified to '/' delimited path for consistency + * @return a new structure node for the file if it was not found in the model + */ + public IProgramElement findElementForSourceFile(String sourceFile) { + try { + if (!isValid() || sourceFile == null) { + return IHierarchy.NO_STRUCTURE; + } else { + String correctedPath = asm.getCanonicalFilePath(new File(sourceFile)); + // StructureNode node = (StructureNode)getFileMap().get(correctedPath);//findFileNode(filePath, model); + IProgramElement node = (IProgramElement) findInFileMap(correctedPath);// findFileNode(filePath, model); + if (node != null) { + return node; + } else { + return createFileStructureNode(correctedPath); + } + } + } catch (Exception e) { + return IHierarchy.NO_STRUCTURE; + } + } + + /** + * TODO: discriminate columns + */ + public IProgramElement findElementForSourceLine(ISourceLocation location) { + try { + return findElementForSourceLine(asm.getCanonicalFilePath(location.getSourceFile()), location.getLine()); + } catch (Exception e) { + return null; + } + } + + /** + * Never returns null + * + * @param sourceFilePath canonicalized path for consistency + * @param lineNumber if 0 or 1 the corresponding file node will be returned + * @return a new structure node for the file if it was not found in the model + */ + public IProgramElement findElementForSourceLine(String sourceFilePath, int lineNumber) { + String canonicalSFP = asm.getCanonicalFilePath(new File(sourceFilePath)); + // Used to do this: + // IProgramElement node2 = findNodeForSourceLineHelper(root, canonicalSFP, lineNumber, -1); + + // Find the relevant source file node first + IProgramElement node = findNodeForSourceFile(root, canonicalSFP); + if (node == null) { + return createFileStructureNode(sourceFilePath); + } + + // Check if there is a more accurate child node of that source file node: + IProgramElement closernode = findCloserMatchForLineNumber(node, lineNumber); + if (closernode == null) { + return node; + } else { + return closernode; + } + } + + /** + * Discover the node representing a particular source file. + * + * @param node where in the model to start looking (usually the root on the initial call) + * @param sourcefilePath the source file being searched for + * @return the node representing that source file or null if it cannot be found + */ + public IProgramElement findNodeForSourceFile(IProgramElement node, String sourcefilePath) { + // 1. why is <root> a sourcefile node? + // 2. should isSourceFile() return true for a FILE that is a .class file...? + if ((node.getKind().isSourceFile() && !node.getName().equals("<root>")) || node.getKind().isFile()) { + ISourceLocation nodeLoc = node.getSourceLocation(); + if (nodeLoc != null && asm.getCanonicalFilePath(nodeLoc.getSourceFile()).equals(sourcefilePath)) { + return node; + } + return null; // no need to search children of a source file node + } else { + // check the children + for (IProgramElement child : node.getChildren()) { + IProgramElement foundit = findNodeForSourceFile(child, sourcefilePath); + if (foundit != null) { + return foundit; + } + } + return null; + } + } + + public IProgramElement findElementForOffSet(String sourceFilePath, int lineNumber, int offSet) { + String canonicalSFP = asm.getCanonicalFilePath(new File(sourceFilePath)); + IProgramElement node = findNodeForSourceLineHelper(root, canonicalSFP, lineNumber, offSet); + if (node != null) { + return node; + } else { + return createFileStructureNode(sourceFilePath); + } + } + + private IProgramElement createFileStructureNode(String sourceFilePath) { + // SourceFilePath might have originated on windows on linux... + int lastSlash = sourceFilePath.lastIndexOf('\\'); + if (lastSlash == -1) { + lastSlash = sourceFilePath.lastIndexOf('/'); + } + // '!' is used like in URLs "c:/blahblah/X.jar!a/b.class" + int i = sourceFilePath.lastIndexOf('!'); + int j = sourceFilePath.indexOf(".class"); + if (i > lastSlash && i != -1 && j != -1) { + // we are a binary aspect in the default package + lastSlash = i; + } + String fileName = sourceFilePath.substring(lastSlash + 1); + IProgramElement fileNode = new ProgramElement(asm, fileName, IProgramElement.Kind.FILE_JAVA, new SourceLocation(new File( + sourceFilePath), 1, 1), 0, null, null); + // fileNode.setSourceLocation(); + fileNode.addChild(NO_STRUCTURE); + return fileNode; + } + + /** + * For a specified node, check if any of the children more accurately represent the specified line. + * + * @param node where to start looking + * @param lineno the line number + * @return any closer match below 'node' or null if nothing is a more accurate match + */ + public IProgramElement findCloserMatchForLineNumber(IProgramElement node, int lineno) { + if (node == null || node.getChildren() == null) { + return null; + } + for (IProgramElement child : node.getChildren()) { + ISourceLocation childLoc = child.getSourceLocation(); + if (childLoc != null) { + if (childLoc.getLine() <= lineno && childLoc.getEndLine() >= lineno) { + // This child is a better match for that line number + IProgramElement evenCloserMatch = findCloserMatchForLineNumber(child, lineno); + if (evenCloserMatch == null) { + return child; + } else { + return evenCloserMatch; + } + } else if (child.getKind().isType()) { // types are a bit clueless about where they are... do other nodes have + // similar problems?? + IProgramElement evenCloserMatch = findCloserMatchForLineNumber(child, lineno); + if (evenCloserMatch != null) { + return evenCloserMatch; + } + } + } + } + return null; + } + + private IProgramElement findNodeForSourceLineHelper(IProgramElement node, String sourceFilePath, int lineno, int offset) { + if (matches(node, sourceFilePath, lineno, offset) && !hasMoreSpecificChild(node, sourceFilePath, lineno, offset)) { + return node; + } + + if (node != null) { + for (IProgramElement child : node.getChildren()) { + IProgramElement foundNode = findNodeForSourceLineHelper(child, sourceFilePath, lineno, offset); + if (foundNode != null) { + return foundNode; + } + } + } + + return null; + } + + private boolean matches(IProgramElement node, String sourceFilePath, int lineNumber, int offSet) { + // try { + // if (node != null && node.getSourceLocation() != null) + // System.err.println("====\n1: " + + // sourceFilePath + "\n2: " + + // node.getSourceLocation().getSourceFile().getCanonicalPath().equals(sourceFilePath) + // ); + ISourceLocation nodeSourceLocation = (node != null ? node.getSourceLocation() : null); + return node != null + && nodeSourceLocation != null + && nodeSourceLocation.getSourceFile().getAbsolutePath().equals(sourceFilePath) + && ((offSet != -1 && nodeSourceLocation.getOffset() == offSet) || offSet == -1) + && ((nodeSourceLocation.getLine() <= lineNumber && nodeSourceLocation.getEndLine() >= lineNumber) || (lineNumber <= 1 && node + .getKind().isSourceFile())); + // } catch (IOException ioe) { + // return false; + // } + } + + private boolean hasMoreSpecificChild(IProgramElement node, String sourceFilePath, int lineNumber, int offSet) { + for (IProgramElement child : node.getChildren()) { + if (matches(child, sourceFilePath, lineNumber, offSet)) { + return true; + } + } + return false; + } + + public String getConfigFile() { + return configFile; + } + + public void setConfigFile(String configFile) { + this.configFile = configFile; + } + + public IProgramElement findElementForHandle(String handle) { + return findElementForHandleOrCreate(handle, true); + } + + // TODO: optimize this lookup + // only want to create a file node if can't find the IPE if called through + // findElementForHandle() to mirror behaviour before pr141730 + public IProgramElement findElementForHandleOrCreate(String handle, boolean create) { + // try the cache first... + IProgramElement ipe = null; + synchronized (this) { + ipe = handleMap.get(handle); + if (ipe != null) { + return ipe; + } + ipe = findElementForHandle(root, handle); + if (ipe == null && create) { + ipe = createFileStructureNode(getFilename(handle)); + } + if (ipe != null) { + cache(handle, ipe); + } + } + return ipe; + } + + private IProgramElement findElementForHandle(IProgramElement parent, String handle) { + for (IProgramElement node : parent.getChildren()) { + String nodeHid = node.getHandleIdentifier(); + if (handle.equals(nodeHid)) { + return node; + } else { + if (handle.startsWith(nodeHid)) { + // it must be down here if it is anywhere + IProgramElement childSearch = findElementForHandle(node, handle); + if (childSearch != null) { + return childSearch; + } + } + } + } + return null; + } + + // + // private IProgramElement findElementForBytecodeInfo( + // IProgramElement node, + // String parentName, + // String name, + // String signature) { + // for (Iterator it = node.getChildren().iterator(); it.hasNext(); ) { + // IProgramElement curr = (IProgramElement)it.next(); + // if (parentName.equals(curr.getParent().getBytecodeName()) + // && name.equals(curr.getBytecodeName()) + // && signature.equals(curr.getBytecodeSignature())) { + // return node; + // } else { + // IProgramElement childSearch = findElementForBytecodeInfo(curr, parentName, name, signature); + // if (childSearch != null) return childSearch; + // } + // } + // return null; + // } + + protected void cache(String handle, IProgramElement pe) { + if (!AsmManager.isCompletingTypeBindings()) { + handleMap.put(handle, pe); + } + } + + public void flushTypeMap() { + typeMap.clear(); + + } + + public void flushHandleMap() { + handleMap.clear(); + } + + public void flushFileMap() { + fileMap.clear(); + } + + public void forget(IProgramElement compilationUnitNode, IProgramElement typeNode) { + String k = null; + synchronized (this) { + // handle map + // type map + for (Map.Entry<String, IProgramElement> typeMapEntry : typeMap.entrySet()) { + if (typeMapEntry.getValue() == typeNode) { + k = typeMapEntry.getKey(); + break; + } + } + if (k != null) { + typeMap.remove(k); + } + } + + if (compilationUnitNode != null) { + k = null; + for (Map.Entry<String, IProgramElement> entry : fileMap.entrySet()) { + if (entry.getValue() == compilationUnitNode) { + k = entry.getKey(); + break; + } + } + if (k != null) { + fileMap.remove(k); + } + } + } + + // TODO rename this method ... it does more than just the handle map + public void updateHandleMap(Set<String> deletedFiles) { + // Only delete the entries we need to from the handle map - for performance reasons + List<String> forRemoval = new ArrayList<String>(); + Set<String> k = null; + synchronized (this) { + k = handleMap.keySet(); + for (String handle : k) { + IProgramElement ipe = handleMap.get(handle); + if (ipe == null) { + System.err.println("handleMap expectation not met, where is the IPE for " + handle); + } + if (ipe == null || deletedFiles.contains(getCanonicalFilePath(ipe))) { + forRemoval.add(handle); + } + } + for (String handle : forRemoval) { + handleMap.remove(handle); + } + forRemoval.clear(); + k = typeMap.keySet(); + for (String typeName : k) { + IProgramElement ipe = typeMap.get(typeName); + if (deletedFiles.contains(getCanonicalFilePath(ipe))) { + forRemoval.add(typeName); + } + } + for (String typeName : forRemoval) { + typeMap.remove(typeName); + } + forRemoval.clear(); + } + for (Map.Entry<String, IProgramElement> entry : fileMap.entrySet()) { + String filePath = entry.getKey(); + if (deletedFiles.contains(getCanonicalFilePath(entry.getValue()))) { + forRemoval.add(filePath); + } + } + for (String filePath : forRemoval) { + fileMap.remove(filePath); + } + } + + private String getFilename(String hid) { + return asm.getHandleProvider().getFileForHandle(hid); + } + + private String getCanonicalFilePath(IProgramElement ipe) { + if (ipe.getSourceLocation() != null) { + return asm.getCanonicalFilePath(ipe.getSourceLocation().getSourceFile()); + } + return ""; + } + +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/CharOperation.java b/asm/src/main/java/org/aspectj/asm/internal/CharOperation.java new file mode 100644 index 000000000..bf5330a11 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/CharOperation.java @@ -0,0 +1,205 @@ +/******************************************************************** + * Copyright (c) 2006 Contributors. 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://eclipse.org/legal/epl-v10.html + * + * Contributors: IBM Corporation - initial API and implementation + * Helen Hawkins - initial version + *******************************************************************/ +package org.aspectj.asm.internal; + +/** + * Taken from org.aspectj.org.eclipse.jdt.core.compiler.CharOperation + * + */ +public class CharOperation { + + public static final char[][] NO_CHAR_CHAR = new char[0][]; + + public static final char[] NO_CHAR = new char[0]; + + /** + * Taken from org.aspectj.org.eclipse.jdt.core.compiler.CharOperation + */ + public static final char[] subarray(char[] array, int start, int end) { + if (end == -1) + end = array.length; + if (start > end) + return null; + if (start < 0) + return null; + if (end > array.length) + return null; + + char[] result = new char[end - start]; + System.arraycopy(array, start, result, 0, end - start); + return result; + } + + public static final char[][] subarray(char[][] array, int start, int end) { + if (end == -1) + end = array.length; + if (start > end) + return null; + if (start < 0) + return null; + if (end > array.length) + return null; + + char[][] result = new char[end - start][]; + System.arraycopy(array, start, result, 0, end - start); + return result; + } + + public static final char[][] splitOn(char divider, char[] array) { + int length = array == null ? 0 : array.length; + if (length == 0) + return NO_CHAR_CHAR; + + int wordCount = 1; + for (int i = 0; i < length; i++) + if (array[i] == divider) + wordCount++; + char[][] split = new char[wordCount][]; + int last = 0, currentWord = 0; + for (int i = 0; i < length; i++) { + if (array[i] == divider) { + split[currentWord] = new char[i - last]; + System.arraycopy(array, last, split[currentWord++], 0, i - last); + last = i + 1; + } + } + split[currentWord] = new char[length - last]; + System.arraycopy(array, last, split[currentWord], 0, length - last); + return split; + } + + /** + * Taken from org.aspectj.org.eclipse.jdt.core.compiler.CharOperation + */ + public static final int lastIndexOf(char toBeFound, char[] array) { + for (int i = array.length; --i >= 0;) + if (toBeFound == array[i]) + return i; + return -1; + } + + /** + * Taken from org.aspectj.org.eclipse.jdt.core.compiler.CharOperation + */ + public static final int indexOf(char toBeFound, char[] array) { + for (int i = 0; i < array.length; i++) + if (toBeFound == array[i]) + return i; + return -1; + } + + /** + * Taken from org.aspectj.org.eclipse.jdt.core.compiler.CharOperation + */ + public static final char[] concat(char[] first, char[] second) { + if (first == null) + return second; + if (second == null) + return first; + + int length1 = first.length; + int length2 = second.length; + char[] result = new char[length1 + length2]; + System.arraycopy(first, 0, result, 0, length1); + System.arraycopy(second, 0, result, length1, length2); + return result; + } + + /** + * Taken from org.aspectj.org.eclipse.jdt.core.compiler.CharOperation + */ + public static final boolean equals(char[] first, char[] second) { + if (first == second) + return true; + if (first == null || second == null) + return false; + if (first.length != second.length) + return false; + + for (int i = first.length; --i >= 0;) + if (first[i] != second[i]) + return false; + return true; + } + + final static public String toString(char[][] array) { + char[] result = concatWith(array, '.'); + return new String(result); + } + + public static final char[] concatWith(char[][] array, char separator) { + int length = array == null ? 0 : array.length; + if (length == 0) + return CharOperation.NO_CHAR; + + int size = length - 1; + int index = length; + while (--index >= 0) { + if (array[index].length == 0) + size--; + else + size += array[index].length; + } + if (size <= 0) + return CharOperation.NO_CHAR; + char[] result = new char[size]; + index = length; + while (--index >= 0) { + length = array[index].length; + if (length > 0) { + System.arraycopy(array[index], 0, result, (size -= length), length); + if (--size >= 0) + result[size] = separator; + } + } + return result; + } + + public static final int hashCode(char[] array) { + int length = array.length; + int hash = length == 0 ? 31 : array[0]; + if (length < 8) { + for (int i = length; --i > 0;) + hash = (hash * 31) + array[i]; + } else { + // 8 characters is enough to compute a decent hash code, don't waste time examining every character + for (int i = length - 1, last = i > 16 ? i - 16 : 0; i > last; i -= 2) + hash = (hash * 31) + array[i]; + } + return hash & 0x7FFFFFFF; + } + + public static final boolean equals(char[][] first, char[][] second) { + if (first == second) + return true; + if (first == null || second == null) + return false; + if (first.length != second.length) + return false; + + for (int i = first.length; --i >= 0;) + if (!equals(first[i], second[i])) + return false; + return true; + } + + /** + * Taken from org.aspectj.org.eclipse.jdt.core.compiler.CharOperation + */ + public static final void replace(char[] array, char toBeReplaced, char replacementChar) { + if (toBeReplaced != replacementChar) { + for (int i = 0, max = array.length; i < max; i++) { + if (array[i] == toBeReplaced) + array[i] = replacementChar; + } + } + } +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/HandleProviderDelimiter.java b/asm/src/main/java/org/aspectj/asm/internal/HandleProviderDelimiter.java new file mode 100644 index 000000000..9bbfc307c --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/HandleProviderDelimiter.java @@ -0,0 +1,143 @@ +/******************************************************************** + * Copyright (c) 2006 Contributors. 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://eclipse.org/legal/epl-v10.html + * + * Contributors: IBM Corporation - initial API and implementation + * Helen Hawkins - initial version + *******************************************************************/ +package org.aspectj.asm.internal; + +import org.aspectj.asm.IProgramElement; + +/** + * Uses "typesafe enum" pattern. + */ +public class HandleProviderDelimiter { + + // taken from JavaElement + public static final HandleProviderDelimiter JAVAPROJECT = new HandleProviderDelimiter('='); + public static final HandleProviderDelimiter PACKAGEFRAGMENT = new HandleProviderDelimiter('<'); + public static final HandleProviderDelimiter FIELD = new HandleProviderDelimiter('^'); + public static final HandleProviderDelimiter METHOD = new HandleProviderDelimiter('~'); + public static final HandleProviderDelimiter INITIALIZER = new HandleProviderDelimiter('|'); + public static final HandleProviderDelimiter COMPILATIONUNIT = new HandleProviderDelimiter('{'); + public static final HandleProviderDelimiter CLASSFILE = new HandleProviderDelimiter('('); + public static final HandleProviderDelimiter TYPE = new HandleProviderDelimiter('['); + public static final HandleProviderDelimiter IMPORTDECLARATION = new HandleProviderDelimiter('#'); + public static final HandleProviderDelimiter COUNT = new HandleProviderDelimiter('!'); + public static final HandleProviderDelimiter ESCAPE = new HandleProviderDelimiter('\\'); + public static final HandleProviderDelimiter PACKAGEDECLARATION = new HandleProviderDelimiter('%'); + public static final HandleProviderDelimiter PACKAGEFRAGMENTROOT = new HandleProviderDelimiter('/'); + // these below are not currently used because no iprogramelement.kind + // equivalent + public static final HandleProviderDelimiter LOCALVARIABLE = new HandleProviderDelimiter('@'); + public static final HandleProviderDelimiter TYPE_PARAMETER = new HandleProviderDelimiter(']'); + + // AspectJ specific ones + public static final HandleProviderDelimiter ASPECT_CU = new HandleProviderDelimiter('*'); + public static final HandleProviderDelimiter ADVICE = new HandleProviderDelimiter('&'); + public static final HandleProviderDelimiter ASPECT_TYPE = new HandleProviderDelimiter('\''); + public static final HandleProviderDelimiter CODEELEMENT = new HandleProviderDelimiter('?'); + public static final HandleProviderDelimiter ITD_FIELD = new HandleProviderDelimiter(','); + public static final HandleProviderDelimiter ITD = new HandleProviderDelimiter(')'); + public static final HandleProviderDelimiter DECLARE = new HandleProviderDelimiter('`'); + public static final HandleProviderDelimiter POINTCUT = new HandleProviderDelimiter('"'); + + public static final HandleProviderDelimiter PHANTOM = new HandleProviderDelimiter(';'); + + private static char empty = ' '; + private final char delim; + + private HandleProviderDelimiter(char delim) { + this.delim = delim; + } + + /** + * Returns the delimiter for the HandleProviderDelimiter, for example ASPECT returns '*' and METHOD returns '~' + */ + public char getDelimiter() { + return delim; + } + + /** + * Returns the delimiter for the given IProgramElement for example if the IProgramElement is an aspect returns '*' and if the + * IProgramElement is a method returns '~' + */ + public static char getDelimiter(IProgramElement ipe) { + IProgramElement.Kind kind = ipe.getKind(); + if (kind.equals(IProgramElement.Kind.PROJECT)) { + return JAVAPROJECT.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.PACKAGE)) { + return PACKAGEFRAGMENT.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.FILE_JAVA)) { + if (ipe.getName().endsWith(".aj")) { + return ASPECT_CU.getDelimiter(); + } else { + return COMPILATIONUNIT.getDelimiter(); + } + } else if (kind.equals(IProgramElement.Kind.FILE_ASPECTJ)) { + return ASPECT_CU.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.IMPORT_REFERENCE)) { + return IMPORTDECLARATION.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.PACKAGE_DECLARATION)) { + return PACKAGEDECLARATION.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.CLASS) || kind.equals(IProgramElement.Kind.INTERFACE) + || kind.equals(IProgramElement.Kind.ENUM) || kind.equals(IProgramElement.Kind.ANNOTATION)) { + return TYPE.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.ASPECT)) { + if (ipe.isAnnotationStyleDeclaration()) { + return TYPE.getDelimiter(); + } else { + return ASPECT_TYPE.getDelimiter(); + } + } else if (kind.equals(IProgramElement.Kind.INITIALIZER)) { + return INITIALIZER.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.INTER_TYPE_FIELD)) { + return ITD_FIELD.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.INTER_TYPE_METHOD) || kind.equals(IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR) + || kind.equals(IProgramElement.Kind.INTER_TYPE_PARENT)) { + return ITD.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.CONSTRUCTOR) || kind.equals(IProgramElement.Kind.METHOD)) { + return METHOD.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.FIELD) || kind.equals(IProgramElement.Kind.ENUM_VALUE)) { + return FIELD.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.POINTCUT)) { + if (ipe.isAnnotationStyleDeclaration()) { + return METHOD.getDelimiter(); + } else { + return POINTCUT.getDelimiter(); + } + } else if (kind.equals(IProgramElement.Kind.ADVICE)) { + if (ipe.isAnnotationStyleDeclaration()) { + return METHOD.getDelimiter(); + } else { + return ADVICE.getDelimiter(); + } + } else if (kind.equals(IProgramElement.Kind.DECLARE_PARENTS) || kind.equals(IProgramElement.Kind.DECLARE_WARNING) + || kind.equals(IProgramElement.Kind.DECLARE_ERROR) || kind.equals(IProgramElement.Kind.DECLARE_SOFT) + || kind.equals(IProgramElement.Kind.DECLARE_PRECEDENCE) + || kind.equals(IProgramElement.Kind.DECLARE_ANNOTATION_AT_CONSTRUCTOR) + || kind.equals(IProgramElement.Kind.DECLARE_ANNOTATION_AT_FIELD) + || kind.equals(IProgramElement.Kind.DECLARE_ANNOTATION_AT_METHOD) + || kind.equals(IProgramElement.Kind.DECLARE_ANNOTATION_AT_TYPE)) { + return DECLARE.getDelimiter(); + } else if (kind.equals(IProgramElement.Kind.CODE)) { + return CODEELEMENT.getDelimiter(); + } else if (kind == IProgramElement.Kind.FILE) { + if (ipe.getName().endsWith(".class")) { + return CLASSFILE.getDelimiter(); + } else if (ipe.getName().endsWith(".aj")) { + return ASPECT_CU.getDelimiter(); + } else if (ipe.getName().endsWith(".java")) { + return COMPILATIONUNIT.getDelimiter(); + } else { + return empty; + } + } + return empty; + } + +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/JDTLikeHandleProvider.java b/asm/src/main/java/org/aspectj/asm/internal/JDTLikeHandleProvider.java new file mode 100644 index 000000000..3751dfc1b --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/JDTLikeHandleProvider.java @@ -0,0 +1,474 @@ +/******************************************************************** + * Copyright (c) 2006 Contributors. 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://eclipse.org/legal/epl-v10.html + * + * Contributors: IBM Corporation - initial API and implementation + * Helen Hawkins - initial version + *******************************************************************/ +package org.aspectj.asm.internal; + +import java.io.File; +import java.util.Iterator; +import java.util.List; + +import org.aspectj.asm.AsmManager; +import org.aspectj.asm.IElementHandleProvider; +import org.aspectj.asm.IProgramElement; +import org.aspectj.bridge.ISourceLocation; + +/** + * Creates JDT-like handles, for example + * + * method with string argument: <tjp{Demo.java[Demo~main~\[QString; method with generic argument: + * <pkg{MyClass.java[MyClass~myMethod~QList\<QString;>; an aspect: <pkg*A1.aj}A1 advice with Integer arg: + * <pkg*A8.aj}A8&afterReturning&QInteger; method call: <pkg*A10.aj[C~m1?method-call(void pkg.C.m2()) + * + */ +public class JDTLikeHandleProvider implements IElementHandleProvider { + + private final AsmManager asm; + + private static final char[] empty = new char[] {}; + private static final char[] countDelim = new char[] { HandleProviderDelimiter.COUNT.getDelimiter() }; + + private static final String backslash = "\\"; + private static final String emptyString = ""; + + public JDTLikeHandleProvider(AsmManager asm) { + this.asm = asm; + } + + public void initialize() { + // nothing to do + } + + public String createHandleIdentifier(IProgramElement ipe) { + // AjBuildManager.setupModel --> top of the tree is either + // <root> or the .lst file + if (ipe == null || (ipe.getKind().equals(IProgramElement.Kind.FILE_JAVA) && ipe.getName().equals("<root>"))) { + return ""; + } else if (ipe.getHandleIdentifier(false) != null) { + // have already created the handle for this ipe + // therefore just return it + return ipe.getHandleIdentifier(); + } else if (ipe.getKind().equals(IProgramElement.Kind.FILE_LST)) { + String configFile = asm.getHierarchy().getConfigFile(); + int start = configFile.lastIndexOf(File.separator); + int end = configFile.lastIndexOf(".lst"); + if (end != -1) { + configFile = configFile.substring(start + 1, end); + } else { + configFile = new StringBuffer("=").append(configFile.substring(start + 1)).toString(); + } + ipe.setHandleIdentifier(configFile); + return configFile; + } else if (ipe.getKind() == IProgramElement.Kind.SOURCE_FOLDER) { + StringBuffer sb = new StringBuffer(); + sb.append(createHandleIdentifier(ipe.getParent())).append("/"); + // pr249216 - escape any embedded slashes + String folder = ipe.getName(); + if (folder.endsWith("/")) { + folder = folder.substring(0, folder.length() - 1); + } + if (folder.indexOf("/") != -1) { + folder = folder.replace("/", "\\/"); + } + sb.append(folder); + String handle = sb.toString(); + ipe.setHandleIdentifier(handle); + return handle; + } + IProgramElement parent = ipe.getParent(); + if (parent != null && parent.getKind().equals(IProgramElement.Kind.IMPORT_REFERENCE)) { + // want to miss out '#import declaration' in the handle + parent = ipe.getParent().getParent(); + } + + StringBuffer handle = new StringBuffer(); + // add the handle for the parent + handle.append(createHandleIdentifier(parent)); + // add the correct delimiter for this ipe + handle.append(HandleProviderDelimiter.getDelimiter(ipe)); + // add the name and any parameters unless we're an initializer + // (initializer's names are '...') + if (!ipe.getKind().equals(IProgramElement.Kind.INITIALIZER)) { + if (ipe.getKind() == IProgramElement.Kind.CLASS && ipe.getName().endsWith("{..}")) { + // format: 'new Runnable() {..}' but its anon-y-mouse + // dont append anything, there may be a count to follow though (!<n>) + } else { + if (ipe.getKind() == IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR) { + handle.append(ipe.getName()).append("_new").append(getParameters(ipe)); + } else { + // if (ipe.getKind() == IProgramElement.Kind.PACKAGE && ipe.getName().equals("DEFAULT")) { + // // the delimiter will be in there, but skip the word DEFAULT as it is just a placeholder + // } else { + if (ipe.getKind().isDeclareAnnotation()) { + // escape the @ (pr249216c9) + handle.append("declare \\@").append(ipe.getName().substring(9)).append(getParameters(ipe)); + } else { + if (ipe.getFullyQualifiedName() != null) { + handle.append(ipe.getFullyQualifiedName()); + } else { + handle.append(ipe.getName()); + } + handle.append(getParameters(ipe)); + } + } + // } + } + } + // add the count, for example '!2' if its the second ipe of its + // kind in the aspect + handle.append(getCount(ipe)); + + ipe.setHandleIdentifier(handle.toString()); + return handle.toString(); + } + + private String getParameters(IProgramElement ipe) { + if (ipe.getParameterSignatures() == null || ipe.getParameterSignatures().isEmpty()) { + return ""; + } + List<String> sourceRefs = ipe.getParameterSignaturesSourceRefs(); + List<char[]> parameterTypes = ipe.getParameterSignatures(); + StringBuffer sb = new StringBuffer(); + if (sourceRefs != null) { + for (int i = 0; i < sourceRefs.size(); i++) { + String sourceRef = sourceRefs.get(i); + sb.append(HandleProviderDelimiter.getDelimiter(ipe)); + sb.append(sourceRef); + } + } else { + for (char[] element : parameterTypes) { + sb.append(HandleProviderDelimiter.getDelimiter(ipe)); + sb.append(NameConvertor.createShortName(element, false, false)); + } + } + return sb.toString(); + } + + /** + * Determine a count to be suffixed to the handle, this is only necessary for identical looking entries at the same level in the + * model (for example two anonymous class declarations). The format is !<n> where n will be greater than 2. + * + * @param ipe the program element for which the handle is being constructed + * @return a char suffix that will either be empty or of the form "!<n>" + */ + private char[] getCount(IProgramElement ipe) { + // TODO could optimize this code + char[] byteCodeName = ipe.getBytecodeName().toCharArray(); + + if (ipe.getKind().isInterTypeMember()) { + int count = 1; + List<IProgramElement> kids = ipe.getParent().getChildren(); + for (Iterator<IProgramElement> iterator = kids.iterator(); iterator.hasNext();) { + IProgramElement object = iterator.next(); + if (object.equals(ipe)) { + break; + } + if (object.getKind().isInterTypeMember()) { + if (object.getName().equals(ipe.getName()) && getParameters(object).equals(getParameters(ipe))) { + String existingHandle = object.getHandleIdentifier(); + int suffixPosition = existingHandle.indexOf('!'); + if (suffixPosition != -1) { + count = new Integer(existingHandle.substring(suffixPosition + 1)).intValue() + 1; + } else { + if (count == 1) { + count = 2; + } + } + } + } + } + if (count > 1) { + return CharOperation.concat(countDelim, new Integer(count).toString().toCharArray()); + } + } else if (ipe.getKind().isDeclare()) { + // // look at peer declares + int count = computeCountBasedOnPeers(ipe); + if (count > 1) { + return CharOperation.concat(countDelim, new Integer(count).toString().toCharArray()); + } + } else if (ipe.getKind().equals(IProgramElement.Kind.ADVICE)) { + // Look at any peer advice + int count = 1; + List<IProgramElement> kids = ipe.getParent().getChildren(); + String ipeSig = ipe.getBytecodeSignature(); + // remove return type from the signature - it should not be included in the comparison + int idx = 0; + ipeSig = shortenIpeSig(ipeSig); + for (IProgramElement object : kids) { + if (object.equals(ipe)) { + break; + } + if (object.getKind() == ipe.getKind()) { + if (object.getName().equals(ipe.getName())) { + String sig1 = object.getBytecodeSignature(); + if (sig1 != null && (idx = sig1.indexOf(")")) != -1) { + sig1 = sig1.substring(0, idx); + } + // this code needs a speed overhaul... and some proper tests + // Two static parts because one may be enclosing jpsp (269522) + if (sig1 != null) { + if (sig1.indexOf("Lorg/aspectj/lang") != -1) { + if (sig1.endsWith("Lorg/aspectj/lang/JoinPoint$StaticPart;")) { + sig1 = sig1.substring(0, sig1.lastIndexOf("Lorg/aspectj/lang/JoinPoint$StaticPart;")); + } + if (sig1.endsWith("Lorg/aspectj/lang/JoinPoint;")) { + sig1 = sig1.substring(0, sig1.lastIndexOf("Lorg/aspectj/lang/JoinPoint;")); + } + if (sig1.endsWith("Lorg/aspectj/lang/JoinPoint$StaticPart;")) { + sig1 = sig1.substring(0, sig1.lastIndexOf("Lorg/aspectj/lang/JoinPoint$StaticPart;")); + } + } + } + + if (sig1 == null && ipeSig == null || (sig1 != null && sig1.equals(ipeSig))) { + String existingHandle = object.getHandleIdentifier(); + int suffixPosition = existingHandle.indexOf('!'); + if (suffixPosition != -1) { + count = new Integer(existingHandle.substring(suffixPosition + 1)).intValue() + 1; + } else { + if (count == 1) { + count = 2; + } + } + } + } + } + } + if (count > 1) { + return CharOperation.concat(countDelim, new Integer(count).toString().toCharArray()); + } + } else if (ipe.getKind().equals(IProgramElement.Kind.INITIALIZER)) { + // return String.valueOf(++initializerCounter).toCharArray(); + // Look at any peer advice + int count = 1; + List<IProgramElement> kids = ipe.getParent().getChildren(); + String ipeSig = ipe.getBytecodeSignature(); + // remove return type from the signature - it should not be included in the comparison + int idx = 0; + ipeSig = shortenIpeSig(ipeSig); + for (IProgramElement object : kids) { + if (object.equals(ipe)) { + break; + } + if (object.getKind() == ipe.getKind()) { + if (object.getName().equals(ipe.getName())) { + String sig1 = object.getBytecodeSignature(); + if (sig1 != null && (idx = sig1.indexOf(")")) != -1) { + sig1 = sig1.substring(0, idx); + } + // this code needs a speed overhaul... and some proper tests + // Two static parts because one may be enclosing jpsp (269522) + if (sig1 != null) { + if (sig1.indexOf("Lorg/aspectj/lang") != -1) { + if (sig1.endsWith("Lorg/aspectj/lang/JoinPoint$StaticPart;")) { + sig1 = sig1.substring(0, sig1.lastIndexOf("Lorg/aspectj/lang/JoinPoint$StaticPart;")); + } + if (sig1.endsWith("Lorg/aspectj/lang/JoinPoint;")) { + sig1 = sig1.substring(0, sig1.lastIndexOf("Lorg/aspectj/lang/JoinPoint;")); + } + if (sig1.endsWith("Lorg/aspectj/lang/JoinPoint$StaticPart;")) { + sig1 = sig1.substring(0, sig1.lastIndexOf("Lorg/aspectj/lang/JoinPoint$StaticPart;")); + } + } + } + + if (sig1 == null && ipeSig == null || (sig1 != null && sig1.equals(ipeSig))) { + String existingHandle = object.getHandleIdentifier(); + int suffixPosition = existingHandle.indexOf('!'); + if (suffixPosition != -1) { + count = new Integer(existingHandle.substring(suffixPosition + 1)).intValue() + 1; + } else { + if (count == 1) { + count = 2; + } + } + } + } + } + } + // if (count > 1) { + return new Integer(count).toString().toCharArray(); + // return CharOperation.concat(countDelim, new Integer(count).toString().toCharArray()); + // } + } else if (ipe.getKind().equals(IProgramElement.Kind.CODE)) { + int index = CharOperation.lastIndexOf('!', byteCodeName); + if (index != -1) { + return convertCount(CharOperation.subarray(byteCodeName, index + 1, byteCodeName.length)); + } + } else if (ipe.getKind() == IProgramElement.Kind.CLASS) { + // depends on previous children + int count = 1; + List<IProgramElement> kids = ipe.getParent().getChildren(); + if (ipe.getName().endsWith("{..}")) { + // only depends on previous anonymous children, name irrelevant + for (IProgramElement object : kids) { + if (object.equals(ipe)) { + break; + } + if (object.getKind() == ipe.getKind()) { + if (object.getName().endsWith("{..}")) { + String existingHandle = object.getHandleIdentifier(); + int suffixPosition = existingHandle.lastIndexOf('!'); + int lastSquareBracket = existingHandle.lastIndexOf('['); // type delimiter + if (suffixPosition != -1 && lastSquareBracket < suffixPosition) { // pr260384 + count = new Integer(existingHandle.substring(suffixPosition + 1)).intValue() + 1; + } else { + if (count == 1) { + count = 2; + } + } + } + } + } + } else { + for (IProgramElement object : kids) { + if (object.equals(ipe)) { + break; + } + if (object.getKind() == ipe.getKind()) { + if (object.getName().equals(ipe.getName())) { + String existingHandle = object.getHandleIdentifier(); + int suffixPosition = existingHandle.lastIndexOf('!'); + int lastSquareBracket = existingHandle.lastIndexOf('['); // type delimiter + if (suffixPosition != -1 && lastSquareBracket < suffixPosition) { // pr260384 + count = new Integer(existingHandle.substring(suffixPosition + 1)).intValue() + 1; + } else { + if (count == 1) { + count = 2; + } + } + } + } + } + } + if (count > 1) { + return CharOperation.concat(countDelim, new Integer(count).toString().toCharArray()); + } + } + return empty; + } + + private String shortenIpeSig(String ipeSig) { + int idx; + if (ipeSig != null && ((idx = ipeSig.indexOf(")")) != -1)) { + ipeSig = ipeSig.substring(0, idx); + } + if (ipeSig != null) { + if (ipeSig.indexOf("Lorg/aspectj/lang") != -1) { + if (ipeSig.endsWith("Lorg/aspectj/lang/JoinPoint$StaticPart;")) { + ipeSig = ipeSig.substring(0, ipeSig.lastIndexOf("Lorg/aspectj/lang/JoinPoint$StaticPart;")); + } + if (ipeSig.endsWith("Lorg/aspectj/lang/JoinPoint;")) { + ipeSig = ipeSig.substring(0, ipeSig.lastIndexOf("Lorg/aspectj/lang/JoinPoint;")); + } + if (ipeSig.endsWith("Lorg/aspectj/lang/JoinPoint$StaticPart;")) { + ipeSig = ipeSig.substring(0, ipeSig.lastIndexOf("Lorg/aspectj/lang/JoinPoint$StaticPart;")); + } + } + } + return ipeSig; + } + + private int computeCountBasedOnPeers(IProgramElement ipe) { + int count = 1; + for (IProgramElement object : ipe.getParent().getChildren()) { + if (object.equals(ipe)) { + break; + } + if (object.getKind() == ipe.getKind()) { + if (object.getKind().toString().equals(ipe.getKind().toString())) { + String existingHandle = object.getHandleIdentifier(); + int suffixPosition = existingHandle.indexOf('!'); + if (suffixPosition != -1) { + count = new Integer(existingHandle.substring(suffixPosition + 1)).intValue() + 1; + } else { + if (count == 1) { + count = 2; + } + } + } + } + } + return count; + } + + /** + * Only returns the count if it's not equal to 1 + */ + private char[] convertCount(char[] c) { + if ((c.length == 1 && c[0] != ' ' && c[0] != '1') || c.length > 1) { + return CharOperation.concat(countDelim, c); + } + return empty; + } + + public String getFileForHandle(String handle) { + IProgramElement node = asm.getHierarchy().getElement(handle); + if (node != null) { + return asm.getCanonicalFilePath(node.getSourceLocation().getSourceFile()); + } else if (handle.charAt(0) == HandleProviderDelimiter.ASPECT_CU.getDelimiter() + || handle.charAt(0) == HandleProviderDelimiter.COMPILATIONUNIT.getDelimiter()) { + // it's something like *MyAspect.aj or {MyClass.java. In other words + // it's a file node that's been created with no children and no + // parent + return backslash + handle.substring(1); + } + return emptyString; + } + + public int getLineNumberForHandle(String handle) { + IProgramElement node = asm.getHierarchy().getElement(handle); + if (node != null) { + return node.getSourceLocation().getLine(); + } else if (handle.charAt(0) == HandleProviderDelimiter.ASPECT_CU.getDelimiter() + || handle.charAt(0) == HandleProviderDelimiter.COMPILATIONUNIT.getDelimiter()) { + // it's something like *MyAspect.aj or {MyClass.java. In other words + // it's a file node that's been created with no children and no + // parent + return 1; + } + return -1; + } + + public int getOffSetForHandle(String handle) { + IProgramElement node = asm.getHierarchy().getElement(handle); + if (node != null) { + return node.getSourceLocation().getOffset(); + } else if (handle.charAt(0) == HandleProviderDelimiter.ASPECT_CU.getDelimiter() + || handle.charAt(0) == HandleProviderDelimiter.COMPILATIONUNIT.getDelimiter()) { + // it's something like *MyAspect.aj or {MyClass.java. In other words + // it's a file node that's been created with no children and no + // parent + return 0; + } + return -1; + } + + public String createHandleIdentifier(ISourceLocation location) { + IProgramElement node = asm.getHierarchy().findElementForSourceLine(location); + if (node != null) { + return createHandleIdentifier(node); + } + return null; + } + + public String createHandleIdentifier(File sourceFile, int line, int column, int offset) { + IProgramElement node = asm.getHierarchy().findElementForOffSet(sourceFile.getAbsolutePath(), line, offset); + if (node != null) { + return createHandleIdentifier(node); + } + return null; + } + + public boolean dependsOnLocation() { + // handles are independent of soureLocations therefore return false + return false; + } + +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/NameConvertor.java b/asm/src/main/java/org/aspectj/asm/internal/NameConvertor.java new file mode 100644 index 000000000..340d41586 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/NameConvertor.java @@ -0,0 +1,253 @@ +/******************************************************************** + * Copyright (c) 2006 Contributors. 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://eclipse.org/legal/epl-v10.html + * + * Contributors: IBM Corporation - initial API and implementation + * Helen Hawkins - initial version + *******************************************************************/ +package org.aspectj.asm.internal; + +public class NameConvertor { + + private static final char BOOLEAN = 'Z'; + private static final char BYTE = 'B'; + private static final char CHAR = 'C'; + private static final char DOUBLE = 'D'; + private static final char FLOAT = 'F'; + private static final char INT = 'I'; + private static final char LONG = 'J'; + private static final char SHORT = 'S'; + private static final char ARRAY = '['; + private static final char RESOLVED = 'L'; + private static final char UNRESOLVED = 'Q'; + + public static final char PARAMETERIZED = 'P'; + + private static final char[] BOOLEAN_NAME = new char[] { 'b', 'o', 'o', 'l', 'e', 'a', 'n' }; + private static final char[] BYTE_NAME = new char[] { 'b', 'y', 't', 'e' }; + private static final char[] CHAR_NAME = new char[] { 'c', 'h', 'a', 'r' }; + private static final char[] DOUBLE_NAME = new char[] { 'd', 'o', 'u', 'b', 'l', 'e' }; + private static final char[] FLOAT_NAME = new char[] { 'f', 'l', 'o', 'a', 't' }; + private static final char[] INT_NAME = new char[] { 'i', 'n', 't' }; + private static final char[] LONG_NAME = new char[] { 'l', 'o', 'n', 'g' }; + private static final char[] SHORT_NAME = new char[] { 's', 'h', 'o', 'r', 't' }; + + private static final char[] SQUARE_BRACKETS = new char[] { '[', ']' }; + private static final char[] GREATER_THAN = new char[] { '>' }; + private static final char[] LESS_THAN = new char[] { '<' }; + private static final char[] COMMA = new char[] { ',' }; + private static final char[] BACKSLASH_LESSTHAN = new char[] { '\\', '<' }; + private static final char[] SEMICOLON = new char[] { ';' }; + + /** + * Creates a readable name from the given char array, for example, given 'I' returns 'int'. Moreover, given + * 'Ljava/lang/String;<Ljava/lang/String;>' returns 'java.lang.String<java.lang.String>' + */ + public static char[] convertFromSignature(char[] c) { + int lt = CharOperation.indexOf('<', c); + int sc = CharOperation.indexOf(';', c); + int gt = CharOperation.indexOf('>', c); + + int smallest = 0; + if (lt == -1 && sc == -1 && gt == -1) { + // we have something like 'Ljava/lang/String' or 'I' + return getFullyQualifiedTypeName(c); + } else if (lt != -1 && (sc == -1 || lt <= sc) && (gt == -1 || lt <= gt)) { + // we have something like 'Ljava/lang/String<I' + smallest = lt; + } else if (sc != -1 && (lt == -1 || sc <= lt) && (gt == -1 || sc <= gt)) { + // we have something like 'Ljava/lang/String;I' + smallest = sc; + } else { + // we have something like '>;' + smallest = gt; + } + char[] first = CharOperation.subarray(c, 0, smallest); + char[] second = CharOperation.subarray(c, smallest + 1, c.length); + if (smallest == 0 && first.length == 0 && c[0] == '>') { + // c = {'>',';'} therefore we just want to return '>' to + // close the generic signature + return GREATER_THAN; + } else if (first.length == 1 && second.length == 0) { + return first; + } else if (second.length == 0 || (second.length == 1 && second[0] == ';')) { + // we've reached the end of the array, therefore only care about + // the first part + return convertFromSignature(first); + } else if (smallest == lt) { + // if c = 'Ljava/lang/String;<I' then first = 'Ljava/Lang/String;' and + // second = 'I'. Want to end up with 'Ljava.lang.String<I' and so add + // the '<' back. + char[] inclLT = CharOperation.concat(convertFromSignature(first), LESS_THAN); + return CharOperation.concat(inclLT, convertFromSignature(second)); + } else if (smallest == gt) { + char[] inclLT = CharOperation.concat(convertFromSignature(first), GREATER_THAN); + return CharOperation.concat(inclLT, convertFromSignature(second)); + } else if (second.length != 2) { + // if c = 'Ljava/lang/Sting;LMyClass' then first = 'Ljava/lang/String' + // and second = 'LMyClass'. Want to end up with 'java.lang.String,MyClass + // so want to add a ','. However, only want to do this if we're in the + // middle of a '<...>' + char[] inclComma = CharOperation.concat(convertFromSignature(first), COMMA); + return CharOperation.concat(inclComma, convertFromSignature(second)); + } + return CharOperation.concat(convertFromSignature(first), convertFromSignature(second)); + } + + /** + * Given a char array, returns the type name for this. For example 'I' returns 'int', 'Ljava/lang/String' returns + * 'java.lang.String' and '[Ljava/lang/String' returns 'java.lang.String[]' + * + * NOTE: Doesn't go any deaper so given 'Ljava/lang/String;<Ljava/lang/String;>' it would return + * 'java.lang.String;<Ljava.lang.String;>', however, only called with something like 'Ljava/lang/String' + */ + private static char[] getFullyQualifiedTypeName(char[] c) { + if (c.length == 0) { + return c; + } + if (c[0] == BOOLEAN) { + return BOOLEAN_NAME; + } else if (c[0] == BYTE) { + return BYTE_NAME; + } else if (c[0] == CHAR) { + return CHAR_NAME; + } else if (c[0] == DOUBLE) { + return DOUBLE_NAME; + } else if (c[0] == FLOAT) { + return FLOAT_NAME; + } else if (c[0] == INT) { + return INT_NAME; + } else if (c[0] == LONG) { + return LONG_NAME; + } else if (c[0] == SHORT) { + return SHORT_NAME; + } else if (c[0] == ARRAY) { + return CharOperation.concat(getFullyQualifiedTypeName(CharOperation.subarray(c, 1, c.length)), SQUARE_BRACKETS); + } else { + char[] type = CharOperation.subarray(c, 1, c.length); + CharOperation.replace(type, '/', '.'); + return type; + } + } + + // public static char[] createShortName(char[] c) { + // return createShortName(c, false); + // } + + /** + * Given 'Ppkg/MyGenericClass<Ljava/lang/String;Ljava/lang/Integer;>;' will return 'QMyGenericClass<QString;QInteger;>;' + */ + public static char[] createShortName(char[] c, boolean haveFullyQualifiedAtLeastOneThing, boolean needsFullyQualifiedFirstEntry) { + if (c[0] == '[') { + char[] ret = CharOperation.concat( + new char[] { '\\', '[' }, + createShortName(CharOperation.subarray(c, 1, c.length), haveFullyQualifiedAtLeastOneThing, + needsFullyQualifiedFirstEntry)); + return ret; + } else if (c[0] == '+') { + char[] ret = CharOperation.concat( + new char[] { '+' }, + createShortName(CharOperation.subarray(c, 1, c.length), haveFullyQualifiedAtLeastOneThing, + needsFullyQualifiedFirstEntry)); + return ret; + } else if (c[0] == '*') { + return c; // c is *>; + } + int lt = CharOperation.indexOf('<', c); + int sc = CharOperation.indexOf(';', c); + int gt = CharOperation.indexOf('>', c); + + int smallest = 0; + if (lt == -1 && sc == -1 && gt == -1) { + // we have something like 'Ljava/lang/String' or 'I' + if (!needsFullyQualifiedFirstEntry) { + return getTypeName(c, true); + } else { + return getTypeName(c, haveFullyQualifiedAtLeastOneThing); + } + } else if (lt != -1 && (sc == -1 || lt <= sc) && (gt == -1 || lt <= gt)) { + // we have something like 'Ljava/lang/String<I' + smallest = lt; + } else if (sc != -1 && (lt == -1 || sc <= lt) && (gt == -1 || sc <= gt)) { + // we have something like 'Ljava/lang/String;I' + smallest = sc; + } else { + // we have something like '>;' + smallest = gt; + } + char[] first = CharOperation.subarray(c, 0, smallest); + char[] second = CharOperation.subarray(c, smallest + 1, c.length); + if (smallest == 0 && first.length == 0 && c[0] == '>') { + // c = {'>',';'} therefore we just want to return c to + // close the generic signature + return c; + } else if (first.length == 1 && second.length == 0) { + return first; + } else if (second.length == 0 || (second.length == 1 && second[0] == ';')) { + // we've reached the end of the array, therefore only care about + // the first part + return CharOperation.concat(createShortName(first, haveFullyQualifiedAtLeastOneThing, needsFullyQualifiedFirstEntry), + new char[] { ';' }); + } else if (smallest == lt) { + // if c = 'Ljava/lang/String;<I' then first = 'Ljava/Lang/String;' and + // second = 'I'. Want to end up with 'LString<I' and so add + // the '<' back. + char[] inclLT = CharOperation.concat(createShortName(first, haveFullyQualifiedAtLeastOneThing, true), + BACKSLASH_LESSTHAN); + return CharOperation.concat(inclLT, createShortName(second, true, false)); + } else if (smallest == gt) { + char[] inclLT = CharOperation.concat( + createShortName(first, haveFullyQualifiedAtLeastOneThing, needsFullyQualifiedFirstEntry), GREATER_THAN); + return CharOperation.concat(inclLT, createShortName(second, true, false)); + } else { + // if c = 'Ljava/lang/Sting;LMyClass;' then first = 'Ljava/lang/String' + // and second = 'LMyClass;'. Want to end up with 'QString;QMyClass; + // so add the ';' back + char[] firstTypeParam = CharOperation.concat(createShortName(first, haveFullyQualifiedAtLeastOneThing, false), + SEMICOLON); + return CharOperation.concat(firstTypeParam, createShortName(second, true, false)); + } + } + + // public static char[] getTypeName(char[] name) { + // return getTypeName(name, false); + // } + + /** + * Convert a typename into its handle form. There are various cases to consider here - many are discussed in pr249216. The flag + * allreadyFQd indicates if we've already included a fq'd name in what we are creating - if we have then further references + * should not be fq'd and can be the short name (so java.util.Set becomes just Set). + * + */ + + /** + * Given 'Qjava/lang/String;' returns 'QString;' + */ + public static char[] getTypeName(char[] name, boolean haveFullyQualifiedAtLeastOneThing) { + if (!haveFullyQualifiedAtLeastOneThing) { + if (name[0] == RESOLVED || name[0] == PARAMETERIZED) { + char[] sub = CharOperation.subarray(name, 1, name.length); + CharOperation.replace(sub, '/', '.'); + return CharOperation.concat(new char[] { UNRESOLVED }, sub); + } else { + char[] sub = CharOperation.subarray(name, 1, name.length); + CharOperation.replace(sub, '/', '.'); + return CharOperation.concat(new char[] { name[0] }, sub); + } + } else { + int i = CharOperation.lastIndexOf('/', name); + if (i != -1) { + if (name[0] == RESOLVED || name[0] == PARAMETERIZED) { + return CharOperation.concat(new char[] { UNRESOLVED }, CharOperation.subarray(name, i + 1, name.length)); + } else { + return CharOperation.concat(new char[] { name[0] }, CharOperation.subarray(name, i + 1, name.length)); + } + } + } + return name; + } + +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/ProgramElement.java b/asm/src/main/java/org/aspectj/asm/internal/ProgramElement.java new file mode 100644 index 000000000..51aafd936 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/ProgramElement.java @@ -0,0 +1,852 @@ +/* ******************************************************************* + * Copyright (c) 2003,2010 Contributors. + * 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: + * Mik Kersten initial implementation + * Andy Clement, IBM, SpringSource Extensions for better IDE representation + * ******************************************************************/ + +package org.aspectj.asm.internal; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.aspectj.asm.AsmManager; +import org.aspectj.asm.HierarchyWalker; +import org.aspectj.asm.IProgramElement; +import org.aspectj.bridge.IMessage; +import org.aspectj.bridge.ISourceLocation; + +/** + * @author Mik Kersten + * @author Andy Clement + */ +public class ProgramElement implements IProgramElement { + + public transient AsmManager asm; // which structure model is this node part of + private static final long serialVersionUID = 171673495267384449L; + public static boolean shortITDNames = true; + + private final static String UNDEFINED = "<undefined>"; + private final static int AccPublic = 0x0001; + private final static int AccPrivate = 0x0002; + private final static int AccProtected = 0x0004; + private final static int AccPrivileged = 0x0006; // XXX is this right? + private final static int AccStatic = 0x0008; + private final static int AccFinal = 0x0010; + private final static int AccSynchronized = 0x0020; + private final static int AccVolatile = 0x0040; + private final static int AccTransient = 0x0080; + private final static int AccNative = 0x0100; + // private final static int AccInterface = 0x0200; + private final static int AccAbstract = 0x0400; + // private final static int AccStrictfp = 0x0800; + + protected String name; + private Kind kind; + protected IProgramElement parent = null; + protected List<IProgramElement> children = Collections.emptyList(); + public Map<String, Object> kvpairs = Collections.emptyMap(); + protected ISourceLocation sourceLocation = null; + public int modifiers; + private String handle = null; + + public AsmManager getModel() { + return asm; + } + + /** Used during deserialization */ + public ProgramElement() { + } + + /** Use to create program element nodes that do not correspond to source locations */ + public ProgramElement(AsmManager asm, String name, Kind kind, List<IProgramElement> children) { + this.asm = asm; + if (asm == null && !name.equals("<build to view structure>")) { + throw new RuntimeException(); + } + this.name = name; + this.kind = kind; + if (children != null) { + setChildren(children); + } + } + + public ProgramElement(AsmManager asm, String name, IProgramElement.Kind kind, ISourceLocation sourceLocation, int modifiers, + String comment, List<IProgramElement> children) { + this(asm, name, kind, children); + this.sourceLocation = sourceLocation; + setFormalComment(comment); + // if (comment!=null && comment.length()>0) formalComment = comment; + this.modifiers = modifiers; + } + + public int getRawModifiers() { + return this.modifiers; + } + + public List<IProgramElement.Modifiers> getModifiers() { + return genModifiers(this.modifiers); + } + + public Accessibility getAccessibility() { + return genAccessibility(this.modifiers); + } + + public void setDeclaringType(String t) { + if (t != null && t.length() > 0) { + fixMap(); + kvpairs.put("declaringType", t); + } + } + + public String getDeclaringType() { + String dt = (String) kvpairs.get("declaringType"); + if (dt == null) { + return ""; // assumption that not having one means "" is at HtmlDecorator line 111 + } + return dt; + } + + public String getPackageName() { + if (kind == Kind.PACKAGE) { + return getName(); + } + if (getParent() == null) { + return ""; + } + return getParent().getPackageName(); + } + + public Kind getKind() { + return kind; + } + + public boolean isCode() { + return kind.equals(Kind.CODE); + } + + public ISourceLocation getSourceLocation() { + return sourceLocation; + } + + // not really sure why we have this setter ... how can we be in the situation where we didn't + // know the location when we built the node but we learned it later on? + public void setSourceLocation(ISourceLocation sourceLocation) { + // this.sourceLocation = sourceLocation; + } + + public IMessage getMessage() { + return (IMessage) kvpairs.get("message"); + // return message; + } + + public void setMessage(IMessage message) { + fixMap(); + kvpairs.put("message", message); + // this.message = message; + } + + public IProgramElement getParent() { + return parent; + } + + public void setParent(IProgramElement parent) { + this.parent = parent; + } + + public boolean isMemberKind() { + return kind.isMember(); + } + + public void setRunnable(boolean value) { + fixMap(); + if (value) { + kvpairs.put("isRunnable", "true"); + } else { + kvpairs.remove("isRunnable"); + // this.runnable = value; + } + } + + public boolean isRunnable() { + return kvpairs.get("isRunnable") != null; + // return runnable; + } + + public boolean isImplementor() { + return kvpairs.get("isImplementor") != null; + // return implementor; + } + + public void setImplementor(boolean value) { + fixMap(); + if (value) { + kvpairs.put("isImplementor", "true"); + } else { + kvpairs.remove("isImplementor"); + // this.implementor = value; + } + } + + public boolean isOverrider() { + return kvpairs.get("isOverrider") != null; + // return overrider; + } + + public void setOverrider(boolean value) { + fixMap(); + if (value) { + kvpairs.put("isOverrider", "true"); + } else { + kvpairs.remove("isOverrider"); + // this.overrider = value; + } + } + + public String getFormalComment() { + return (String) kvpairs.get("formalComment"); + // return formalComment; + } + + public String toString() { + return toLabelString(); + } + + private static List<IProgramElement.Modifiers> genModifiers(int modifiers) { + List<IProgramElement.Modifiers> modifiersList = new ArrayList<IProgramElement.Modifiers>(); + if ((modifiers & AccStatic) != 0) { + modifiersList.add(IProgramElement.Modifiers.STATIC); + } + if ((modifiers & AccFinal) != 0) { + modifiersList.add(IProgramElement.Modifiers.FINAL); + } + if ((modifiers & AccSynchronized) != 0) { + modifiersList.add(IProgramElement.Modifiers.SYNCHRONIZED); + } + if ((modifiers & AccVolatile) != 0) { + modifiersList.add(IProgramElement.Modifiers.VOLATILE); + } + if ((modifiers & AccTransient) != 0) { + modifiersList.add(IProgramElement.Modifiers.TRANSIENT); + } + if ((modifiers & AccNative) != 0) { + modifiersList.add(IProgramElement.Modifiers.NATIVE); + } + if ((modifiers & AccAbstract) != 0) { + modifiersList.add(IProgramElement.Modifiers.ABSTRACT); + } + return modifiersList; + } + + public static IProgramElement.Accessibility genAccessibility(int modifiers) { + if ((modifiers & AccPublic) != 0) { + return IProgramElement.Accessibility.PUBLIC; + } + if ((modifiers & AccPrivate) != 0) { + return IProgramElement.Accessibility.PRIVATE; + } + if ((modifiers & AccProtected) != 0) { + return IProgramElement.Accessibility.PROTECTED; + } + if ((modifiers & AccPrivileged) != 0) { + return IProgramElement.Accessibility.PRIVILEGED; + } else { + return IProgramElement.Accessibility.PACKAGE; + } + } + + public String getBytecodeName() { + String s = (String) kvpairs.get("bytecodeName"); + if (s == null) { + return UNDEFINED; + } + return s; + } + + public void setBytecodeName(String s) { + fixMap(); + kvpairs.put("bytecodeName", s); + } + + public void setBytecodeSignature(String s) { + fixMap(); + // Different kinds of format here. The one worth compressing starts with a '(': + // (La/b/c/D;Le/f/g/G;)Ljava/lang/String; + // maybe want to avoid generics initially. + // boolean worthCompressing = s.charAt(0) == '(' && s.indexOf('<') == -1 && s.indexOf('P') == -1; // starts parentheses and + // no + // // generics + // if (worthCompressing) { + // kvpairs.put("bytecodeSignatureCompressed", asm.compress(s)); + // } else { + kvpairs.put("bytecodeSignature", s); + // } + } + + public String getBytecodeSignature() { + String s = (String) kvpairs.get("bytecodeSignature"); + // if (s == null) { + // List compressed = (List) kvpairs.get("bytecodeSignatureCompressed"); + // if (compressed != null) { + // return asm.decompress(compressed, '/'); + // } + // } + // if (s==null) return UNDEFINED; + return s; + } + + public String getSourceSignature() { + return (String) kvpairs.get("sourceSignature"); + } + + public void setSourceSignature(String string) { + fixMap(); + // System.err.println(name+" SourceSig=>"+string); + kvpairs.put("sourceSignature", string); + // sourceSignature = string; + } + + public void setKind(Kind kind) { + this.kind = kind; + } + + public void setCorrespondingType(String s) { + fixMap(); + kvpairs.put("returnType", s); + // this.returnType = s; + } + + public void setParentTypes(List<String> ps) { + fixMap(); + kvpairs.put("parentTypes", ps); + } + + @SuppressWarnings("unchecked") + public List<String> getParentTypes() { + return (List<String>) (kvpairs == null ? null : kvpairs.get("parentTypes")); + } + + /** + * {@inheritDoc} + */ + public void setAnnotationType(String fullyQualifiedAnnotationType) { + fixMap(); + kvpairs.put("annotationType", fullyQualifiedAnnotationType); + } + + public void setAnnotationRemover(boolean isRemover) { + fixMap(); + kvpairs.put("annotationRemover", isRemover); + } + + public String getAnnotationType() { + if (isAnnotationRemover()) { + return null; + } + return (String) (kvpairs == null ? null : kvpairs.get("annotationType")); + } + + public boolean isAnnotationRemover() { + if (kvpairs == null) { + return false; + } + Boolean b = (Boolean) kvpairs.get("annotationRemover"); + if (b == null) { + return false; + } + return b.booleanValue(); + } + + public String[] getRemovedAnnotationTypes() { + if (!isAnnotationRemover()) { + return null; + } + String annotype = (String) (kvpairs == null ? null : kvpairs.get("annotationType")); + if (annotype == null) { + return null; + } else { + return new String[] { annotype }; + } + } + + public String getCorrespondingType() { + return getCorrespondingType(false); + } + + public String getCorrespondingTypeSignature() { + String typename = (String) kvpairs.get("returnType"); + if (typename == null) { + return null; + } + return nameToSignature(typename); + } + + public static String nameToSignature(String name) { + int len = name.length(); + if (len < 8) { + if (name.equals("byte")) { + return "B"; + } + if (name.equals("char")) { + return "C"; + } + if (name.equals("double")) { + return "D"; + } + if (name.equals("float")) { + return "F"; + } + if (name.equals("int")) { + return "I"; + } + if (name.equals("long")) { + return "J"; + } + if (name.equals("short")) { + return "S"; + } + if (name.equals("boolean")) { + return "Z"; + } + if (name.equals("void")) { + return "V"; + } + if (name.equals("?")) { + return name; + } + } + if (name.endsWith("[]")) { + return "[" + nameToSignature(name.substring(0, name.length() - 2)); + } + if (len != 0) { + // check if someone is calling us with something that is a signature already + assert name.charAt(0) != '['; + + if (name.indexOf("<") == -1) { + // not parameterized + return new StringBuilder("L").append(name.replace('.', '/')).append(';').toString(); + } else { + StringBuffer nameBuff = new StringBuffer(); + int nestLevel = 0; + nameBuff.append("L"); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + switch (c) { + case '.': + nameBuff.append('/'); + break; + case '<': + nameBuff.append("<"); + nestLevel++; + StringBuffer innerBuff = new StringBuffer(); + while (nestLevel > 0) { + c = name.charAt(++i); + if (c == '<') { + nestLevel++; + } + if (c == '>') { + nestLevel--; + } + if (c == ',' && nestLevel == 1) { + nameBuff.append(nameToSignature(innerBuff.toString())); + innerBuff = new StringBuffer(); + } else { + if (nestLevel > 0) { + innerBuff.append(c); + } + } + } + nameBuff.append(nameToSignature(innerBuff.toString())); + nameBuff.append('>'); + break; + case '>': + throw new IllegalStateException("Should by matched by <"); + case ',': + throw new IllegalStateException("Should only happen inside <...>"); + default: + nameBuff.append(c); + } + } + nameBuff.append(";"); + return nameBuff.toString(); + } + } else { + throw new IllegalArgumentException("Bad type name: " + name); + } + } + + public String getCorrespondingType(boolean getFullyQualifiedType) { + String returnType = (String) kvpairs.get("returnType"); + if (returnType == null) { + returnType = ""; + } + if (getFullyQualifiedType) { + return returnType; + } + return trim(returnType); + } + + /** + * Trim down fully qualified types to their short form (e.g. a.b.c.D<e.f.G> becomes D<G>) + */ + public static String trim(String fqname) { + int i = fqname.indexOf("<"); + if (i == -1) { + int lastdot = fqname.lastIndexOf('.'); + if (lastdot == -1) { + return fqname; + } else { + return fqname.substring(lastdot + 1); + } + } + char[] charArray = fqname.toCharArray(); + StringBuilder candidate = new StringBuilder(charArray.length); + StringBuilder complete = new StringBuilder(charArray.length); + for (char c : charArray) { + switch (c) { + case '.': + candidate.setLength(0); + break; + case '<': + case ',': + case '>': + complete.append(candidate).append(c); + candidate.setLength(0); + break; + default: + candidate.append(c); + } + } + complete.append(candidate); + return complete.toString(); + } + + public String getName() { + return name; + } + + public List<IProgramElement> getChildren() { + return children; + } + + public void setChildren(List<IProgramElement> children) { + this.children = children; + if (children == null) { + return; + } + for (Iterator<IProgramElement> it = children.iterator(); it.hasNext();) { + (it.next()).setParent(this); + } + } + + public void addChild(IProgramElement child) { + if (children == null || children == Collections.EMPTY_LIST) { + children = new ArrayList<IProgramElement>(); + } + children.add(child); + child.setParent(this); + } + + public void addChild(int position, IProgramElement child) { + if (children == null || children == Collections.EMPTY_LIST) { + children = new ArrayList<IProgramElement>(); + } + children.add(position, child); + child.setParent(this); + } + + public boolean removeChild(IProgramElement child) { + child.setParent(null); + return children.remove(child); + } + + public void setName(String string) { + name = string; + } + + public IProgramElement walk(HierarchyWalker walker) { + if (children != null) { + for (IProgramElement child : children) { + walker.process(child); + } + } + return this; + } + + public String toLongString() { + final StringBuffer buffer = new StringBuffer(); + HierarchyWalker walker = new HierarchyWalker() { + private int depth = 0; + + public void preProcess(IProgramElement node) { + for (int i = 0; i < depth; i++) { + buffer.append(' '); + } + buffer.append(node.toString()); + buffer.append('\n'); + depth += 2; + } + + public void postProcess(IProgramElement node) { + depth -= 2; + } + }; + walker.process(this); + return buffer.toString(); + } + + public void setModifiers(int i) { + this.modifiers = i; + } + + /** + * Convenience mechanism for setting new modifiers which do not require knowledge of the private internal representation + * + * @param newModifier + */ + public void addModifiers(IProgramElement.Modifiers newModifier) { + modifiers |= newModifier.getBit(); + } + + public String toSignatureString() { + return toSignatureString(true); + } + + public String toSignatureString(boolean getFullyQualifiedArgTypes) { + StringBuffer sb = new StringBuffer(); + sb.append(name); + + List<char[]> ptypes = getParameterTypes(); + if (ptypes != null && (!ptypes.isEmpty() || this.kind.equals(IProgramElement.Kind.METHOD)) + || this.kind.equals(IProgramElement.Kind.CONSTRUCTOR) || this.kind.equals(IProgramElement.Kind.ADVICE) + || this.kind.equals(IProgramElement.Kind.POINTCUT) || this.kind.equals(IProgramElement.Kind.INTER_TYPE_METHOD) + || this.kind.equals(IProgramElement.Kind.INTER_TYPE_CONSTRUCTOR)) { + sb.append('('); + for (Iterator<char[]> it = ptypes.iterator(); it.hasNext();) { + char[] arg = it.next(); + if (getFullyQualifiedArgTypes) { + sb.append(arg); + } else { + int index = CharOperation.lastIndexOf('.', arg); + if (index != -1) { + sb.append(CharOperation.subarray(arg, index + 1, arg.length)); + } else { + sb.append(arg); + } + } + if (it.hasNext()) { + sb.append(","); + } + } + sb.append(')'); + } + + return sb.toString(); + } + + /** + * TODO: move the "parent != null"==>injar heuristic to more explicit + */ + public String toLinkLabelString() { + return toLinkLabelString(true); + } + + public String toLinkLabelString(boolean getFullyQualifiedArgTypes) { + String label; + if (kind == Kind.CODE || kind == Kind.INITIALIZER) { + label = parent.getParent().getName() + ": "; + } else if (kind.isInterTypeMember()) { + if (shortITDNames) { + // if (name.indexOf('.')!=-1) return toLabelString().substring(name.indexOf('.')+1); + label = ""; + } else { + int dotIndex = name.indexOf('.'); + if (dotIndex != -1) { + return parent.getName() + ": " + toLabelString().substring(dotIndex + 1); + } else { + label = parent.getName() + '.'; + } + } + } else if (kind == Kind.CLASS || kind == Kind.ASPECT || kind == Kind.INTERFACE) { + label = ""; + } else if (kind.equals(Kind.DECLARE_PARENTS)) { + label = ""; + } else { + if (parent != null) { + label = parent.getName() + '.'; + } else { + label = "injar aspect: "; + } + } + label += toLabelString(getFullyQualifiedArgTypes); + return label; + } + + public String toLabelString() { + return toLabelString(true); + } + + public String toLabelString(boolean getFullyQualifiedArgTypes) { + String label = toSignatureString(getFullyQualifiedArgTypes); + String details = getDetails(); + if (details != null) { + label += ": " + details; + } + return label; + } + + public String getHandleIdentifier() { + return getHandleIdentifier(true); + } + + public String getHandleIdentifier(boolean create) { + String h = handle; + if (null == handle && create) { + if (asm == null && name.equals("<build to view structure>")) { + h = "<build to view structure>"; + } else { + try { + h = asm.getHandleProvider().createHandleIdentifier(this); + } catch (ArrayIndexOutOfBoundsException aioobe) { + throw new RuntimeException("AIOOBE whilst building handle for " + this, aioobe); + } + } + } + setHandleIdentifier(h); + return h; + } + + public void setHandleIdentifier(String handle) { + this.handle = handle; + } + + @SuppressWarnings("unchecked") + public List<String> getParameterNames() { + List<String> parameterNames = (List<String>) kvpairs.get("parameterNames"); + return parameterNames; + } + + public void setParameterNames(List<String> list) { + if (list == null || list.size() == 0) { + return; + } + fixMap(); + kvpairs.put("parameterNames", list); + // parameterNames = list; + } + + public List<char[]> getParameterTypes() { + List<char[]> l = getParameterSignatures(); + if (l == null || l.isEmpty()) { + return Collections.emptyList(); + } + List<char[]> params = new ArrayList<char[]>(); + for (Iterator<char[]> iter = l.iterator(); iter.hasNext();) { + char[] param = iter.next(); + params.add(NameConvertor.convertFromSignature(param)); + } + return params; + } + + @SuppressWarnings("unchecked") + public List<char[]> getParameterSignatures() { + List<char[]> parameters = (List<char[]>) kvpairs.get("parameterSigs"); + return parameters; + } + + @SuppressWarnings("unchecked") + public List<String> getParameterSignaturesSourceRefs() { + List<String> parameters = (List<String>) kvpairs.get("parameterSigsSourceRefs"); + return parameters; + } + + /** + * Set the parameter signatures for this method/constructor. The bit flags tell us if any were not singletypereferences in the + * the source. A singletypereference would be 'String' - whilst a qualifiedtypereference would be 'java.lang.String' - this has + * an effect on the handles. + */ + public void setParameterSignatures(List<char[]> list, List<String> sourceRefs) { + fixMap(); + if (list == null || list.size() == 0) { + kvpairs.put("parameterSigs", Collections.EMPTY_LIST); + } else { + kvpairs.put("parameterSigs", list); + } + if (sourceRefs != null && sourceRefs.size() != 0) { + kvpairs.put("parameterSigsSourceRefs", sourceRefs); + } + } + + public String getDetails() { + String details = (String) kvpairs.get("details"); + return details; + } + + public void setDetails(String string) { + fixMap(); + kvpairs.put("details", string); + } + + public void setFormalComment(String txt) { + if (txt != null && txt.length() > 0) { + fixMap(); + kvpairs.put("formalComment", txt); + } + } + + private void fixMap() { + if (kvpairs == Collections.EMPTY_MAP) { + kvpairs = new HashMap<String, Object>(); + } + } + + public void setExtraInfo(ExtraInformation info) { + fixMap(); + kvpairs.put("ExtraInformation", info); + } + + public ExtraInformation getExtraInfo() { + return (ExtraInformation) kvpairs.get("ExtraInformation"); + } + + public boolean isAnnotationStyleDeclaration() { + return kvpairs.get("annotationStyleDeclaration") != null; + } + + public void setAnnotationStyleDeclaration(boolean b) { + if (b) { + fixMap(); + kvpairs.put("annotationStyleDeclaration", "true"); + } + } + + @SuppressWarnings("unchecked") + public Map<String, List<String>> getDeclareParentsMap() { + Map<String, List<String>> s = (Map<String, List<String>>) kvpairs.get("declareparentsmap"); + return s; + } + + public void setDeclareParentsMap(Map<String, List<String>> newmap) { + fixMap(); + kvpairs.put("declareparentsmap", newmap); + } + + public void addFullyQualifiedName(String fqname) { + fixMap(); + kvpairs.put("itdfqname", fqname); + } + + public String getFullyQualifiedName() { + return (String) kvpairs.get("itdfqname"); + } +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/Relationship.java b/asm/src/main/java/org/aspectj/asm/internal/Relationship.java new file mode 100644 index 000000000..8bd4a7e66 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/Relationship.java @@ -0,0 +1,86 @@ +/* ******************************************************************* + * Copyright (c) 2003,2010 Contributors + * 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: + * Mik Kersten initial implementation + * Andy Clement Extensions for better IDE representation + * ******************************************************************/ +package org.aspectj.asm.internal; + +import java.util.List; + +import org.aspectj.asm.IRelationship; + +/** + * @author Mik Kersten + * @author Andy Clement + */ +public class Relationship implements IRelationship { + + private static final long serialVersionUID = 3855166397957609120L; + + private String name; + private Kind kind; + private boolean isAffects; + private String sourceHandle; + private List<String> targets; + private boolean hasRuntimeTest; + + public Relationship(String name, Kind kind, String sourceHandle, List<String> targets, boolean runtimeTest) { + this.name = name; + this.isAffects = name.equals("advises") || name.equals("declares on") || name.equals("softens") + || name.equals("matched by") || name.equals("declared on") || name.equals("annotates"); + this.kind = kind; + this.sourceHandle = sourceHandle; + this.targets = targets; + this.hasRuntimeTest = runtimeTest; + } + + public String getName() { + return name; + } + + public Kind getKind() { + return kind; + } + + public String toString() { + return name; + } + + public String getSourceHandle() { + return sourceHandle; + } + + // TODO should be a Set and not a list + public List<String> getTargets() { + return targets; + } + + public void addTarget(String handle) { + if (targets.contains(handle)) { + return; + } + targets.add(handle); + } + + public boolean hasRuntimeTest() { + return hasRuntimeTest; + } + + /** + * Return the direction of the relationship. It might be affects or affected-by. The direction enables the incremental model + * repair code to do the right thing. + * + * @return true if is an affects relationship: advises/declareson/softens/matchedby/declaredon/annotates + */ + public boolean isAffects() { + return isAffects; + } + +} diff --git a/asm/src/main/java/org/aspectj/asm/internal/RelationshipMap.java b/asm/src/main/java/org/aspectj/asm/internal/RelationshipMap.java new file mode 100644 index 000000000..1fea7bb85 --- /dev/null +++ b/asm/src/main/java/org/aspectj/asm/internal/RelationshipMap.java @@ -0,0 +1,150 @@ +/* ******************************************************************* + * Copyright (c) 2003,2010 Contributors + * 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: + * Mik Kersten initial implementation + * Andy Clement + * ******************************************************************/ + +package org.aspectj.asm.internal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.aspectj.asm.IProgramElement; +import org.aspectj.asm.IRelationship; +import org.aspectj.asm.IRelationship.Kind; +import org.aspectj.asm.IRelationshipMap; + +/** + * @author Mik Kersten + * @author Andy Clement + */ +public class RelationshipMap extends HashMap<String, List<IRelationship>> implements IRelationshipMap { + + private static final long serialVersionUID = 496638323566589643L; + + public RelationshipMap() { + } + + public List<IRelationship> get(String handle) { + List<IRelationship> relationships = super.get(handle); + if (relationships == null) { + return null; + } else { + return relationships; + } + } + + public List<IRelationship> get(IProgramElement source) { + return get(source.getHandleIdentifier()); + } + + public IRelationship get(String source, IRelationship.Kind kind, String relationshipName, boolean runtimeTest, + boolean createIfMissing) { + List<IRelationship> relationships = get(source); + if (relationships == null) { + if (!createIfMissing) { + return null; + } + relationships = new ArrayList<IRelationship>(); + IRelationship rel = new Relationship(relationshipName, kind, source, new ArrayList<String>(), runtimeTest); + relationships.add(rel); + + super.put(source, relationships); + return rel; + } else { + for (Iterator<IRelationship> it = relationships.iterator(); it.hasNext();) { + IRelationship curr = it.next(); + if (curr.getKind() == kind && curr.getName().equals(relationshipName) && curr.hasRuntimeTest() == runtimeTest) { + return curr; + } + } + if (createIfMissing) { + // At this point we did find some relationships for 'source' but not one that looks like what we are + // after (either the kind or the name or the dynamictests setting don't match) + IRelationship rel = new Relationship(relationshipName, kind, source, new ArrayList<String>(), runtimeTest); + relationships.add(rel); + return rel; + } + } + return null; + } + + public IRelationship get(IProgramElement source, IRelationship.Kind kind, String relationshipName, boolean runtimeTest, + boolean createIfMissing) { + return get(source.getHandleIdentifier(), kind, relationshipName, runtimeTest, createIfMissing); + } + + public IRelationship get(IProgramElement source, Kind kind, String relationshipName) { + return get(source, kind, relationshipName, false, true); + } + + public boolean remove(String source, IRelationship relationship) { + List<IRelationship> list = super.get(source); + if (list != null) { + return list.remove(relationship); + // boolean matched = false; + // for (Iterator it = list.iterator(); it.hasNext(); ) { + // IRelationship curr = (IRelationship)it.next(); + // if (curr.getName().equals(relationship.getName())) { + // curr.getTargets().addAll(relationship.getTargets()); + // matched = true; + // } + // } + // if (!matched) list.remove(relationship); + } + return false; + } + + public void removeAll(String source) { + super.remove(source); + } + + public void put(String source, IRelationship relationship) { + List<IRelationship> existingRelationships = super.get(source); + if (existingRelationships == null) { + // new entry + existingRelationships = new ArrayList<IRelationship>(); + existingRelationships.add(relationship); + super.put(source, existingRelationships); + } else { + boolean matched = false; + for (IRelationship existingRelationship : existingRelationships) { + if (existingRelationship.getName().equals(relationship.getName()) + && existingRelationship.getKind() == relationship.getKind()) { + existingRelationship.getTargets().addAll(relationship.getTargets()); + matched = true; + } + } + if (matched) { + // bug? + System.err.println("matched = true"); + } + if (matched) { + existingRelationships.add(relationship); // Is this a bug, will it give us double entries? + } + } + } + + public void put(IProgramElement source, IRelationship relationship) { + put(source.getHandleIdentifier(), relationship); + } + + public void clear() { + super.clear(); + } + + public Set<String> getEntries() { + return keySet(); + } + +} |