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