org.aspectj.asm.AsmManager.java Source code

Java tutorial

Introduction

Here is the source code for org.aspectj.asm.AsmManager.java

Source

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