package net.sourceforge.c4jplugin.internal.ui.contracthierarchy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Vector; import net.sourceforge.c4jplugin.internal.core.ContractReferenceModel; import net.sourceforge.c4jplugin.internal.util.ContractReferenceUtil; import net.sourceforge.c4jplugin.internal.util.ExceptionHandler; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.ElementChangedEvent; import org.eclipse.jdt.core.IElementChangedListener; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.ITypeHierarchyChangedListener; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; public class ContractHierarchy implements IContractHierarchy, IElementChangedListener, ITypeHierarchyChangedListener { public static boolean DEBUG = false; protected ITypeHierarchy typeHierarchy; protected Map<IType, Vector<IType>> contractToSupercontracts; protected Map<IType, Vector<IType>> contractToSubcontracts; protected Map<IType, Integer> typeFlags; protected Vector<IType> rootContracts; protected static final IType[] NO_TYPE = new IType[0]; /* * Whether this hierarchy needs refresh */ public boolean needsRefresh = true; protected boolean computeSubcontracts = true; protected IType inputContract = null; /** * The progress monitor to report work completed too. */ protected IProgressMonitor progressMonitor = null; /** * Change listeners - null if no one is listening. */ protected ArrayList<IContractHierarchyChangedListener> changeListeners = null; public ContractHierarchy(IType type, IProgressMonitor pm) throws JavaModelException { this(type, pm, true); } public ContractHierarchy(IType type, IProgressMonitor pm, boolean computeSubcontracts) throws JavaModelException { IResource res = ContractReferenceModel.getDirectContract(type.getUnderlyingResource()); if (res != null) { this.inputContract = ContractReferenceUtil.getType(JavaCore.create(res)); } this.computeSubcontracts = computeSubcontracts; try { IProgressMonitor subpm1 = null; if (pm != null) { pm.beginTask("Creating Contract Hierarchy", 300); subpm1 = new SubProgressMonitor(pm, 200); } if (computeSubcontracts) typeHierarchy = type.newTypeHierarchy(subpm1); else typeHierarchy = type.newSupertypeHierarchy(subpm1); if (DEBUG) { System.out.print("TYPE HIERARCHY FOR " + type.getElementName() + " contains: "); for (IType t : typeHierarchy.getAllTypes()) { System.out.print(t.getElementName() + " "); } System.out.println(""); for (IType t : typeHierarchy.getAllTypes()) { System.out.print("SUPER TYPES FOR " + t.getElementName() + " are: "); for (IType s : typeHierarchy.getAllSupertypes(t)) { System.out.print(s.getElementName() + " "); } System.out.println(""); System.out.print("SUB TYPES FOR " + t.getElementName() + " are: "); for (IType s : typeHierarchy.getAllSubtypes(t)) { System.out.print(s.getElementName() + " "); } System.out.println(""); } } IProgressMonitor subpm2 = null; if (pm != null) subpm2 = new SubProgressMonitor(pm, 100); refresh(subpm2); } finally { if (pm != null) pm.done(); } typeHierarchy.addTypeHierarchyChangedListener(this); } /** * Initializes this hierarchy's internal tables with the given size. */ protected void initialize(int size) { if (size < 10) { size = 10; } int smallSize = (size / 2); this.rootContracts = new Vector<IType>(); this.contractToSubcontracts = new HashMap<IType, Vector<IType>>(smallSize); this.contractToSupercontracts = new HashMap<IType, Vector<IType>>(smallSize); this.typeFlags = new HashMap<IType, Integer>(smallSize); //this.projectRegion = new Region(); //this.packageRegion = new Region(); //this.files = new HashMap(5); } public synchronized void addContractHierarchyChangedListener( IContractHierarchyChangedListener listener) { ArrayList listeners = this.changeListeners; if (listeners == null) { this.changeListeners = listeners = new ArrayList<ITypeHierarchyChangedListener>(); } // register with JavaCore to get Java element delta on first listener added if (listeners.size() == 0) { JavaCore.addElementChangedListener(this); } // add listener only if it is not already present if (listeners.indexOf(listener) == -1) { listeners.add(listener); } } /** * Notifies listeners that this hierarchy has changed and needs * refreshing. Note that listeners can be removed as we iterate * through the list. */ protected void fireChange() { ArrayList<IContractHierarchyChangedListener> listeners = this.changeListeners; if (listeners == null) { return; } if (DEBUG) { System.out.println("FIRING hierarchy change ["+Thread.currentThread()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ if (this.inputContract != null) { System.out.println(" for hierarchy focused on " + this.inputContract.toString()); //$NON-NLS-1$ } } // clone so that a listener cannot have a side-effect on this list when being notified listeners = (ArrayList<IContractHierarchyChangedListener>)listeners.clone(); for (int i= 0; i < listeners.size(); i++) { final IContractHierarchyChangedListener listener= listeners.get(i); SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { ExceptionHandler.log(exception, "Exception occurred in listener of Type hierarchy change notification"); //$NON-NLS-1$ } public void run() throws Exception { listener.contractHierarchyChanged(ContractHierarchy.this); } }); } } protected void cacheFlags(IType type, int flags) { this.typeFlags.put(type, new Integer(flags)); } /** * Adds the given subcontract to the contract. */ protected void addSubcontract(IType type, IType subtype) { Vector<IType> subtypes = this.contractToSubcontracts.get(type); if (subtypes == null) { subtypes = new Vector<IType>(); this.contractToSubcontracts.put(type, subtypes); } if (!subtypes.contains(subtype)) { subtypes.add(subtype); } } public boolean contains(IType type) { // root classes if (this.rootContracts.contains(type)) return true; // classes Vector<IType> supers = this.contractToSupercontracts.get(type); if (supers != null) { return true; } return false; } public boolean exists() { if (!this.needsRefresh) return true; return typeHierarchy.exists(); } public IType[] getAllContracts() { Vector<IType> classes = (Vector<IType>)this.rootContracts.clone(); for (IType root : this.rootContracts) { addAllCheckingDuplicates(classes, Arrays.asList(getAllSubcontracts(root))); } return classes.toArray(new IType[classes.size()]); } public IType[] getAllSubcontracts(IType type) { return getAllSubcontractsForContract(type); } /** * @see #getAllSubtypes(IType) */ private IType[] getAllSubcontractsForContract(IType type) { ArrayList<IType> subTypes = new ArrayList<IType>(); getAllSubcontractsForContract0(type, subTypes); IType[] subClasses = new IType[subTypes.size()]; subTypes.toArray(subClasses); return subClasses; } /** */ private void getAllSubcontractsForContract0(IType type, ArrayList<IType> subs) { IType[] subTypes = getSubcontractsForContract(type); if (subTypes.length != 0) { for (IType subType : subTypes) { subs.add(subType); getAllSubcontractsForContract0(subType, subs); } } } public IType[] getAllSupercontracts(IType type) { ArrayList<IType> supers = new ArrayList<IType>(); if (this.contractToSupercontracts.get(type) == null) { return NO_TYPE; } getAllSupercontracts0(type, supers); IType[] superinterfaces = new IType[supers.size()]; supers.toArray(superinterfaces); return superinterfaces; } private void getAllSupercontracts0(IType type, ArrayList<IType> supers) { Vector<IType> superinterfaces = this.contractToSupercontracts.get(type); if (superinterfaces != null && superinterfaces.size() != 0) { addAllCheckingDuplicates(supers, superinterfaces); for (IType superinterface : superinterfaces) { getAllSupercontracts0(superinterface, supers); } } } /** * Adds all of the elements in the collection to the list if the * element is not already in the list. */ private void addAllCheckingDuplicates(Collection<IType> dest, Collection<IType> src) { if (dest == null || src == null) return; for (IType element : src) { if (!dest.contains(element)) { dest.add(element); } } } /* private void getAllSupertypes0(IType type, ArrayList<IType> supers) { Vector<IType> superinterfaces = this.typeToSuperInterfaces.get(type); if (superinterfaces != null && superinterfaces.size() != 0) { addAllCheckingDuplicates(supers, superinterfaces); for (IType superinterface : superinterfaces) { getAllSuperInterfaces0(superinterface, supers); } } IType superclass = this.classToSuperclass.get(type); if (superclass != null) { supers.add(superclass); getAllSupertypes0(superclass, supers); } } */ /* public IType[] getAllTypes() { IType[] classes = getAllClasses(); int classesLength = classes.length; IType[] allInterfaces = getAllInterfaces(); int interfacesLength = allInterfaces.length; IType[] all = new IType[classesLength + interfacesLength]; System.arraycopy(classes, 0, all, 0, classesLength); System.arraycopy(allInterfaces, 0, all, classesLength, interfacesLength); return all; } */ public int getCachedFlags(IType type) { Integer flagObject = this.typeFlags.get(type); if (flagObject != null){ return flagObject.intValue(); } return -1; } public IType[] getRootContracts() { return this.rootContracts.toArray(new IType[this.rootContracts.size()]); } public IType[] getSubcontracts(IType type) { return getSubcontractsForContract(type); } /** * Returns an array of subtypes for the given type - will never return null. */ private IType[] getSubcontractsForContract(IType type) { Vector<IType> vector = this.contractToSubcontracts.get(type); if (vector == null) return NO_TYPE; else return vector.toArray(new IType[vector.size()]); } public IType[] getSupercontracts(IType type) { Vector<IType> types = this.contractToSupercontracts.get(type); if (types == null) { return NO_TYPE; } return types.toArray(new IType[types.size()]); } public IType getType() { return this.inputContract; } public synchronized void refresh(IProgressMonitor monitor) throws JavaModelException { try { this.progressMonitor = monitor; if (monitor != null) { int num = typeHierarchy.getAllTypes().length; // 5 points for checking super and sub contracts of each type // 1 point for checking root types // 1 point for initialization int amount = num*5 + num + 1; if (this.inputContract != null) { monitor.beginTask(ContractHierarchyMessages.bind(ContractHierarchyMessages.hierarchy_creatingOnType, this.inputContract.getFullyQualifiedName()), amount); } else { monitor.beginTask(ContractHierarchyMessages.hierarchy_creating, amount); } } long start = -1; if (DEBUG) { start = System.currentTimeMillis(); if (this.computeSubcontracts) { System.out.println("CREATING CONTRACT HIERARCHY [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ } else { System.out.println("CREATING SUPER CONTRACT HIERARCHY [" + Thread.currentThread() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ } if (this.inputContract != null) { System.out.println(" on type " + this.inputContract.toString()); //$NON-NLS-1$ } } compute(); //initializeRegions(); this.needsRefresh = false; //this.changeCollector = null; if (DEBUG) { if (this.computeSubcontracts) { System.out.println("CREATED TYPE HIERARCHY in " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ } else { System.out.println("CREATED SUPER TYPE HIERARCHY in " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ } System.out.println(this.toString()); } } catch (JavaModelException e) { throw e; /*} catch (CoreException e) { throw new JavaModelException(e);*/ } finally { if (monitor != null) { monitor.done(); } this.progressMonitor = null; } } protected void compute() throws JavaModelException { initialize(1); if (this.progressMonitor != null) this.progressMonitor.worked(1); if (DEBUG) System.out.println("BEGIN COMPUTING CONTRACT HIERARCHY"); // root contracts computeRootContracts(this.typeHierarchy, this.rootContracts); if (DEBUG) { System.out.print("ROOT CONTRACTS ARE : "); for (IType type : getRootContracts()) { System.out.print(type.getElementName() + " "); } System.out.println(""); } IType[] classes = typeHierarchy.getAllTypes(); for (IType clazz : classes) { IResource contract = ContractReferenceModel.getDirectContract(clazz.getUnderlyingResource()); if (contract == null) continue; IType contractType = ContractReferenceUtil.getType(JavaCore.create(contract)); if (contractType == null) continue; //cache flags cacheFlags(contractType, contractType.getFlags()); // contract to supercontracts if (!this.rootContracts.contains(contractType)) { Vector<IType> supercontracts = new Vector<IType>(); computeSupercontracts(clazz, supercontracts); if (supercontracts.size() > 0) this.contractToSupercontracts.put(contractType, supercontracts); } if (DEBUG) { System.out.print("SUPER CONTRACTS FOR " + contractType.getElementName() + " ARE : "); for (IType type : getSupercontracts(contractType)) { System.out.print(type.getElementName() + " "); } System.out.println(""); } // contract to subcontracts Vector<IType> subcontracts = new Vector<IType>(); computeSubcontracts(clazz, subcontracts); if (subcontracts.size() > 0) this.contractToSubcontracts.put(contractType, subcontracts); if (this.progressMonitor != null) this.progressMonitor.worked(5); if (DEBUG) { System.out.print("SUB CONTRACTS FOR " + contractType.getElementName() + " ARE : "); for (IType type : getSubcontracts(contractType)) { System.out.print(type.getElementName() + " "); } System.out.println(""); } } if (DEBUG) { System.out.print("Contract hierarchy of " + (getType() == null ? "null" : getType().getElementName()) + " contains: "); for (IType type : getAllContracts()) { System.out.print(type.getElementName() + " "); } System.out.println(""); } } private void computeSubcontracts(IType type, Vector<IType> res) throws JavaModelException { Vector<IType> subTypes = new Vector<IType>(); Collections.addAll(subTypes, this.typeHierarchy.getAllSubtypes(type)); for (IType subType : subTypes) { IResource subContract = ContractReferenceModel.getDirectContract(subType.getUnderlyingResource()); if (subContract == null) continue; boolean bIsSub = true; for (IType superType : this.typeHierarchy.getAllSupertypes(subType)) { if (!subTypes.contains(superType)) continue; if (ContractReferenceModel.getDirectContract(superType.getUnderlyingResource()) != null) { bIsSub = false; break; } } if (bIsSub) { res.add(ContractReferenceUtil.getType(JavaCore.create(subContract))); } } } private void computeSupercontracts(IType type, Vector<IType> res) throws JavaModelException { Vector<IType> superTypes = new Vector<IType>(); Collections.addAll(superTypes, this.typeHierarchy.getAllSupertypes(type)); for (IType superType : superTypes) { IResource superContract = ContractReferenceModel.getDirectContract(superType.getUnderlyingResource()); if (superContract == null) continue; boolean bIsSuper = true; for (IType subType : this.typeHierarchy.getAllSubtypes(superType)) { if (!superTypes.contains(subType)) continue; if (ContractReferenceModel.getDirectContract(subType.getUnderlyingResource()) != null) { bIsSuper = false; break; } } if (bIsSuper) { res.add(ContractReferenceUtil.getType(JavaCore.create(superContract))); } } } private void computeRootContracts(ITypeHierarchy hierarchy, Vector<IType> res) throws JavaModelException { IType[] allTypes = hierarchy.getAllTypes(); for (IType type : allTypes) { IResource contract = ContractReferenceModel.getDirectContract(type.getUnderlyingResource()); if (contract == null) continue; boolean isRoot = true; IType[] supers = hierarchy.getAllSupertypes(type); for (IType superType : supers) { Boolean isContracted = ContractReferenceModel.isContracted(superType.getUnderlyingResource()); if (isContracted != null && isContracted == true) { isRoot = false; break; } } if (isRoot) { res.add(ContractReferenceUtil.getType(JavaCore.create(contract))); } if (this.progressMonitor != null) this.progressMonitor.worked(1); } } public void removeContractHierarchyChangedListener( IContractHierarchyChangedListener listener) { // TODO Auto-generated method stub } public void elementChanged(ElementChangedEvent event) { // type hierarchy change has already been fired if (this.needsRefresh) return; /* if (isAffected(event.getDelta())) { this.needsRefresh = true; fireChange(); } */ } /** * Returns true if the given delta could change this contract hierarchy */ /* protected synchronized boolean isAffected(IJavaElementDelta delta) { IJavaElement element= delta.getElement(); switch (element.getElementType()) { case IJavaElement.JAVA_MODEL: return isAffectedByJavaModel(delta, element); case IJavaElement.JAVA_PROJECT: return isAffectedByJavaProject(delta, element); case IJavaElement.PACKAGE_FRAGMENT_ROOT: return isAffectedByPackageFragmentRoot(delta, element); case IJavaElement.PACKAGE_FRAGMENT: return isAffectedByPackageFragment(delta, (PackageFragment) element); case IJavaElement.CLASS_FILE: case IJavaElement.COMPILATION_UNIT: return isAffectedByOpenable(delta, element); } return false; }*/ public void typeHierarchyChanged(ITypeHierarchy typeHierarchy) { fireChange(); } public ITypeHierarchy getTypeHierarchy() { return typeHierarchy; } public boolean isSupercontract(IType possibleSupercontract, IType type) { IType[] superContracts = getSupercontracts(type); for (IType superContract : superContracts) { if (possibleSupercontract.equals(superContract) || isSupercontract(possibleSupercontract, superContract)) { return true; } } return false; } public boolean isSubcontract(IType contract) { if (this.inputContract != null) { IType[] subs = getAllSubcontracts(this.inputContract); for (IType sub : subs) { if (contract.equals(sub)) return true; } return false; } else { try { IResource res = ContractReferenceModel.getTarget(contract.getUnderlyingResource()); if (res != null) { IType target = ContractReferenceUtil.getType(JavaCore.create(res)); IType[] subs = typeHierarchy.getAllSubtypes(typeHierarchy.getType()); for (IType sub : subs) { if (target.equals(sub)) return true; } return false; } } catch (JavaModelException e) {} return false; } } public boolean isSupercontract(IType contract) { if (this.inputContract != null) { IType[] supers = getAllSupercontracts(this.inputContract); for (IType s : supers) { if (contract.equals(s)) return true; } return false; } else { try { IResource res = ContractReferenceModel.getTarget(contract.getUnderlyingResource()); if (res != null) { IType target = ContractReferenceUtil.getType(JavaCore.create(res)); IType[] supers = typeHierarchy.getAllSupertypes(typeHierarchy.getType()); for (IType s : supers) { if (target.equals(s)) return true; } return false; } } catch (JavaModelException e) {} return false; } } }