/* * 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. */ /* * Contributor(s): [email protected] */ package org.netbeans.modules.maven.hints.errors; import com.sun.source.tree.ArrayTypeTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewArrayTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import java.awt.EventQueue; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import javax.lang.model.element.Name; import org.apache.maven.artifact.Artifact; import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; import org.netbeans.api.java.lexer.JavaTokenId; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.SourceUtils; import org.netbeans.api.lexer.Token; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.modules.java.hints.spi.ErrorRule; import org.netbeans.modules.java.hints.spi.ErrorRule.Data; import org.netbeans.modules.maven.api.ModelUtils; import org.netbeans.modules.maven.api.ModuleInfoUtils; import org.netbeans.modules.maven.api.NbMavenProject; import static org.netbeans.modules.maven.hints.errors.Bundle.*; import org.netbeans.modules.maven.hints.ui.SearchDependencyUI; import org.netbeans.modules.maven.indexer.api.NBVersionInfo; import org.netbeans.modules.maven.indexer.api.RepositoryPreferences; import org.netbeans.modules.maven.indexer.api.RepositoryQueries; import org.netbeans.spi.editor.hints.ChangeInfo; import org.netbeans.spi.editor.hints.EnhancedFix; import org.netbeans.spi.editor.hints.Fix; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Exceptions; import org.openide.util.NbBundle.Messages; import org.openide.util.RequestProcessor; /** * * @author Anuradha G */ public class SearchClassDependencyInRepo implements ErrorRule<Void> { private static final String MODULE_DOES_NOT_READ = "compiler.err.package.not.visible/compiler.misc.not.def.access.does.not.read"; private final AtomicBoolean cancel = new AtomicBoolean(false); public SearchClassDependencyInRepo() { } @Override public Set<String> getCodes() { return new HashSet<String>(Arrays.asList( MODULE_DOES_NOT_READ, "compiler.err.cant.resolve",//NOI18N "compiler.err.cant.resolve.location",//NOI18N "compiler.err.doesnt.exist",//NOI18N "compiler.err.not.stmt"));//NOI18N } @Override public List<Fix> run(final CompilationInfo info, String diagnosticKey, final int offset, TreePath treePath, Data<Void> data) { cancel.set(false); if (!SearchClassDependencyHint.isHintEnabled()) { return Collections.emptyList(); } //copyed from ImportClass int errorPosition = offset + 1; //TODO: +1 required to work OK, rethink if (errorPosition == (-1)) { return Collections.<Fix>emptyList(); } //copyed from ImportClass-end FileObject fileObject = info.getFileObject(); Project project = FileOwnerQuery.getOwner(fileObject); if (project == null) { return Collections.emptyList(); } NbMavenProject mavProj = project.getLookup().lookup(NbMavenProject.class); if (mavProj == null) { return Collections.emptyList(); } //copyed from ImportClass TreePath path = info.getTreeUtilities().pathFor(errorPosition); if (path.getParentPath() == null) { return Collections.emptyList(); } Tree leaf = path.getParentPath().getLeaf(); switch (leaf.getKind()) { case METHOD_INVOCATION: { MethodInvocationTree mit = (MethodInvocationTree) leaf; if (!mit.getTypeArguments().contains(path.getLeaf())) { return Collections.<Fix>emptyList(); } } //genaric handling case PARAMETERIZED_TYPE: { leaf = path.getParentPath().getParentPath().getLeaf(); } break; case ARRAY_TYPE: { leaf = path.getParentPath().getParentPath().getLeaf(); } break; } switch (leaf.getKind()) { case VARIABLE: { Name typeName = null; VariableTree variableTree = (VariableTree) leaf; if (variableTree.getType() != null) { switch (variableTree.getType().getKind()) { case IDENTIFIER: { typeName = ((IdentifierTree) variableTree.getType()).getName(); } break; case PARAMETERIZED_TYPE: { ParameterizedTypeTree ptt = ((ParameterizedTypeTree) variableTree.getType()); if (ptt.getType() != null && ptt.getType().getKind() == Kind.IDENTIFIER) { typeName = ((IdentifierTree) ptt.getType()).getName(); } } break; case ARRAY_TYPE: { ArrayTypeTree ptt = ((ArrayTypeTree) variableTree.getType()); if (ptt.getType() != null && ptt.getType().getKind() == Kind.IDENTIFIER) { typeName = ((IdentifierTree) ptt.getType()).getName(); } } break; } } ExpressionTree initializer = variableTree.getInitializer(); if (typeName != null && initializer != null) { Name itName = null; switch (initializer.getKind()) { case NEW_CLASS: { ExpressionTree identifier; NewClassTree classTree = (NewClassTree) initializer; identifier = classTree.getIdentifier(); if (identifier != null) { switch (identifier.getKind()) { case IDENTIFIER: itName = ((IdentifierTree) identifier).getName(); break; case PARAMETERIZED_TYPE: { ParameterizedTypeTree ptt = ((ParameterizedTypeTree) identifier); if (ptt.getType() != null && ptt.getType().getKind() == Kind.IDENTIFIER) { itName = ((IdentifierTree) ptt.getType()).getName(); } } break; } } } break; case NEW_ARRAY: { NewArrayTree arrayTree = (NewArrayTree) initializer; Tree type = arrayTree.getType(); if (type != null) { if (type.getKind().equals(Kind.IDENTIFIER)) { itName = ((IdentifierTree) type).getName(); } } } break; } if (typeName.equals(itName)) { return Collections.<Fix>emptyList(); } } } break; } String simpleOrQualifiedName = null; // XXX somewhat crude; is there a simpler way? TreePath p = path; while (p != null) { TreePath parent = p.getParentPath(); if (parent == null) { break; } Kind parentKind = parent.getLeaf().getKind(); if (parentKind == Kind.IMPORT) { simpleOrQualifiedName = p.getLeaf().toString(); break; } else if (parentKind == Kind.MEMBER_SELECT || parentKind == Kind.IDENTIFIER) { p = parent; } else { break; } } if (simpleOrQualifiedName == null) { try { Token<?> ident = findUnresolvedElementToken(info, offset); if (ident == null) { return Collections.<Fix>emptyList(); } simpleOrQualifiedName = ident.text().toString(); } catch (IOException e) { Exceptions.printStackTrace(e); return Collections.<Fix>emptyList(); } } //copyed from ImportClass-end if (cancel.get()) { return Collections.<Fix>emptyList(); } boolean isTestSource = false; //#212331 star static imports need to be stripped of the .* part. if (simpleOrQualifiedName.endsWith(".*")) { simpleOrQualifiedName = simpleOrQualifiedName.substring(0, simpleOrQualifiedName.length() - ".*".length()); } MavenProject mp = mavProj.getMavenProject(); String testSourceDirectory = mp.getBuild().getTestSourceDirectory(); if (testSourceDirectory != null) { File testdir = new File(testSourceDirectory); FileObject fo = FileUtil.toFileObject(testdir); if (fo != null) { isTestSource = FileUtil.isParentOf(fo, fileObject); } } List<Fix> fixes = new ArrayList<Fix>(); if(MODULE_DOES_NOT_READ.equals(diagnosticKey)) { Artifact artifact = getArtifact(mavProj, getVersionInfos(simpleOrQualifiedName), isTestSource); if(artifact != null) { URL url = FileUtil.urlForArchiveOrDir(artifact.getFile()); String name = url != null ? SourceUtils.getModuleName(url) : null; fixes.add(new AddRequiresFix(mavProj, name, artifact)); } return fixes; } if (SearchClassDependencyHint.isSearchDialog()) { fixes.add(new MavenSearchFix(project, simpleOrQualifiedName, isTestSource)); } else { //mkleint: this option is has rather serious performance impact. // we need to work on performance before we enable it.. // the result() version's impact is better, always just searching matters, never indexing. Collection<NBVersionInfo> findVersionsByClass = filter(mavProj, getVersionInfos(simpleOrQualifiedName), isTestSource); for (NBVersionInfo nbvi : findVersionsByClass) { fixes.add(new MavenFixImport(project, nbvi, isTestSource)); } } return fixes; } private static List<NBVersionInfo> getVersionInfos(String simpleOrQualifiedName) { return RepositoryQueries.findVersionsByClassResult(simpleOrQualifiedName, RepositoryPreferences.getInstance().getRepositoryInfos()).getResults(); } private Collection<NBVersionInfo> filter(NbMavenProject mavProj, List<NBVersionInfo> nbvis, boolean test) { Map<String, NBVersionInfo> items = new HashMap<String, NBVersionInfo>(); //check dependency already added List<Dependency> dependencies = new ArrayList<Dependency>(); MavenProject prj = mavProj.getMavenProject(); if (test) { dependencies.addAll(prj.getTestDependencies()); } else { dependencies.addAll(prj.getDependencies()); } for (NBVersionInfo info : nbvis) { String key = info.getGroupId() + ":" + info.getArtifactId(); boolean b = items.containsKey(key); if (!b) { items.put(key, info); } for (Dependency dependency : dependencies) { //check group id and ArtifactId and Scope even if (dependency.getGroupId() != null && dependency.getGroupId().equals(info.getGroupId())) { if (dependency.getArtifactId() != null && dependency.getArtifactId().equals(info.getArtifactId())) { if (!test && dependency.getScope() != null && ("compile".equals(dependency.getScope()))) {//NOI18N return Collections.emptyList(); } } } } } List<NBVersionInfo> filterd = new ArrayList<NBVersionInfo>(items.values()); return filterd; } private Artifact getArtifact(NbMavenProject mavProj, List<NBVersionInfo> nbvis, boolean isTestSource) { MavenProject mp = mavProj.getMavenProject(); List<Artifact> arts = new LinkedList<Artifact>(isTestSource ? mp.getTestArtifacts() : mp.getCompileArtifacts()); for (NBVersionInfo info : nbvis) { for (Artifact a : arts) { if (a.getGroupId() != null && a.getGroupId().equals(info.getGroupId())) { if (a.getArtifactId() != null && a.getArtifactId().equals(info.getArtifactId())) { String scope = a.getScope(); if ("compile".equals(scope) || "test".equals(scope)) { // NOI18N return a; } } } } } return null; } //copyed from ImportClass private static Token findUnresolvedElementToken(CompilationInfo info, int offset) throws IOException { TokenHierarchy<?> th = info.getTokenHierarchy(); TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language()); if (ts == null) { return null; } ts.move(offset); if (ts.moveNext()) { Token t = ts.token(); if (t.id() == JavaTokenId.DOT) { ts.moveNext(); t = ts.token(); } else { if (t.id() == JavaTokenId.LT) { ts.moveNext(); t = ts.token(); } else { if (t.id() == JavaTokenId.NEW) { boolean cont = ts.moveNext(); while (cont && ts.token().id() == JavaTokenId.WHITESPACE) { cont = ts.moveNext(); } if (!cont) { return null; } t = ts.token(); } } } if (t.id() == JavaTokenId.IDENTIFIER) { return ts.offsetToken(); } } return null; } @Override public String getId() { return "MAVEN_MISSING_CLASS";//NOI18N } @Override @Messages("LBL_Class_Search_DisplayName=Add Class Dependency From Maven Repository") public String getDisplayName() { return LBL_Class_Search_DisplayName(); } @Override public void cancel() { //cancel task cancel.set(true); } static final class MavenFixImport implements EnhancedFix { private final Project mavProj; private final NBVersionInfo nbvi; private final boolean test; public MavenFixImport(Project mavProj, NBVersionInfo nbvi, boolean test) { this.mavProj = mavProj; this.nbvi = nbvi; this.test = test; } @Override public CharSequence getSortText() { return getText(); } @Override @Messages({ "# {0} - maven coordinates", "LBL_Class_Search_Fix=Add Maven Dependency # {0}"}) public String getText() { return LBL_Class_Search_Fix(nbvi.getGroupId() + " : " + nbvi.getArtifactId() + " : " + nbvi.getVersion()); } @Override public ChangeInfo implement() throws Exception { ModelUtils.addDependency(mavProj.getProjectDirectory().getFileObject("pom.xml"), nbvi.getGroupId(), nbvi.getArtifactId(), nbvi.getVersion(), nbvi.getType(), test ? "test" : null, null, true);//NOI18N RequestProcessor.getDefault().post(new Runnable() { @Override public void run() { mavProj.getLookup().lookup(NbMavenProject.class).triggerDependencyDownload(); } }); return null; } } static final class MavenSearchFix implements EnhancedFix { private final Project mavProj; private final String clazz; private final boolean test; public MavenSearchFix(Project mavProj, String clazz, boolean test) { this.mavProj = mavProj; this.clazz = clazz; this.test = test; } @Override public CharSequence getSortText() { return getText(); } @Override @Messages({ "# {0} - classname", "LBL_Class_Search_ALL_Fix=Search Dependency at Maven Repositories for {0}"}) public String getText() { return LBL_Class_Search_ALL_Fix(clazz); } @Override @Messages("LBL_Search_Repo=Search In Maven Repositories ") public ChangeInfo implement() throws Exception { Runnable r = new Runnable() { public void run() { NBVersionInfo nbvi = null; SearchDependencyUI dependencyUI = new SearchDependencyUI(clazz, mavProj); DialogDescriptor dd = new DialogDescriptor(dependencyUI, LBL_Search_Repo()); dd.setClosingOptions(new Object[]{ dependencyUI.getAddButton(), DialogDescriptor.CANCEL_OPTION }); dd.setOptions(new Object[]{ dependencyUI.getAddButton(), DialogDescriptor.CANCEL_OPTION }); Object ret = DialogDisplayer.getDefault().notify(dd); if (dependencyUI.getAddButton() == ret) { nbvi = dependencyUI.getSelectedVersion(); } if (nbvi != null) { ModelUtils.addDependency(mavProj.getProjectDirectory().getFileObject("pom.xml"), nbvi.getGroupId(), nbvi.getArtifactId(), nbvi.getVersion(), nbvi.getType(), test ? "test" : null, null, true);//NOI18N RequestProcessor.getDefault().post(new Runnable() { @Override public void run() { mavProj.getLookup().lookup(NbMavenProject.class).triggerDependencyDownload(); } }); } } }; if(EventQueue.isDispatchThread()) { r.run(); } else { EventQueue.invokeLater(r); } return null; } } static final class AddRequiresFix implements EnhancedFix { private final NbMavenProject prj; private final String moduleName; private final Artifact artifact; public AddRequiresFix(NbMavenProject prj, String moduleName, Artifact artifact) { this.prj = prj; this.moduleName = moduleName; this.artifact = artifact; } @Override public CharSequence getSortText() { return getText(); } @Override @Messages({ "# {0} - classname", "LBL_Add_Fix=Add module {0} to ModuleInfo"}) public String getText() { return LBL_Add_Fix(moduleName); } @Override public ChangeInfo implement() { ModuleInfoUtils.addRequires(prj, Arrays.asList(artifact)); return null; } } }