package com.redhat.ceylon.eclipse.code.outline; import static com.redhat.ceylon.eclipse.code.outline.HierarchyMode.HIERARCHY; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.ENABLE_HIERARCHY_FILTERS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.HIERARCHY_FILTERS; import static com.redhat.ceylon.eclipse.util.ModelProxy.getDeclarationInUnit; import static com.redhat.ceylon.model.cmr.JDKUtils.isJDKModule; import static com.redhat.ceylon.model.cmr.JDKUtils.isOracleJDKModule; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.getInterveningRefinements; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.getSignature; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isAbstraction; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isVariadic; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPartSite; import com.redhat.ceylon.compiler.typechecker.tree.Tree.CompilationUnit; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.util.Filters; import com.redhat.ceylon.eclipse.util.ModelProxy; import com.redhat.ceylon.ide.common.model.BaseIdeModule; import com.redhat.ceylon.model.typechecker.model.ClassOrInterface; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.TypedDeclaration; import com.redhat.ceylon.model.typechecker.model.Unit; public final class CeylonHierarchyContentProvider implements ITreeContentProvider { // implements ILazyTreeContentProvider { private final IWorkbenchPartSite site; private HierarchyMode mode = HIERARCHY; private CeylonHierarchyNode hierarchyRoot; private CeylonHierarchyNode supertypesRoot; private CeylonHierarchyNode subtypesRoot; private boolean showingRefinements; private boolean empty; private boolean excludeJDK; private boolean excludeOracleJDK = true; private int depthInHierarchy; private boolean veryAbstractType; private String description; private String label; CeylonHierarchyContentProvider(IWorkbenchPartSite site, String label, boolean excludeJDK, boolean excludeOracleJDK) { this.site = site; this.label = label; this.excludeJDK = excludeJDK; this.excludeOracleJDK = excludeOracleJDK; } Declaration getDeclaration() { return subtypesRoot==null ? null : subtypesRoot.getDeclaration(); } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { if (newInput!=null && newInput!=oldInput) { filters.initFilters(); ModelProxy proxy = (ModelProxy) newInput; Declaration declaration = proxy.get(); if (declaration instanceof TypedDeclaration) { TypedDeclaration td = (TypedDeclaration) declaration; if (td.getTypeDeclaration().isAnonymous()) { TypedDeclaration atd = (TypedDeclaration) declaration; declaration = atd.getTypeDeclaration(); } } showingRefinements = !(declaration instanceof TypeDeclaration); empty = declaration==null; if (!empty) { String name = declaration.getQualifiedNameString(); veryAbstractType = name.equals("ceylon.language::Object") || name.equals("ceylon.language::Anything") || name.equals("ceylon.language::Basic") || name.equals("ceylon.language::Identifiable"); description = declaration.getName();//getDescriptionFor(declaration); if (//isShowingRefinements() && declaration.isClassOrInterfaceMember()) { ClassOrInterface classOrInterface = (ClassOrInterface) declaration.getContainer(); description = classOrInterface.getName() + '.' + description; } rebuild(declaration); } } } private void rebuild(Declaration declaration) { if (declaration!=null) { try { site.getWorkbenchWindow() .run(true, true, new Runnable(declaration)); } catch (Exception e) { e.printStackTrace(); } } } boolean isVeryAbstractType() { return veryAbstractType; } boolean isShowingRefinements() { return showingRefinements; } public boolean isEmpty() { return empty; } @Override public void dispose() {} @Override public Object getParent(Object element) { if (element instanceof CeylonHierarchyNode) { CeylonHierarchyNode chn = (CeylonHierarchyNode) element; return chn.getParent(); } return null; } @Override public boolean hasChildren(Object element) { return getChildren(element).length>0; } @Override public Object[] getElements(Object inputElement) { return getChildren(inputElement); } @Override public CeylonHierarchyNode[] getChildren(Object parent) { if (parent instanceof ModelProxy) { switch (mode) { case HIERARCHY: return new CeylonHierarchyNode[] { hierarchyRoot }; case SUPERTYPES: return new CeylonHierarchyNode[] { supertypesRoot }; case SUBTYPES: return new CeylonHierarchyNode[] { subtypesRoot }; default: throw new RuntimeException(); } } else if (parent instanceof CeylonHierarchyNode) { CeylonHierarchyNode chn = (CeylonHierarchyNode) parent; return chn.getChildren(); } else { return null; } } HierarchyMode getMode() { return mode; } void setMode(HierarchyMode mode) { this.mode = mode; } boolean isExcludeJDK() { return excludeJDK; } void setExcludeJDK(boolean excludeJDK) { this.excludeJDK = excludeJDK; rebuild(getDeclaration()); } boolean isExcludeOracleJDK() { return excludeOracleJDK; } void setExcludeOracleJDK(boolean excludeOracleJDK) { this.excludeOracleJDK = excludeOracleJDK; rebuild(getDeclaration()); } int getDepthInHierarchy() { return depthInHierarchy; } private class Runnable implements IRunnableWithProgress { private final Declaration declaration; private final Map<Declaration, CeylonHierarchyNode> subtypesOfSupertypes = new HashMap<Declaration,CeylonHierarchyNode>(); private final Map<Declaration,CeylonHierarchyNode> subtypesOfAllTypes = new HashMap<Declaration,CeylonHierarchyNode>(); private final Map<Declaration,CeylonHierarchyNode> supertypesOfAllTypes = new HashMap<Declaration,CeylonHierarchyNode>(); private void add(Declaration td, Declaration etd) { if (inheritedBy(td)) { getSubtypeHierarchyNode(etd) .addChild(getSubtypeHierarchyNode(td)); } if (inherits(td)) { getSupertypeHierarchyNode(td) .addChild(getSupertypeHierarchyNode(etd)); } } private boolean inherits(Declaration td) { if (declaration instanceof TypeDeclaration && td instanceof TypeDeclaration) { return ((TypeDeclaration) declaration) .inherits((TypeDeclaration) td); } else if (declaration instanceof TypedDeclaration && td instanceof TypedDeclaration) { return declaration.getRefinedDeclaration() .equals(td.getRefinedDeclaration()); } else { return false; } } private boolean inheritedBy(Declaration td) { if (declaration instanceof TypeDeclaration && td instanceof TypeDeclaration) { return ((TypeDeclaration) td) .inherits((TypeDeclaration) declaration); } else if (declaration instanceof TypedDeclaration && td instanceof TypedDeclaration) { return declaration.getRefinedDeclaration() .equals(td.getRefinedDeclaration()); } else { return false; } } private CeylonHierarchyNode getSubtypePathNode( Declaration declaration) { CeylonHierarchyNode n = subtypesOfSupertypes.get(declaration); if (n==null) { n = new CeylonHierarchyNode(declaration); subtypesOfSupertypes.put(declaration, n); } return n; } private CeylonHierarchyNode getSubtypeHierarchyNode( Declaration declaration) { CeylonHierarchyNode n = subtypesOfAllTypes.get(declaration); if (n==null) { n = new CeylonHierarchyNode(declaration); subtypesOfAllTypes.put(declaration, n); } return n; } private CeylonHierarchyNode getSupertypeHierarchyNode( Declaration declaration) { CeylonHierarchyNode n = supertypesOfAllTypes.get(declaration); if (n==null) { n = new CeylonHierarchyNode(declaration); supertypesOfAllTypes.put(declaration, n); } return n; } public Runnable(Declaration declaration) { this.declaration = declaration; } @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask("Building hierarchy", 100000); Set<Module> allModules = collectModules(); monitor.worked(10000); Set<Package> packages = new HashSet<Package>(); int ams = allModules.size(); for (Module module: allModules) { collectPackages(packages, module); monitor.worked(10000/ams); if (monitor.isCanceled()) return; } CeylonHierarchyNode node = getSubtypePathNode(declaration); node.setFocus(true); subtypesOfAllTypes.put(declaration, node); Declaration root = declaration; depthInHierarchy = 0; if (declaration instanceof TypeDeclaration) { root = collectSuperclasses(root); } else if (declaration instanceof TypedDeclaration) { root = collectOverriddenMembers(root); } hierarchyRoot = getSubtypePathNode(root); subtypesRoot = getSubtypeHierarchyNode(declaration); subtypesRoot.setFocus(true); supertypesRoot = getSupertypeHierarchyNode(declaration); supertypesRoot.setFocus(true); if (monitor.isCanceled()) return; IEditorPart part = site.getPage() .getActiveEditor(); List<Type> signature = getSignature(declaration); boolean isVariadic = isVariadic(declaration); int ps = packages.size(); for (Package pack: packages) { //workaround CME List<Declaration> members = //IMPORTANT: call getMembers() to //force lazy loading of all Units! pack.getMembers(); int ms = members.size(); monitor.subTask("scanning " + pack.getNameAsString()); //iterate over units and then declarations //in order to pick up nested declarations, //unlike in OpenDeclarationDialog where we //iterate over Package.getMembers() because //we only want toplevels for (Unit unit: pack.getUnits()) { try { //TODO: unshared inner types get // missed for binary modules for (Declaration d: unit.getDeclarations()) { if (!isFiltered(d)) { d = replaceWithCurrentEditorDeclaration( part, pack, d); //TODO: not enough to catch *new* subtypes in the dirty editor if (d instanceof ClassOrInterface || d instanceof TypeParameter) { try { if (declaration instanceof TypeDeclaration) { addTypeToHierarchy(d); } else if (declaration instanceof TypedDeclaration) { addMemberToHierarchy(signature, isVariadic, d); } } catch (Exception e) { String qn = d.getQualifiedNameString(); System.err.println(qn); throw e; } } } monitor.worked(15000/ps/ms); if (monitor.isCanceled()) return; } } catch (Exception e) { e.printStackTrace(); } } if (monitor.isCanceled()) return; } monitor.done(); } private Declaration collectOverriddenMembers(Declaration root) { Declaration memberDec = declaration; Declaration refinedDeclaration = declaration.getRefinedDeclaration(); Scope container = declaration.getContainer(); if (container instanceof TypeDeclaration) { TypeDeclaration dec = (TypeDeclaration) container; List<Type> signature = getSignature(declaration); boolean isVariadic = isVariadic(declaration); depthInHierarchy++; root = memberDec; //first walk up the superclass hierarchy String decname = declaration.getName(); while (dec!=null) { TypeDeclaration mc = (TypeDeclaration) memberDec.getContainer(); TypeDeclaration rc = (TypeDeclaration) refinedDeclaration.getContainer(); Type extended = dec.getExtendedType(); if (extended==null) { dec = null; } else { TypeDeclaration superDec = extended.getDeclaration(); Declaration superMemberDec = superDec.getDirectMember( decname, signature, false); if (superMemberDec!=null && !isAbstraction(superMemberDec) && superMemberDec.getRefinedDeclaration() .equals(refinedDeclaration)) { List<Declaration> directlyInheritedMembers = getInterveningRefinements( decname, signature, isVariadic, refinedDeclaration, mc, superDec, true); List<Declaration> all = getInterveningRefinements( decname, signature, isVariadic, refinedDeclaration, mc, rc, true); for (Declaration d: all) { TypeDeclaration dtd = (TypeDeclaration) d.getContainer(); if (!superDec.inherits(dtd)) { getSubtypePathNode(superMemberDec) .setNonUnique(true); } } directlyInheritedMembers.remove(superMemberDec); if (directlyInheritedMembers.size()>0) { getSubtypePathNode(superMemberDec) .setNonUnique(true); } getSubtypePathNode(superMemberDec) .addChild(getSubtypePathNode(memberDec)); depthInHierarchy++; root = superMemberDec; memberDec = superMemberDec; } //TODO else add an "empty" node to the hierarchy like in JDT dec = superDec; } } //now look at the very top of the hierarchy, even if it is an interface if (!memberDec.equals(refinedDeclaration)) { TypeDeclaration mc = (TypeDeclaration) memberDec.getContainer(); TypeDeclaration rc = (TypeDeclaration) refinedDeclaration.getContainer(); List<Declaration> directlyInheritedMembers = getInterveningRefinements( decname, signature, isVariadic, declaration.getRefinedDeclaration(), mc, rc, true); directlyInheritedMembers.remove(refinedDeclaration); if (directlyInheritedMembers.size()>1) { //multiple intervening interfaces CeylonHierarchyNode n = new CeylonHierarchyNode(memberDec); n.setMultiple(true); n.addChild(getSubtypePathNode(memberDec)); getSubtypePathNode(refinedDeclaration) .addChild(n); } else if (directlyInheritedMembers.size()==1) { //exactly one intervening interface Declaration idec = directlyInheritedMembers.get(0); getSubtypePathNode(idec) .addChild(getSubtypePathNode(memberDec)); getSubtypePathNode(refinedDeclaration) .addChild(getSubtypePathNode(idec)); } else { //no intervening interfaces getSubtypePathNode(refinedDeclaration) .addChild(getSubtypePathNode(memberDec)); } root = refinedDeclaration; } } return root; } private Declaration collectSuperclasses(Declaration root) { TypeDeclaration dec = (TypeDeclaration) declaration; while (dec!=null) { depthInHierarchy++; root = dec; Type extended = dec.getExtendedType(); if (extended!=null) { TypeDeclaration superDec = extended.getDeclaration(); if (!dec.getSatisfiedTypes() .isEmpty()) { getSubtypePathNode(superDec) .setNonUnique(true); } getSubtypePathNode(superDec) .addChild(getSubtypePathNode(dec)); dec = superDec; } else { dec = null; } } return root; } private void collectPackages(Set<Package> packages, Module module) { if (includeModule(module)) { //TODO: I guess we don't need to clone the // package list before iterating, like // what we do in OpenDeclarationDialog, // because all we're doing is adding // the packages to a Set for (Package pack: module.getPackages()) { if (!filters.isFiltered(pack)) { packages.add(pack); } } } } private boolean includeModule(Module module) { return !excluded(module) && !filters.isFiltered(module) && module.isAvailable(); } private boolean excluded(Module module) { String moduleName = module.getNameAsString(); return excludeJDK && isJDKModule(moduleName) || excludeOracleJDK && isOracleJDKModule(moduleName); } private Set<Module> collectModules() { Unit unit = declaration.getUnit(); Module currentModule = unit.getPackage() .getModule(); Set<Module> allModules = new HashSet<Module>(); if (currentModule instanceof BaseIdeModule) { BaseIdeModule jdtCurrentModule = (BaseIdeModule) currentModule; List<BaseIdeModule> moduleInAllProjects = new ArrayList<BaseIdeModule>(); moduleInAllProjects.add(jdtCurrentModule); moduleInAllProjects.addAll(jdtCurrentModule.getModuleInReferencingProjectsAsJavaList()); for (BaseIdeModule ideModule: moduleInAllProjects) { allModules.add(ideModule); allModules.addAll(ideModule.getReferencingModulesAsJavaList()); allModules.addAll(ideModule.getTransitiveDependenciesAsJavaList()); } } return allModules; } private void addMemberToHierarchy( List<Type> signature, boolean isVariadic, Declaration dec) { Declaration refinedDeclaration = declaration.getRefinedDeclaration(); TypeDeclaration td = (TypeDeclaration) dec; String name = declaration.getName(); Declaration member = td.getDirectMember(name, signature, false); if (member!=null) { Declaration rd = member.getRefinedDeclaration(); if (rd!=null && rd.equals(refinedDeclaration)) { TypeDeclaration rdc = (TypeDeclaration) refinedDeclaration.getContainer(); List<Declaration> refinements = getInterveningRefinements( name, signature, isVariadic, refinedDeclaration, td, rdc, true); //TODO: keep the directly refined declarations in the model // (get the typechecker to set this up) for (Declaration candidate: refinements) { TypeDeclaration cc = (TypeDeclaration) candidate.getContainer(); List<Declaration> refs = getInterveningRefinements( name, signature, isVariadic, refinedDeclaration, td, cc, true); if (refs.size()==1) { add(member, candidate); } } } } } private void addTypeToHierarchy(Declaration dec) { TypeDeclaration td = (TypeDeclaration) dec; if (!(td instanceof TypeParameter)) { Type et = td.getExtendedType(); if (et!=null) { add(td, et.getDeclaration()); } for (Type st: td.getSatisfiedTypes()) { if (st!=null) { add(td, st.getDeclaration()); } } } } private Declaration replaceWithCurrentEditorDeclaration( IEditorPart part, Package p, Declaration d) { if (part instanceof CeylonEditor && part.isDirty()) { CeylonEditor editor = (CeylonEditor) part; CompilationUnit rootNode = editor.getParseController() .getLastCompilationUnit(); if (rootNode!=null) { Unit unit = rootNode.getUnit(); if (unit!=null && unit.getPackage().equals(p)) { String name = d.getQualifiedNameString(); Declaration result = getDeclarationInUnit(name, unit); if (result!=null) { return result; } } } } return d; } } String getDescription() { if (isShowingRefinements()) { switch (getMode()) { case HIERARCHY: return label + " \u2014 refinement hierarchy of " + description; case SUPERTYPES: return label + " \u2014 generalizations of " + description; case SUBTYPES: return label + " \u2014 refinements of " + description; default: throw new RuntimeException(); } } else { switch (getMode()) { case HIERARCHY: return label + " \u2014 type hierarchy of " + description; case SUPERTYPES: return label + " \u2014 supertypes of " + description; case SUBTYPES: return label + " \u2014 subtypes of " + description; default: throw new RuntimeException(); } } } private Filters filters = new Filters() { @Override protected String enableExtraFiltersPref() { return ENABLE_HIERARCHY_FILTERS; } @Override protected String extraFiltersPref() { return HIERARCHY_FILTERS; } }; private boolean isFiltered(Declaration declaration) { // if (excludeDeprecated && // declaration.isDeprecated()) { // return true; // } if (declaration.isAnnotation() && declaration.getName().contains("__")) { //actually what we should really do is filter //out all constructors for Java annotations return true; } return filters.isFiltered(declaration); } }