/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.netbeans.modules.java.navigation.hierarchy; import org.netbeans.modules.java.navigation.actions.NameActions; import java.awt.Image; import java.awt.Toolkit; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.swing.AbstractAction; import javax.swing.Action; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.StaticResource; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.queries.SourceForBinaryQuery; import org.netbeans.api.java.source.ClassIndex; import org.netbeans.api.java.source.ClassIndex.SearchKind; import org.netbeans.api.java.source.ClassIndex.SearchScope; import org.netbeans.api.java.source.ClasspathInfo; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.SourceUtils; import org.netbeans.api.java.source.TreePathHandle; import org.netbeans.api.java.source.ui.ElementIcons; import org.netbeans.api.java.source.ui.ElementOpen; import org.netbeans.modules.java.navigation.actions.SortActions; import org.netbeans.modules.java.navigation.base.Utils; import org.netbeans.modules.refactoring.api.ui.RefactoringActionsFactory; import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.openide.awt.StatusDisplayer; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectNotFoundException; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.TopologicalSortException; import org.openide.util.Utilities; import org.openide.util.datatransfer.PasteType; import org.openide.util.lookup.AbstractLookup; import org.openide.util.lookup.InstanceContent; import org.openide.util.lookup.InstanceContent.Convertor; /** * * @author Tomas Zezula */ class Nodes { private static final String INSPECT_HIERARCHY_ACTION = "Actions/Edit/org-netbeans-modules-java-navigation-actions-ShowHierarchyAction.instance"; //NOI18N @StaticResource private static final String ICON = "org/netbeans/modules/java/navigation/resources/wait.gif"; //NOI18N private static final String ACTION_FOLDER = "Navigator/Actions/Hierarchy/text/x-java"; //NOI18N private static final WaitNode WAIT_NODE = new WaitNode(); private Nodes() { throw new IllegalStateException(); } static Node rootNode( @NonNull final Children cld, @NonNull final HierarchyFilters filters) { assert filters != null; return new RootNode(cld, globalActions(filters)); } static Node waitNode() { return WAIT_NODE; } static Node superTypeHierarchy( @NonNull final DeclaredType type, @NonNull final ClasspathInfo cpInfo, @NonNull final HierarchyFilters filters) { assert type != null; assert cpInfo != null; assert filters != null; return superTypeHierarchy(type, cpInfo, filters, 0); } private static Node superTypeHierarchy( @NonNull final DeclaredType type, @NonNull final ClasspathInfo cpInfo, @NonNull final HierarchyFilters filters, final int order) { final TypeElement element = (TypeElement)type.asElement(); final TypeMirror superClass = element.getSuperclass(); final List<? extends TypeMirror> interfaces = element.getInterfaces(); final List<Node> childNodes = new ArrayList<Node>(interfaces.size()+1); int childOrder = 0; if (superClass.getKind() != TypeKind.NONE) { childNodes.add(superTypeHierarchy((DeclaredType)superClass, cpInfo, filters, childOrder)); } for (TypeMirror superInterface : interfaces) { childOrder++; childNodes.add(superTypeHierarchy((DeclaredType)superInterface, cpInfo, filters, childOrder)); } final Children cld; if (childNodes.isEmpty()) { cld = Children.LEAF; } else { cld = new SuperTypeChildren(filters); cld.add(childNodes.toArray(new Node[childNodes.size()])); } return new TypeNode( cld, new Description( cpInfo, ElementHandle.create(element), order), filters, globalActions(filters)); } private static Action[] globalActions(@NonNull final HierarchyFilters filters) { return new Action[] { NameActions.createFullyQualifiedNameAction(filters), SortActions.createSortByNameAction(filters), SortActions.createSortBySourceAction(filters) }; } private static final class Description { private final ClasspathInfo cpInfo; private final ElementHandle<TypeElement> handle; private final int order; Description( @NonNull final ClasspathInfo cpInfo, @NonNull final ElementHandle<TypeElement> handle, final int order) { assert cpInfo != null; assert handle != null; this.cpInfo = cpInfo; this.handle = handle; this.order = order; } ClasspathInfo getClasspathInfo() { return cpInfo; } ElementHandle<TypeElement> getHandle() { return handle; } int getSourceOrder() { return order; } } private static class RootNode extends AbstractNode { private Action[] globalActions; RootNode( @NonNull final Children cld, @NonNull final Action[] globalActions) { super(cld); assert globalActions != null; this.globalActions = globalActions; } @Override public Action[] getActions(boolean context) { return globalActions; } } private static class WaitNode extends AbstractNode { @NbBundle.Messages({ "LBL_PleaseWait=Please Wait..." }) WaitNode() { super(Children.LEAF); setIconBaseWithExtension(ICON); setDisplayName(Bundle.LBL_PleaseWait()); } } private static final class TypeNode extends AbstractNode implements PropertyChangeListener { private final Description description; private final HierarchyFilters filters; private final Action[] globalActions; //@GuardedBy("this") private Action openAction; TypeNode( @NonNull final Children cld, @NonNull final Description description, @NonNull final HierarchyFilters filters, @NonNull final Action[] globalActions) { super(cld, createLookup(description)); assert description != null; assert filters != null; assert globalActions != null; this.description = description; this.filters = filters; this.globalActions = globalActions; this.filters.addPropertyChangeListener(this); updateDisplayName(); } @Override public String getShortDescription() { if (filters.isFqn()) { return super.getShortDescription(); } else { return description.getHandle().getQualifiedName(); } } @Override public Image getIcon(int type) { return ImageUtilities.icon2Image( ElementIcons.getElementIcon( description.getHandle().getKind(), EnumSet.noneOf(Modifier.class))); } @Override public Image getOpenedIcon(int type) { return getIcon(type); } @Override public void propertyChange(PropertyChangeEvent evt) { if (HierarchyFilters.PROP_FQN.equals(evt.getPropertyName())) { updateDisplayName(); } } @Override public Action[] getActions(boolean context) { if (context) { return globalActions; } else { final List<? extends Action> additionalActions = Utilities.actionsForPath(ACTION_FOLDER); final int additionalActionSize = additionalActions.isEmpty() ? 0 : additionalActions.size() + 1; final List<Action> actions = new ArrayList<Action>(4 + globalActions.length + additionalActionSize); actions.add(getOpenAction()); actions.add(FileUtil.getConfigObject(INSPECT_HIERARCHY_ACTION, Action.class)); actions.add(RefactoringActionsFactory.whereUsedAction()); actions.add(null); if (additionalActionSize > 0) { actions.addAll(additionalActions); actions.add(null); } actions.addAll(Arrays.asList(globalActions)); return actions.toArray(new Action[actions.size()]); } } @Override public Action getPreferredAction() { return getOpenAction(); } @Override public boolean canCopy() { return false; } @Override public boolean canCut() { return false; } @Override public boolean canDestroy() { return false; } @Override public boolean canRename() { return false; } @Override public PasteType getDropType(Transferable t, int action, int index) { return null; } @Override public Transferable drag() throws IOException { return null; } @Override protected void createPasteTypes(Transferable t, List<PasteType> s) { // Do nothing } private synchronized Action getOpenAction() { if ( openAction == null) { openAction = new OpenAction(); } return openAction; } private void updateDisplayName() { String name = description.handle.getQualifiedName(); if (!filters.isFqn()) { name = getSimpleName(name); } setDisplayName(name); } @NonNull private static Lookup createLookup (@NonNull Description desc) { final InstanceContent ic = new InstanceContent(); ic.add(desc); ic.add(desc, ConvertDescription2TreePathHandle); ic.add(desc, ConvertDescription2FileObject); ic.add(desc, ConvertDescription2DataObject); return new AbstractLookup(ic); } private class OpenAction extends AbstractAction { @NbBundle.Messages({"LBL_GoTo=Go to Source"}) OpenAction() { putValue ( Action.NAME, Bundle.LBL_GoTo()); } @NbBundle.Messages({"MSG_NoSource=Source not available for {0}"}) @Override public void actionPerformed(ActionEvent e) { if (!ElementOpen.open( description.getClasspathInfo(), description.getHandle())) { Toolkit.getDefaultToolkit().beep(); StatusDisplayer.getDefault().setStatusText( Bundle.MSG_NoSource(description.getHandle().getQualifiedName())); } } } private static final Convertor<Description, TreePathHandle> ConvertDescription2TreePathHandle = new InstanceContent.Convertor<Description, TreePathHandle>() { @Override public TreePathHandle convert(Description desc) { return TreePathHandle.from(desc.getHandle(), desc.getClasspathInfo()); } @Override public Class<? extends TreePathHandle> type(Description desc) { return TreePathHandle.class; } @Override public String id(Description desc) { return "IL[" + desc.toString(); } @Override public String displayName(Description desc) { return id(desc); } }; private static final Convertor<Description, FileObject> ConvertDescription2FileObject = new InstanceContent.Convertor<Description, FileObject>() { @Override public FileObject convert(Description desc) { return Utils.getFile( desc.getHandle(), desc.getClasspathInfo()); } @Override public Class<? extends FileObject> type(Description desc) { return FileObject.class; } @Override public String id(Description desc) { return "IL[" + desc.toString(); } @Override public String displayName(Description desc) { return id(desc); } }; private static final Convertor<Description, DataObject> ConvertDescription2DataObject = new InstanceContent.Convertor<Description, DataObject>(){ @Override public DataObject convert(Description desc) { try { final FileObject file = Utils.getFile( desc.getHandle(), desc.getClasspathInfo()); return file == null ? null : DataObject.find(file); } catch (DataObjectNotFoundException ex) { return null; } } @Override public Class<? extends DataObject> type(Description desc) { return DataObject.class; } @Override public String id(Description desc) { return "IL[" + desc.toString(); } @Override public String displayName(Description desc) { return id(desc); } }; } private static class SuperTypeChildren extends Children.SortedArray implements PropertyChangeListener { private final HierarchyFilters hierarchy; SuperTypeChildren(@NonNull final HierarchyFilters filters) { assert filters != null; this.hierarchy = filters; this.hierarchy.addPropertyChangeListener(this); updateComparator(); } private void updateComparator() { if (hierarchy.isNaturalSort()) { setComparator(new OrderComparator()); } else { setComparator(new LexicographicComparator()); } } @Override public void propertyChange(PropertyChangeEvent evt) { if (HierarchyFilters.PROP_NATURAL_SORT.equals(evt.getPropertyName())) { updateComparator(); } else if (!hierarchy.isNaturalSort() && HierarchyFilters.PROP_FQN.equals(evt.getPropertyName())) { updateComparator(); } } } private static final class LexicographicComparator implements Comparator<Node> { @Override public int compare(Node n1, Node n2) { return n1.getDisplayName().compareTo(n2.getDisplayName()); } } private static final class OrderComparator implements Comparator<Node> { @Override public int compare(Node n1, Node n2) { final int o1 = n1.getLookup().lookup(Description.class).getSourceOrder(); final int o2 = n2.getLookup().lookup(Description.class).getSourceOrder(); return o1 < o2 ? -1 : o1 == o2 ? 0 : 1; } } static Node subTypeHierarchy( @NonNull final TypeElement element, @NonNull final CompilationInfo info, @NonNull final HierarchyFilters filters, @NonNull final AtomicBoolean cancel) { try { boolean isSourceRoot = true; FileObject thisRoot = findSourceRoot(SourceUtils.getFile(element, info.getClasspathInfo())); if (thisRoot == null) { thisRoot = findBinaryRoot(element, info); isSourceRoot = false; } if (thisRoot == null) { return null; } ElementHandle<TypeElement> elementHandle = ElementHandle.create(element); TypeDescription td = new TypeDescription(info.getClasspathInfo(), elementHandle); Map<TypeDescription, Set<TypeDescription>> subclassesJoined = new ComputeSubClasses(cancel).computeUsers(info, thisRoot, Collections.singleton(td), new long[1], false, isSourceRoot); if (subclassesJoined == null) return null; List<TypeDescription> inOrder = Utilities.topologicalSort(subclassesJoined.keySet(), subclassesJoined); Collections.reverse(inOrder); Map<TypeDescription, Node> type2Node = new HashMap<TypeDescription, Node>(); for (TypeDescription toProcess : inOrder) { Set<TypeDescription> subclasses = subclassesJoined.get(toProcess); List<Node> childNodes = new ArrayList<Node>(subclasses.size()); for (TypeDescription subclass : subclasses) { Node subNode = new FilterNode(type2Node.get(subclass)); assert subNode != null; childNodes.add(subNode); } final Children cld; if (childNodes.isEmpty()) { cld = Children.LEAF; } else { cld = new SuperTypeChildren(filters); cld.add(childNodes.toArray(new Node[childNodes.size()])); } type2Node.put(toProcess, new TypeNode( cld, new Description( toProcess.cpInfo, toProcess.element, /*XXX:*/0), filters, new Action[0])); } return type2Node.get(td); } catch (TopologicalSortException ex) { Exceptions.printStackTrace(ex); return null; } } private static FileObject findSourceRoot(FileObject file) { final ClassPath cp = file != null ? ClassPath.getClassPath(file, ClassPath.SOURCE) : null; //Null is a valid value for files which have no source path (default filesystem). return cp != null ? cp.findOwnerRoot(file) : null; } @CheckForNull private static FileObject findBinaryRoot( @NonNull final TypeElement element, @NonNull final CompilationInfo info) { final FileObject res = findBinaryInCp( info.getElements(), element, info.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.BOOT)); if (res != null) { return res; } return findBinaryInCp( info.getElements(), element, info.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.COMPILE)); } @CheckForNull private static FileObject findBinaryInCp( @NonNull final Elements elements, @NonNull final TypeElement element, @NonNull final ClassPath cp) { final FileObject file = cp.findResource(String.format( "%s.class", //NOI18N elements.getBinaryName(element).toString().replace('.', '/'))); //NOI18N return file == null ? null : cp.findOwnerRoot(file); } private static String getSimpleName(@NonNull final String fqn) { int sepIndex = fqn.lastIndexOf('$'); //NOI18N if (sepIndex == -1) { sepIndex = fqn.lastIndexOf('.'); //NOI18N } return sepIndex >= 0? fqn.substring(sepIndex+1): fqn; } static final class ComputeSubClasses { private final AtomicBoolean cancel; public ComputeSubClasses(AtomicBoolean cancel) { this.cancel = cancel; } Map<TypeDescription, Set<TypeDescription>> computeUsers(CompilationInfo info, FileObject thisRoot, Set<TypeDescription> baseHandles, long[] classIndexCumulative, boolean interactive, boolean isSourceRoot) { Map<URL, List<URL>> sourceDeps = getDependencies(false); Map<URL, List<URL>> binaryDeps = getDependencies(true); if (sourceDeps == null || binaryDeps == null) { // if (interactive) { // NotifyDescriptor nd = new NotifyDescriptor.Message(NbBundle.getMessage(GoToImplementation.class, "ERR_NoDependencies"), NotifyDescriptor.ERROR_MESSAGE); // // DialogDisplayer.getDefault().notifyLater(nd); // } else { LOG.log(Level.FINE, "No dependencies"); // } return null; } final URL thisRootURL = thisRoot.toURL(); Map<URL, List<URL>> rootPeers = getRootPeers(); List<URL> sourceRoots = reverseSourceRootsInOrder(info, thisRootURL, thisRoot, sourceDeps, binaryDeps, rootPeers, interactive, isSourceRoot); if (sourceRoots == null) { return null; } baseHandles = new HashSet<TypeDescription>(baseHandles); for (Iterator<TypeDescription> it = baseHandles.iterator(); it.hasNext(); ) { if (cancel.get()) return null; if (it.next().element.getBinaryName().contentEquals("java.lang.Object")) { it.remove(); break; } } Map<TypeDescription, Set<TypeDescription>> result = new HashMap<TypeDescription, Set<TypeDescription>>(); Map<TypeDescription, Set<TypeDescription>> auxHandles = new HashMap<TypeDescription, Set<TypeDescription>>(); if (!sourceDeps.containsKey(thisRootURL)) { Set<URL> binaryRoots = new HashSet<URL>(); for (URL sr : sourceRoots) { List<URL> deps = sourceDeps.get(sr); if (deps != null) { binaryRoots.addAll(deps); } } binaryRoots.retainAll(binaryDeps.keySet()); for (TypeDescription handle : baseHandles) { Set<TypeDescription> types = computeUsers(ClasspathInfo.create(ClassPath.EMPTY, ClassPathSupport.createClassPath(binaryRoots.toArray(new URL[0])), ClassPath.EMPTY), SearchScope.DEPENDENCIES, Collections.singleton(handle), classIndexCumulative, result); if (types == null/*canceled*/ || cancel.get()) { return null; } auxHandles.put(handle, types); } } Map<URL, Map<TypeDescription, Set<TypeDescription>>> root2SubClasses = new LinkedHashMap<URL, Map<TypeDescription, Set<TypeDescription>>>(); for (URL file : sourceRoots) { for (TypeDescription base : baseHandles) { if (cancel.get()) return null; Set<TypeDescription> baseTypes = new HashSet<TypeDescription>(); baseTypes.add(base); Set<TypeDescription> aux = auxHandles.get(base); if (aux != null) { baseTypes.addAll(aux); } for (URL dep : sourceDeps.get(file)) { Map<TypeDescription, Set<TypeDescription>> depTypesMulti = root2SubClasses.get(dep); Set<TypeDescription> depTypes = depTypesMulti != null ? depTypesMulti.get(base) : null; if (depTypes != null) { baseTypes.addAll(depTypes); } } Set<TypeDescription> types = computeUsers(file, baseTypes, classIndexCumulative, result); if (types == null/*canceled*/ || cancel.get()) { return null; } types.removeAll(baseTypes); Map<TypeDescription, Set<TypeDescription>> currentUsers = root2SubClasses.get(file); if (currentUsers == null) { root2SubClasses.put(file, currentUsers = new LinkedHashMap<TypeDescription, Set<TypeDescription>>()); } currentUsers.put(base, types); } } return result; } private static final Logger LOG = Logger.getLogger(Nodes.class.getName()); static Map<URL, List<URL>> dependenciesOverride; private static Map<URL, List<URL>> getDependencies(boolean binary) { if (dependenciesOverride != null) { return dependenciesOverride; } ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class); if (l == null) { return null; } Class<?> clazz = null; String method = null; try { clazz = l.loadClass("org.netbeans.modules.parsing.impl.indexing.friendapi.IndexingController"); method = binary ? "getBinaryRootDependencies" : "getRootDependencies"; } catch (ClassNotFoundException ex) { LOG.log(Level.FINE, null, ex); try { clazz = l.loadClass("org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater"); method = binary ? "getDependencies" : "doesnotexist"; } catch (ClassNotFoundException inner) { LOG.log(Level.FINE, null, inner); return null; } } try { Method getDefault = clazz.getDeclaredMethod("getDefault"); Object instance = getDefault.invoke(null); Method dependenciesMethod = clazz.getDeclaredMethod(method); return (Map<URL, List<URL>>) dependenciesMethod.invoke(instance); } catch (IllegalAccessException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (IllegalArgumentException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (InvocationTargetException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (NoSuchMethodException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (SecurityException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (ClassCastException ex) { LOG.log(Level.FINE, null, ex); return null; } } static Map<URL, List<URL>> rootPeers; private static Map<URL, List<URL>> getRootPeers() { if (rootPeers != null) { return rootPeers; } ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class); if (l == null) { return null; } Class<?> clazz = null; String method = null; try { clazz = l.loadClass("org.netbeans.modules.parsing.impl.indexing.friendapi.IndexingController"); method = "getRootPeers"; } catch (ClassNotFoundException ex) { LOG.log(Level.FINE, null, ex); return null; } try { Method getDefault = clazz.getDeclaredMethod("getDefault"); Object instance = getDefault.invoke(null); Method peersMethod = clazz.getDeclaredMethod(method); return (Map<URL, List<URL>>) peersMethod.invoke(instance); } catch (IllegalAccessException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (IllegalArgumentException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (InvocationTargetException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (NoSuchMethodException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (SecurityException ex) { LOG.log(Level.FINE, null, ex); return null; } catch (ClassCastException ex) { LOG.log(Level.FINE, null, ex); return null; } } static List<URL> reverseSourceRootsInOrderOverride; private List<URL> reverseSourceRootsInOrder(CompilationInfo info, URL thisRoot, FileObject thisRootFO, Map<URL, List<URL>> sourceDeps, Map<URL, List<URL>> binaryDeps, Map<URL, List<URL>> rootPeers, boolean interactive, boolean isSourceRoot) { if (reverseSourceRootsInOrderOverride != null) { return reverseSourceRootsInOrderOverride; } Set<URL> sourceRootsSet; if (sourceDeps.containsKey(thisRoot)) { sourceRootsSet = findReverseSourceRoots(thisRoot, sourceDeps, rootPeers, info.getFileObject()); } else { sourceRootsSet = new HashSet<URL>(); for (URL binary : findBinaryRootsForSourceRoot(thisRootFO, binaryDeps, isSourceRoot)) { List<URL> deps = binaryDeps.get(binary); if (deps != null) { sourceRootsSet.addAll(deps); } } } List<URL> sourceRoots; try { sourceRoots = new LinkedList<URL>(Utilities.topologicalSort(sourceDeps.keySet(), sourceDeps)); } catch (TopologicalSortException ex) { if (interactive) { // Exceptions.attachLocalizedMessage(ex,NbBundle.getMessage(GoToImplementation.class, "ERR_CycleInDependencies")); Exceptions.printStackTrace(ex); } else { LOG.log(Level.FINE, null, ex); } return null; } sourceRoots.retainAll(sourceRootsSet); Collections.reverse(sourceRoots); return sourceRoots; } private static Set<URL> findReverseSourceRoots(final URL thisSourceRoot, Map<URL, List<URL>> sourceDeps, Map<URL, List<URL>> rootPeers, final FileObject thisFile) { long startTime = System.currentTimeMillis(); try { //TODO: from SourceUtils (which filters out source roots that do not belong to open projects): //Create inverse dependencies final Map<URL, List<URL>> inverseDeps = new HashMap<URL, List<URL>> (); for (Map.Entry<URL,List<URL>> entry : sourceDeps.entrySet()) { final URL u1 = entry.getKey(); final List<URL> l1 = entry.getValue(); for (URL u2 : l1) { List<URL> l2 = inverseDeps.get(u2); if (l2 == null) { l2 = new ArrayList<URL>(); inverseDeps.put (u2,l2); } l2.add (u1); } } //Collect dependencies final Set<URL> result = new HashSet<URL>(); final LinkedList<URL> todo = new LinkedList<URL> (); todo.add (thisSourceRoot); List<URL> peers = rootPeers != null ? rootPeers.get(thisSourceRoot) : null; if (peers != null) todo.addAll(peers); while (!todo.isEmpty()) { final URL u = todo.removeFirst(); if (!result.contains(u)) { result.add (u); final List<URL> ideps = inverseDeps.get(u); if (ideps != null) { todo.addAll (ideps); } } } return result; } finally { long endTime = System.currentTimeMillis(); Logger.getLogger("TIMER").log(Level.FINE, "Find Reverse Source Roots", //NOI18N new Object[]{thisFile, endTime - startTime}); } } private Set<URL> findBinaryRootsForSourceRoot(FileObject root, Map<URL, List<URL>> binaryDeps, boolean isSourceRoot) { // BinaryForSourceQuery.findBinaryRoots(thisSourceRoot).getRoots(); Set<URL> result = new HashSet<URL>(); if (isSourceRoot) { for (URL bin : binaryDeps.keySet()) { if (cancel.get()) return Collections.emptySet(); for (FileObject s : SourceForBinaryQuery.findSourceRoots(bin).getRoots()) { if (s == root) { result.add(bin); } } } } else if (binaryDeps.containsKey(root.toURL())) { result.add(root.toURL()); } return result; } private Set<TypeDescription> computeUsers(URL source, Set<TypeDescription> base, long[] classIndexCumulative, Map<TypeDescription, Set<TypeDescription>> output) { ClasspathInfo cpinfo = ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, ClassPathSupport.createClassPath(source)); return computeUsers(cpinfo, ClassIndex.SearchScope.SOURCE, base, classIndexCumulative, output); } private Set<TypeDescription> computeUsers(ClasspathInfo cpinfo, SearchScope scope, Set<TypeDescription> base, long[] classIndexCumulative, Map<TypeDescription, Set<TypeDescription>> output) { long startTime = System.currentTimeMillis(); try { List<TypeDescription> l = new LinkedList<TypeDescription>(base); Set<TypeDescription> result = new HashSet<TypeDescription>(); while (!l.isEmpty()) { if (cancel.get()) return null; TypeDescription eh = l.remove(0); result.add(eh); Set<ElementHandle<TypeElement>> typeElements = cpinfo.getClassIndex().getElements(eh.element, Collections.singleton(SearchKind.IMPLEMENTORS), EnumSet.of(scope)); //XXX: Canceling if (typeElements != null) { Set<TypeDescription> outputElements = output.get(eh); if (outputElements == null) { output.put(eh, outputElements = new HashSet<TypeDescription>()); } for (ElementHandle<TypeElement> te : typeElements) { TypeDescription currentTD = new TypeDescription(cpinfo, te); outputElements.add(currentTD); l.add(currentTD); } } } return result; } finally { classIndexCumulative[0] += (System.currentTimeMillis() - startTime); } } } private static final class TypeDescription { private final ClasspathInfo cpInfo; private final ElementHandle<TypeElement> element; public TypeDescription(ClasspathInfo cpInfo, ElementHandle<TypeElement> element) { this.cpInfo = cpInfo; this.element = element; } @Override public int hashCode() { int hash = 7; hash = 79 * hash + (this.cpInfo != null ? this.cpInfo.hashCode() : 0); hash = 79 * hash + (this.element != null ? this.element.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final TypeDescription other = (TypeDescription) obj; if (this.cpInfo != other.cpInfo && (this.cpInfo == null || !this.cpInfo.equals(other.cpInfo))) { return false; } if (this.element != other.element && (this.element == null || !this.element.equals(other.element))) { return false; } return true; } } }