| /* ******************************************************************* |
| * 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)); |
| } |
| } |