/* ******************************************************************* * Copyright (c) 2003 Contributors. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Common Public License v1.0 * which accompanies this distribution and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * Mik Kersten initial implementation * Andy Clement incremental support and switch on/off state * ******************************************************************/ package org.aspectj.asm; import java.io.*; import java.util.*; import org.aspectj.asm.internal.*; import org.aspectj.bridge.ISourceLocation; /** * The Abstract Structure Model (ASM) represents the containment hierarchy and crossccutting * 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 */ public class AsmManager { /** * @deprecated use getDefault() method instead */ private static AsmManager INSTANCE = new AsmManager(); private IElementHandleProvider handleProvider; private List structureListeners = new ArrayList(); private boolean shouldSaveModel = true; public void setRelationshipMap(IRelationshipMap irm) { mapper = irm;} public void setHierarchy(IHierarchy ih) { hierarchy=ih;} // 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; private IRelationshipMap mapper; private static boolean creatingModel = false; public static boolean dumpModelPostBuild = false; // Dumping the model is expensive // SECRETAPI asc pull the secret options together into a system API you lazy fool public static boolean attemptIncrementalModelRepairs = 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 String dumpFilename = ""; private static boolean reporting = false; // static { // setReporting("c:/model.nfo",true,true,true,true); // } protected AsmManager() { hierarchy = new AspectJElementHierarchy(); // List relationships = new ArrayList(); mapper = new RelationshipMap(hierarchy); handleProvider = new FullPathHandleProvider(); } public void createNewASM() { hierarchy = new AspectJElementHierarchy(); mapper = new RelationshipMap(hierarchy); } public IHierarchy getHierarchy() { return hierarchy; } public static AsmManager getDefault() { return INSTANCE; } 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 getInlineAnnotations( String sourceFile, boolean showSubMember, boolean showMemberAndType) { if (!hierarchy.isValid()) return null; HashMap annotations = new HashMap(); IProgramElement node = hierarchy.findElementForSourceFile(sourceFile); if (node == IHierarchy.NO_STRUCTURE) { return null; } else { IProgramElement fileNode = (IProgramElement)node; ArrayList peNodes = new ArrayList(); getAllStructureChildren(fileNode, peNodes, showSubMember, showMemberAndType); for (Iterator it = peNodes.iterator(); it.hasNext(); ) { IProgramElement peNode = (IProgramElement)it.next(); List entries = new ArrayList(); entries.add(peNode); ISourceLocation sourceLoc = peNode.getSourceLocation(); if (null != sourceLoc) { Integer hash = new Integer(sourceLoc.getLine()); List existingEntry = (List)annotations.get(hash); if (existingEntry != null) { entries.addAll(existingEntry); } annotations.put(hash, entries); } } return annotations; } } private void getAllStructureChildren(IProgramElement node, List result, boolean showSubMember, boolean showMemberAndType) { List children = node.getChildren(); if (node.getChildren() == null) return; for (Iterator it = children.iterator(); it.hasNext(); ) { IProgramElement next = (IProgramElement)it.next(); List rels = AsmManager.getDefault().getRelationshipMap().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((IProgramElement)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 (Iterator it = structureListeners.iterator(); it.hasNext(); ) { ((IHierarchyListener)it.next()).elementsUpdated(hierarchy); } } public IElementHandleProvider getHandleProvider() { return handleProvider; } public void setHandleProvider(IElementHandleProvider handleProvider) { this.handleProvider = handleProvider; } /** * Fails silently. */ 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 (Exception e) { // System.err.println("AsmManager: Unable to write structure model: "+configFilePath+" because of:"); // e.printStackTrace(); } } /** * @todo add proper handling of bad paths/suffixes/etc * @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(); hierarchyReadOK = true; mapper = (RelationshipMap)s.readObject(); ((RelationshipMap)mapper).setHierarchy(hierarchy); } } 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) { return configFilePath.substring(0, configFilePath.lastIndexOf(".lst")) + ".ajsym"; } public void setShouldSaveModel(boolean shouldSaveModel) { this.shouldSaveModel = shouldSaveModel; } // ==== implementation of canonical file path map and accessors ============== // a more sophisticated optimisation is left here commented out as the // performance gains don't justify the disturbance this close to a release... // can't call prepareForWeave until preparedForCompilation has completed... // public synchronized void prepareForCompilation(List files) { // canonicalFilePathMap.prepopulate(files); // } // // public synchronized void prepareForWeave() { // canonicalFilePathMap.handover(); // } public String getCanonicalFilePath(File f) { return canonicalFilePathMap.get(f); } private CanonicalFilePathMap canonicalFilePathMap = new CanonicalFilePathMap(); private static class CanonicalFilePathMap { private static final int MAX_SIZE = 4000; private Map pathMap = new HashMap(MAX_SIZE); // // 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 = (String) 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 boolean isReporting() { return reporting; } 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,AsmManager.getDefault().getHierarchy().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 = ModelInfo.summarizeModel().getProperties(); Enumeration 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 ;i0) { // 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 { for (int i = 0 ;iFirst: 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. *

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"); verifyAssumption(progElem!=null); boolean deleteOK = false; IProgramElement parent = progElem.getParent(); // flightrecorder.append("Parent of it is "+parent+"\n"); List kids = parent.getChildren(); // flightrecorder.append("Which has "+kids.size()+" kids\n"); for (int i =0 ;i