diff --git a/maven.embedder/nbproject/project.xml b/maven.embedder/nbproject/project.xml --- a/maven.embedder/nbproject/project.xml +++ b/maven.embedder/nbproject/project.xml @@ -163,6 +163,7 @@ org.netbeans.modules.maven.repository org.netbeans.modules.maven.search org.netbeans.modules.selenium.maven + org.netbeans.modules.refactoring.maven org.apache.maven org.apache.maven.artifact diff --git a/maven.indexer/nbproject/project.xml b/maven.indexer/nbproject/project.xml --- a/maven.indexer/nbproject/project.xml +++ b/maven.indexer/nbproject/project.xml @@ -180,6 +180,7 @@ org.netbeans.modules.maven.j2ee org.netbeans.modules.maven.repository org.netbeans.modules.maven.search + org.netbeans.modules.refactoring.maven org.netbeans.modules.maven.indexer.api org.netbeans.modules.maven.indexer.api.ui org.netbeans.modules.maven.indexer.spi diff --git a/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java b/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java --- a/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java +++ b/maven.indexer/src/org/netbeans/modules/maven/indexer/NexusRepositoryIndexerImpl.java @@ -41,7 +41,12 @@ */ package org.netbeans.modules.maven.indexer; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; import java.io.FileNotFoundException; +import java.util.Map.Entry; import org.apache.maven.index.expr.StringSearchExpression; import org.codehaus.plexus.util.FileUtils; import java.util.Map; @@ -53,6 +58,7 @@ import org.netbeans.modules.maven.indexer.api.NBVersionInfo; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.URI; import java.util.ArrayList; @@ -61,13 +67,18 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -80,7 +91,9 @@ import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.store.LockObtainFailedException; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.InvalidArtifactRTException; @@ -106,10 +119,6 @@ import org.apache.maven.index.creator.AbstractIndexCreator; import org.apache.maven.index.SearchEngine; import org.apache.maven.index.context.IndexCreator; -import org.apache.maven.index.creator.JarFileContentsIndexCreator; -import org.apache.maven.index.creator.MavenArchetypeArtifactInfoIndexCreator; -import org.apache.maven.index.creator.MavenPluginArtifactInfoIndexCreator; -import org.apache.maven.index.creator.MinimalArtifactInfoIndexCreator; import org.apache.maven.index.updater.IndexUpdateRequest; import org.apache.maven.index.updater.IndexUpdater; import org.apache.maven.index.updater.ResourceFetcher; @@ -140,11 +149,14 @@ import org.codehaus.plexus.classworlds.ClassWorld; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.modules.maven.embedder.MavenEmbedder; import org.netbeans.modules.maven.indexer.spi.ContextLoadedQuery; import org.openide.awt.StatusDisplayer; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; +import org.openide.util.Cancellable; import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.Mutex; @@ -178,6 +190,12 @@ private static final String NB_DEPENDENCY_GROUP = "nbdg"; //NOI18N private static final String NB_DEPENDENCY_ARTIFACT = "nbda"; //NOI18N private static final String NB_DEPENDENCY_VERSION = "nbdv"; //NOI18N + /** Used for indexing all classes in a local repository artifact: bar.Foo<foo.Bar (class bar.Foo is used by class for.Bar) + * < may be confusing to read, but will speed up the lucene prefix search: query: bar.Foo<* + */ + private static final String NB_DEPENDENCY_CLASSES = "nbdc"; //NOI18N + /** Used for separating a class and it's dependencies as field value of {@link #NB_DEPENDENCY_CLASSES} bar.Foo<foo.Bar */ + private static final String NB_DEPENDENCY_CLASS_SEPARATOR = "<"; private static final Logger LOGGER = Logger.getLogger(NexusRepositoryIndexerImpl.class.getName()); /*custom Index creators*/ /** @@ -281,7 +299,7 @@ LOGGER.log(Level.FINE, "Local context changed: {0}, unload/load", info.getId()); unloadIndexingContext(info); } else { - LOGGER.log(Level.FINER, "Skipping Context: {0}, already loaded.", info.getId()); + LOGGER.log(Level.FINE, "Skipping Context: {0}, already loaded.", info.getId()); break LOAD; // XXX does it suffice to just return here, or is code after block needed? } } @@ -296,17 +314,15 @@ List creators = new ArrayList(); try { - // XXX MINDEXER-34: maven-plugin must follow min, so using embedder.lookupList(IndexCreator.class) does not work - for (String id : new String[] {MinimalArtifactInfoIndexCreator.ID, MavenPluginArtifactInfoIndexCreator.ID, MavenArchetypeArtifactInfoIndexCreator.ID, JarFileContentsIndexCreator.ID}) { - creators.add(embedder.lookup(IndexCreator.class, id)); - } + creators.addAll(embedder.lookupList(IndexCreator.class)); } catch (ComponentLookupException x) { throw new IOException(x); } if (info.isLocal()) { // #164593 creators.add(new NbIndexCreator()); + creators.add(new NbClassDependenciesIndexCreator()); } else { - creators.add(new NotifyingIndexCreator()); + creators.add(new NotifyingIndexCreator(info)); } try { indexer.addIndexingContextForced( @@ -317,7 +333,6 @@ info.isRemoteDownloadable() ? info.getRepositoryUrl() : null, // repositoryUrl info.isRemoteDownloadable() ? indexUpdateUrl : null, creators); - LOGGER.log(Level.FINE, "using index creators: {0}", creators); } catch (IOException ex) { LOGGER.log(Level.INFO, "Found a broken index at " + loc.getAbsolutePath(), ex); FileUtils.deleteDirectory(loc); @@ -517,7 +532,7 @@ } } if (nic != null) { - nic.start(listener); + nic.start(); } try { remoteIndexUpdater.fetchAndUpdateIndex(iur); @@ -554,16 +569,38 @@ } /** Just tracks what is being unpacked after a remote index has been downloaded. */ private static final class NotifyingIndexCreator implements IndexCreator { - private RemoteIndexTransferListener listener; - NotifyingIndexCreator() {} - private void start(RemoteIndexTransferListener listener) { - this.listener = listener; + private final RepositoryInfo beingIndexed; + private ProgressHandle handle; + private final AtomicBoolean canceled = new AtomicBoolean(); + NotifyingIndexCreator(RepositoryInfo info) { + beingIndexed = info; + } + private void start() { + handle = null; + canceled.set(false); } private void end() { - listener = null; + if (handle != null) { + handle.finish(); + handle = null; + } + canceled.set(false); } public @Override void updateDocument(ArtifactInfo artifactInfo, Document document) { - listener.unpackingProgress(artifactInfo.groupId + ':' + artifactInfo.artifactId); + if (canceled.get()) { + throw new Cancellation(); + } + if (handle == null) { + handle = ProgressHandleFactory.createHandle(NbBundle.getMessage(NexusRepositoryIndexerImpl.class, "LBL_unpacking", beingIndexed.getName()), new Cancellable() { { + Cancellation.register(this); + } + public @Override boolean cancel() { + return canceled.compareAndSet(false, true); + } + }); + handle.start(); + } + handle.progress(artifactInfo.groupId + ':' + artifactInfo.artifactId); } public @Override Collection getIndexerFields() { return Collections.emptySet(); @@ -915,6 +952,61 @@ } @Override + public Set findDependendClassesFromClass(final String className, final List repos) { + try { + final Set dependendClasses = new HashSet(); + for (final RepositoryInfo repo : repos) { + if (repo.isLocal()) { + getRepoMutex(repo).writeAccess(new Mutex.ExceptionAction() { + + public @Override + Void run() throws Exception { + loadIndexingContext(repo); + final String searchString = className.replace(".", "/"); + // TODO Query is not really efficient, should return only hits which contain: 'className<' in field + final Query refClassQuery = indexer.constructQuery(NbClassDependenciesIndexCreator.FLD_NB_DEPENDENCY_CLASS.getOntology(), new StringSearchExpression(searchString)); + final TopScoreDocCollector collector = TopScoreDocCollector.create(MAX_RESULT_COUNT, true); + final Collection contexts = getContexts(new RepositoryInfo[]{repo}); + final IndexingContext context = (IndexingContext) contexts.toArray()[0]; + context.getIndexSearcher().search(refClassQuery, collector); + final ScoreDoc[] hits = collector.topDocs().scoreDocs; + for (ScoreDoc hit : hits) { + int docId = hit.doc; + Document d = context.getIndexSearcher().doc(docId); +// LOGGER.log(Level.INFO, className + "uses: " + d.get(NB_DEPENDENCY_CLASSES)); + final Set refClasses = parseField(searchString, d.get(NB_DEPENDENCY_CLASSES)); + for (String binaryClass : refClasses) { + dependendClasses.add(binaryClass); + } + } + return null; + } + + private Set parseField(final String className, final String fldValue) { + final Set refClasses = new HashSet(); + final String[] split = fldValue.split("\n"); + for (String grp : split) { + // TODO this condition is only necessary because the not fully working query --> it filters false positives. + if (grp.contains(className + NB_DEPENDENCY_CLASS_SEPARATOR)) { + final String classGrp = grp.substring(grp.indexOf(NB_DEPENDENCY_CLASS_SEPARATOR) + 1, grp.length() - 1); + final String[] classes = classGrp.split(","); + refClasses.addAll(Arrays.asList(classes)); + } + } + return refClasses; + } + + }); + } + } + return dependendClasses; + } catch (MutexException ex) { + Exceptions.printStackTrace(ex); + } + return Collections.emptySet(); + } + + @Override public List findDependencyUsage(final String groupId, final String artifactId, final String version, List repos) { try { final List infos = new ArrayList(); @@ -1213,8 +1305,9 @@ // we don't want javadoc and sources shown anywhere, we use the getJavadocExists(), getSourceExists() methods. continue; } + // fextension != packaging - e.g a pom could be packaging "bundle" but from type/extension "jar" NBVersionInfo nbvi = new NBVersionInfo(ai.repository, ai.groupId, ai.artifactId, - ai.version, ai.packaging, ai.packaging, ai.name, ai.description, ai.classifier); + ai.version, ai.fextension, ai.packaging, ai.name, ai.description, ai.classifier); /*Javadoc & Sources*/ nbvi.setJavadocExists(ai.javadocExists == ArtifactAvailablility.PRESENT); nbvi.setSourcesExists(ai.sourcesExists == ArtifactAvailablility.PRESENT); @@ -1237,7 +1330,292 @@ } return q; } + + /** + * Scanns every class in all artifacts located in local maven repository for it's class dependencies. + */ + private static class NbClassDependenciesIndexCreator extends AbstractIndexCreator { + private final List remoteRepos; + NbClassDependenciesIndexCreator() { + remoteRepos = new ArrayList(); + for (RepositoryInfo info : RepositoryPreferences.getInstance().getRepositoryInfos()) { + if (!info.isLocal()) { + remoteRepos.add(new MavenArtifactRepository(info.getId(), info.getRepositoryUrl(), new DefaultRepositoryLayout(), new ArtifactRepositoryPolicy(), new ArtifactRepositoryPolicy())); + } + } + } + + private WeakReference embedderRef = null; + + private MavenEmbedder getEmbedder() { + MavenEmbedder res = (null != embedderRef ? embedderRef.get() : null); + if (null == res) { + res = EmbedderFactory.getOnlineEmbedder(); + embedderRef = new WeakReference(res); + } + return res; + } + + private MavenProject load(ArtifactInfo ai) { + try { + Artifact projectArtifact = getEmbedder().createArtifact( + ai.groupId, + ai.artifactId, + ai.version, + ai.packaging != null ? ai.packaging : "jar"); + DefaultProjectBuildingRequest dpbr = new DefaultProjectBuildingRequest(); + dpbr.setLocalRepository(getEmbedder().getLocalRepository()); + dpbr.setRemoteRepositories(remoteRepos); + dpbr.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL); + dpbr.setSystemProperties(getEmbedder().getSystemProperties()); + + ProjectBuildingResult res = getEmbedder().buildProject(projectArtifact, dpbr); + if (res.getProject() != null) { + return res.getProject(); + } else { + LOGGER.log(Level.FINE, "No project model from repository for {0}: {1}", new Object[]{ai, res.getProblems()}); + } + } catch (ProjectBuildingException ex) { + LOGGER.log(Level.FINE, "Failed to load project model from repository for {0}: {1}", new Object[]{ai, ex}); + } catch (Exception exception) { + LOGGER.log(Level.FINE, "Failed to load project model from repository for " + ai, exception); + } + return null; + } + + private Artifact artifact; + + public @Override void populateArtifactInfo(ArtifactContext context) throws IOException { + ArtifactInfo ai = context.getArtifactInfo(); + if (ai.classifier != null) { + //don't process items with classifier + return; + } + try { + MavenProject mp = load(ai); + + if (mp != null) { + artifact = getEmbedder().getLocalRepository().find(mp.getArtifact()); + ai.setFieldValue(NbClassDependenciesIndexCreator.FLD_NB_DEPENDENCY_CLASS.getOntology(), "empty"); + } + } catch (InvalidArtifactRTException ex) { + Exceptions.printStackTrace(ex); + } + } + + @Override + public boolean updateArtifactInfo(Document document, ArtifactInfo artifactInfo) { + // Add the data to the ArtifactInfo + artifactInfo.setFieldValue(NbClassDependenciesIndexCreator.FLD_NB_DEPENDENCY_CLASS.getOntology(), document.get(NB_DEPENDENCY_CLASSES)); + return true; + } + + public @Override void updateDocument(ArtifactInfo ai, Document doc) { + final Map> classDeps = new HashMap>(); + Map classfiles = new TreeMap(); + File jar = null; + if (artifact != null) { + // TODO are there any other possible archive types? + if (isArchiveSupported(artifact) && isArtifactFileArchive(artifact)) { + try { + read(new JarFile(artifact.getFile()), classfiles, new HashSet(Collections.singleton(jar)), ""); + for (Map.Entry entry : classfiles.entrySet()) { + String clazz = entry.getKey(); + byte[] data = entry.getValue(); + addDependenciesToMap(clazz, data, classDeps); + } + addMapToIndex(classDeps, doc); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + } + } + + private boolean isArtifactFileArchive(final Artifact arti) { + final File artifactFile = arti.getFile(); + return artifactFile != null && artifactFile.exists(); + } + + private boolean isArchiveSupported(final Artifact arti) { + final String type = arti.getType(); + if (type.equals("jar")) { + return true; + } else if (type.equals("war")) { + return true; + } + return false; + } + + private void addDependenciesToMap(final String clazz, final byte[] data, final Map> depsMap) throws IOException{ + //log("Verifying linkage of " + clazz.replace('/', '.'), Project.MSG_DEBUG); + final Set dependencies = dependencies(data); + + for (String usedClass : dependencies) { + // filter classes out which are referring themselfs + if (!usedClass.equals(clazz)) { + Set refClasses = depsMap.get(usedClass); + if (refClasses == null) { + refClasses = new HashSet(); + depsMap.put(usedClass, refClasses); + } + refClasses.add(clazz); + } + } + } + + private void addMapToIndex(final Map> classDeps, final Document doc) { + final StringBuilder fldBuilder = new StringBuilder(); + for (Entry> entry : classDeps.entrySet()) { + String usedClass = entry.getKey(); + // add usedClass with a separator + fldBuilder.append(usedClass.replace(".", "/")).append(NB_DEPENDENCY_CLASS_SEPARATOR); + // add all classes which refer usedClass (,) seperated + Set value = entry.getValue(); + for (String refClass : value) { + fldBuilder.append(refClass.replace(".", "/")).append(","); + } + // Mark the end of all referring classes + fldBuilder.append("\n"); + } + LOGGER.log(Level.FINE, "Class dependencies index field: {0}", fldBuilder.toString()); + doc.add(FLD_NB_DEPENDENCY_CLASS.toField(fldBuilder.toString())); + } + + static void read(JarFile jf, Map classfiles, Set alreadyRead, String ignores) throws IOException { + Pattern p = (ignores != null)? Pattern.compile(ignores): null; + Enumeration e = jf.entries(); + while (e.hasMoreElements()) { + JarEntry entry = e.nextElement(); + String name = entry.getName(); + if (!name.endsWith(".class")) { + continue; + } + String clazz = name.substring(0, name.length() - 6).replace('/', '.'); + if (p != null && p.matcher(clazz).matches()) { + continue; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.max((int) entry.getSize(), 0)); + InputStream is = jf.getInputStream(entry); + try { + byte[] buf = new byte[4096]; + int read; + while ((read = is.read(buf)) != -1) { + baos.write(buf, 0, read); + } + } finally { + is.close(); + } + classfiles.put(clazz, baos.toByteArray()); + } + } + + + static Set dependencies(byte[] data) throws IOException { + Set result = new TreeSet(); + DataInput input = new DataInputStream(new ByteArrayInputStream(data)); + skip(input, 8); // magic, minor_version, major_version + int size = input.readUnsignedShort() - 1; // constantPoolCount + String[] utf8Strings = new String[size]; + boolean[] isClassName = new boolean[size]; + boolean[] isDescriptor = new boolean[size]; + for (int i = 0; i < size; i++) { + byte tag = input.readByte(); + switch (tag) { + case 1: // CONSTANT_Utf8 + utf8Strings[i] = input.readUTF(); + break; + case 7: // CONSTANT_Class + int index = input.readUnsignedShort() - 1; + if (index >= size) { + throw new IOException("@" + i + ": CONSTANT_Class_info.name_index " + index + " too big for size of pool " + size); + } + //log("Class reference at " + index, Project.MSG_DEBUG); + isClassName[index] = true; + break; + case 3: // CONSTANT_Integer + case 4: // CONSTANT_Float + case 9: // CONSTANT_Fieldref + case 10: // CONSTANT_Methodref + case 11: // CONSTANT_InterfaceMethodref + skip(input, 4); + break; + case 12: // CONSTANT_NameAndType + skip(input, 2); + index = input.readUnsignedShort() - 1; + if (index >= size || index < 0) { + throw new IOException("@" + i + ": CONSTANT_NameAndType_info.descriptor_index " + index + " too big for size of pool " + size); + } + isDescriptor[index] = true; + break; + case 8: // CONSTANT_String + skip(input, 2); + break; + case 5: // CONSTANT_Long + case 6: // CONSTANT_Double + skip(input, 8); + i++; // weirdness in spec + break; + default: + LOGGER.log(Level.WARNING, "Unrecognized constant pool tag {0} at index {1}; running UTF-8 strings: {2}", new Object[]{tag, i, Arrays.asList(utf8Strings)}); + continue; + } + } + //task.log("UTF-8 strings: " + Arrays.asList(utf8Strings), Project.MSG_DEBUG); + for (int i = 0; i < size; i++) { + String s = utf8Strings[i]; + if (s != null) { + if (isClassName[i]) { + while (s.charAt(0) == '[') { + // array type + s = s.substring(1); + } + if (s.length() == 1) { + // primitive + continue; + } + String c; + if (s.charAt(s.length() - 1) == ';' && s.charAt(0) == 'L') { + // Uncommon but seems sometimes this happens. + c = s.substring(1, s.length() - 1); + } else { + c = s; + } + result.add(c.replace('/', '.')); + } else if (isDescriptor[i]) { + int idx = 0; + while ((idx = s.indexOf('L', idx)) != -1) { + int semi = s.indexOf(';', idx); + if (semi == -1) { + throw new IOException("Invalid type or descriptor: " + s); + } + result.add(s.substring(idx + 1, semi).replace('/', '.')); + idx = semi; + } + } + } + } + return result; + } + + private static void skip(DataInput input, int bytes) throws IOException { + int skipped = input.skipBytes(bytes); + if (skipped != bytes) { + throw new IOException("Truncated class file"); + } + } + + private static final String NS = "urn:NbClassDependenciesIndexCreator"; // NOI18N + public static final IndexerField FLD_NB_DEPENDENCY_CLASS = new IndexerField(new Field( + null, NS, NB_DEPENDENCY_CLASSES, "From a class's inside an artifact used class-dependencies"), + IndexerFieldVersion.V3, NB_DEPENDENCY_CLASSES, "From a class's inside an artifact used class-dependencies", Store.YES, Index.ANALYZED); + @Override + public Collection getIndexerFields() { + return Arrays.asList(FLD_NB_DEPENDENCY_CLASS); + } + } + private static class NbIndexCreator extends AbstractIndexCreator { private final List remoteRepos; @@ -1273,7 +1651,7 @@ MavenProject mp = load(ai); if (mp != null) { List dependencies = mp.getDependencies(); - LOGGER.log(Level.FINER, "Successfully loaded project model from repository for {0} with {1} dependencies", new Object[] {ai, dependencies.size()}); + LOGGER.log(Level.FINE, "Successfully loaded project model from repository for {0} with {1} dependencies", new Object[] {ai, dependencies.size()}); dependenciesByArtifact.put(ai, dependencies); } } catch (InvalidArtifactRTException ex) { @@ -1320,12 +1698,12 @@ if (res.getProject() != null) { return res.getProject(); } else { - LOGGER.log(Level.FINER, "No project model from repository for {0}: {1}", new Object[] {ai, res.getProblems()}); + LOGGER.log(Level.FINE, "No project model from repository for {0}: {1}", new Object[] {ai, res.getProblems()}); } } catch (ProjectBuildingException ex) { - LOGGER.log(Level.FINER, "Failed to load project model from repository for {0}: {1}", new Object[] {ai, ex}); + LOGGER.log(Level.FINE, "Failed to load project model from repository for {0}: {1}", new Object[] {ai, ex}); } catch (Exception exception) { - LOGGER.log(Level.FINER, "Failed to load project model from repository for " + ai, exception); + LOGGER.log(Level.FINE, "Failed to load project model from repository for " + ai, exception); } return null; } diff --git a/maven.indexer/src/org/netbeans/modules/maven/indexer/api/RepositoryQueries.java b/maven.indexer/src/org/netbeans/modules/maven/indexer/api/RepositoryQueries.java --- a/maven.indexer/src/org/netbeans/modules/maven/indexer/api/RepositoryQueries.java +++ b/maven.indexer/src/org/netbeans/modules/maven/indexer/api/RepositoryQueries.java @@ -48,6 +48,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -61,6 +62,7 @@ import org.netbeans.modules.maven.indexer.spi.DependencyInfoQueries; import org.netbeans.modules.maven.indexer.spi.GenericFindQuery; import org.netbeans.modules.maven.indexer.spi.RepositoryIndexerImplementation; +import org.openide.filesystems.FileObject; import org.openide.util.Exceptions; /** @@ -300,6 +302,26 @@ query.addResults(null, true); } + /** + * Returns all referring class of {@code className} in the local repository + * + * @param className the binary class name of the class to find referers. + * @param repos repositories to search in + * @return a Set of binary class name's which are referring {@code className} + */ + public static Set findDependendClassesByClass(final String className, final RepositoryInfo... repos) { + final Collection> all = splitReposByType(repos); + final Set dependendClasses = new HashSet(); + for (List rps : all) { + final RepositoryIndexerImplementation impl = RepositoryIndexer.findImplementation(rps.get(0)); + final ClassesQuery chq = impl.getCapabilityLookup().lookup(ClassesQuery.class); + if (chq != null) { + dependendClasses.addAll(chq.findDependendClassesFromClass(className, rps)); + } + } + return dependendClasses; + } + public static List findArchetypes(RepositoryInfo... repos) { Collection> all = splitReposByType(repos); List toRet = new ArrayList(); diff --git a/maven.indexer/src/org/netbeans/modules/maven/indexer/spi/ClassesQuery.java b/maven.indexer/src/org/netbeans/modules/maven/indexer/spi/ClassesQuery.java --- a/maven.indexer/src/org/netbeans/modules/maven/indexer/spi/ClassesQuery.java +++ b/maven.indexer/src/org/netbeans/modules/maven/indexer/spi/ClassesQuery.java @@ -43,6 +43,7 @@ package org.netbeans.modules.maven.indexer.spi; import java.util.List; +import java.util.Set; import org.netbeans.modules.maven.indexer.api.NBVersionInfo; import org.netbeans.modules.maven.indexer.api.RepositoryInfo; @@ -61,4 +62,13 @@ * search is too general. */ public List findVersionsByClass(final String className, List repos); + + /** + * Returns all referring class of {@code className} in the local repository + * + * @param className the binary class name of the class to find referers. + * @param repos repositories to search in + * @return a Set of binary class name's which are referring {@code className} + */ + public Set findDependendClassesFromClass(final String className, List repos); } diff --git a/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RetoucheCommit.java b/refactoring.maven/src/org/netbeans/modules/refactoring/maven/plugins/RetoucheCommit.java copy from refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RetoucheCommit.java copy to refactoring.maven/src/org/netbeans/modules/refactoring/maven/plugins/RetoucheCommit.java --- a/refactoring.java/src/org/netbeans/modules/refactoring/java/plugins/RetoucheCommit.java +++ b/refactoring.maven/src/org/netbeans/modules/refactoring/maven/plugins/RetoucheCommit.java @@ -41,7 +41,7 @@ * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ -package org.netbeans.modules.refactoring.java.plugins; +package org.netbeans.modules.refactoring.maven.plugins; import java.io.File; import java.io.IOException; diff --git a/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.form b/refactoring.maven/src/org/netbeans/modules/refactoring/maven/ui/WhereUsedPanel.form copy from refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.form copy to refactoring.maven/src/org/netbeans/modules/refactoring/maven/ui/WhereUsedPanel.form --- a/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.form +++ b/refactoring.maven/src/org/netbeans/modules/refactoring/maven/ui/WhereUsedPanel.form @@ -15,7 +15,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -63,12 +63,12 @@ - + - + @@ -88,7 +88,7 @@ - + @@ -96,7 +96,7 @@ - + @@ -123,27 +123,18 @@ - - - - - - - - - - + - + @@ -162,7 +153,7 @@ - + @@ -170,7 +161,7 @@ - + @@ -188,12 +179,12 @@ - + - + @@ -205,6 +196,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -231,8 +242,8 @@ - - + + @@ -271,7 +282,7 @@ - + @@ -290,7 +301,7 @@ - + diff --git a/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.java b/refactoring.maven/src/org/netbeans/modules/refactoring/maven/ui/WhereUsedPanel.java copy from refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.java copy to refactoring.maven/src/org/netbeans/modules/refactoring/maven/ui/WhereUsedPanel.java --- a/refactoring.java/src/org/netbeans/modules/refactoring/java/ui/WhereUsedPanel.java +++ b/refactoring.maven/src/org/netbeans/modules/refactoring/maven/ui/WhereUsedPanel.java @@ -41,11 +41,10 @@ * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ -package org.netbeans.modules.refactoring.java.ui; +package org.netbeans.modules.refactoring.maven.ui; import java.awt.BorderLayout; import java.awt.Component; -import java.awt.Dimension; import java.awt.event.ItemEvent; import java.io.IOException; import java.text.MessageFormat; @@ -64,12 +63,9 @@ import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.ProjectInformation; import org.netbeans.api.project.ProjectUtils; -import org.netbeans.modules.refactoring.java.RetoucheUtils; -import org.netbeans.modules.refactoring.java.ui.WhereUsedPanel.Scope; import org.netbeans.modules.refactoring.spi.ui.CustomRefactoringPanel; import org.openide.awt.Mnemonics; import org.openide.util.NbBundle; -import org.netbeans.modules.refactoring.java.RefactoringModule; import javax.lang.model.element.Modifier; import javax.swing.JLabel; import javax.swing.JPanel; @@ -82,10 +78,13 @@ import org.netbeans.api.java.source.JavaSource.Phase; import org.netbeans.api.java.source.ui.ElementHeaders; import org.netbeans.api.project.Project; +import org.netbeans.modules.refactoring.maven.RefactoringModule; +import org.netbeans.modules.refactoring.maven.RetoucheUtils; /** * @author Jan Becicka + * @author Christian Moser */ public class WhereUsedPanel extends JPanel implements CustomRefactoringPanel { @@ -103,16 +102,47 @@ initComponents(); //parent.setPreviewEnabled(false); } + + /** + * Disable/Enables currently not supported search options. + * + * @param state selected-state of {@link #searchInLocalRepo} checkbox + */ + private void searchInLocalRepoHelper(final Boolean state) { + RefactoringModule.setOption("searchInLocalRepo.whereUsed", state); + if (state) { + searchInComments.setEnabled(false); + c_subclasses.setEnabled(false); + c_directOnly.setEnabled(false); + } else { + searchInComments.setEnabled(true); + c_subclasses.setEnabled(true); + c_directOnly.setEnabled(true); + } + RefactoringModule.setOption("searchInComments.whereUsed", false); + searchInComments.setSelected(false); + c_subclasses.setSelected(false); + c_directOnly.setSelected(false); + c_usages.setSelected(true); + } public enum Scope { - ALL, - CURRENT + OPEN_PROJECTS, + CURRENT_PROJECT, + LOCAL_REPO, } public Scope getScope() { - if (scope.getSelectedIndex()==1) - return Scope.CURRENT; - return Scope.ALL; + switch (scope.getSelectedIndex()) { + case 0: + return Scope.OPEN_PROJECTS; + case 1: + return Scope.CURRENT_PROJECT; + case 2: + return Scope.LOCAL_REPO; + default: + return Scope.OPEN_PROJECTS; + } } private boolean initialized = false; @@ -133,13 +163,16 @@ Project p = FileOwnerQuery.getOwner(element.getFileObject()); final JLabel currentProject; final JLabel allProjects; + final JLabel localRepo; if (p!=null) { ProjectInformation pi = ProjectUtils.getInformation(FileOwnerQuery.getOwner(element.getFileObject())); currentProject = new JLabel(pi.getDisplayName(), pi.getIcon(), SwingConstants.LEFT); allProjects = new JLabel(NbBundle.getMessage(WhereUsedPanel.class,"LBL_AllProjects"), pi.getIcon(), SwingConstants.LEFT); + localRepo = new JLabel(NbBundle.getMessage(WhereUsedPanel.class,"LBL_LocalRepo"), pi.getIcon(), SwingConstants.LEFT); } else { currentProject = null; allProjects = null; + localRepo = null; } CancellableTask task =new CancellableTask() { public void cancel() { @@ -189,6 +222,7 @@ SwingUtilities.invokeLater(new Runnable() { public void run() { + boolean localRepoSupported = false; remove(classesPanel); remove(methodsPanel); label.setText(labelText); @@ -208,6 +242,7 @@ } else if ((element.getKind() == ElementKind.CLASS) || (element.getKind() == ElementKind.INTERFACE)) { add(classesPanel, BorderLayout.CENTER); classesPanel.setVisible(true); + localRepoSupported = true; } else { remove(classesPanel); remove(methodsPanel); @@ -216,11 +251,18 @@ c_usages.setVisible(false); c_directOnly.setVisible(false); } + if (currentProject!=null) { - scope.setModel(new DefaultComboBoxModel(new Object[]{allProjects, currentProject })); + scope.setModel(new DefaultComboBoxModel(new Object[]{allProjects, currentProject, localRepo })); int defaultItem = (Integer) RefactoringModule.getOption("whereUsed.scope", 0); // NOI18N scope.setSelectedIndex(defaultItem); scope.setRenderer(new JLabelRenderer()); + // refersh local repo scope + searchInLocalRepoHelper(defaultItem == 2 ? true : false); + if (!localRepoSupported) { + // Local Repo scope is only supported in class / interface search + scope.removeItem(localRepo); + } } else { scopePanel.setVisible(false); } @@ -310,10 +352,10 @@ m_overriders = new javax.swing.JCheckBox(); m_usages = new javax.swing.JCheckBox(); classesPanel = new javax.swing.JPanel(); - jPanel2 = new javax.swing.JPanel(); c_subclasses = new javax.swing.JRadioButton(); c_usages = new javax.swing.JRadioButton(); c_directOnly = new javax.swing.JRadioButton(); + localRepoPanel = new javax.swing.JPanel(); commentsPanel = new javax.swing.JPanel(); label = new javax.swing.JLabel(); searchInComments = new javax.swing.JCheckBox(); @@ -342,7 +384,7 @@ gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; gridBagConstraints.insets = new java.awt.Insets(0, 12, 0, 0); methodsPanel.add(m_isBaseClass, gridBagConstraints); - java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/netbeans/modules/refactoring/java/ui/Bundle"); // NOI18N + java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/netbeans/modules/refactoring/maven/ui/Bundle"); // NOI18N m_isBaseClass.getAccessibleContext().setAccessibleDescription(bundle.getString("ACSD_isBaseClass")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); @@ -396,14 +438,6 @@ add(methodsPanel, java.awt.BorderLayout.CENTER); classesPanel.setLayout(new java.awt.GridBagLayout()); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - classesPanel.add(jPanel2, gridBagConstraints); buttonGroup.add(c_subclasses); org.openide.awt.Mnemonics.setLocalizedText(c_subclasses, org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "LBL_FindAllSubtypes")); // NOI18N @@ -437,13 +471,33 @@ classesPanel.add(c_directOnly, gridBagConstraints); c_directOnly.getAccessibleContext().setAccessibleDescription(bundle.getString("ACSD_directOnly")); // NOI18N + javax.swing.GroupLayout localRepoPanelLayout = new javax.swing.GroupLayout(localRepoPanel); + localRepoPanel.setLayout(localRepoPanelLayout); + localRepoPanelLayout.setHorizontalGroup( + localRepoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 361, Short.MAX_VALUE) + ); + localRepoPanelLayout.setVerticalGroup( + localRepoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 8, Short.MAX_VALUE) + ); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + classesPanel.add(localRepoPanel, gridBagConstraints); + add(classesPanel, java.awt.BorderLayout.CENTER); commentsPanel.setLayout(new java.awt.BorderLayout()); commentsPanel.add(label, java.awt.BorderLayout.NORTH); searchInComments.setSelected(((Boolean) RefactoringModule.getOption("searchInComments.whereUsed", Boolean.FALSE)).booleanValue()); - org.openide.awt.Mnemonics.setLocalizedText(searchInComments, org.openide.util.NbBundle.getBundle(WhereUsedPanel.class).getString("LBL_SearchInComents")); // NOI18N + searchInComments.setLabel(org.openide.util.NbBundle.getMessage(WhereUsedPanel.class, "LBL_SearchInComents")); // NOI18N searchInComments.setMargin(new java.awt.Insets(10, 14, 2, 2)); searchInComments.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { @@ -473,7 +527,7 @@ .addContainerGap() .addComponent(scopeLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(scope, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(scope, 0, 301, Short.MAX_VALUE) .addContainerGap()) ); scopePanelLayout.setVerticalGroup( @@ -489,6 +543,7 @@ private void scopeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_scopeActionPerformed RefactoringModule.setOption("whereUsed.scope", scope.getSelectedIndex()); // NOI18N + searchInLocalRepoHelper(scope.getSelectedIndex() == 2 ? true : false); }//GEN-LAST:event_scopeActionPerformed private void searchInCommentsItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_searchInCommentsItemStateChanged @@ -530,8 +585,8 @@ private javax.swing.JPanel classesPanel; private javax.swing.JPanel commentsPanel; private javax.swing.JPanel jPanel1; - private javax.swing.JPanel jPanel2; private javax.swing.JLabel label; + private javax.swing.JPanel localRepoPanel; private javax.swing.JCheckBox m_isBaseClass; private javax.swing.JCheckBox m_overriders; private javax.swing.JCheckBox m_usages;