--- a/java.hints/nbproject/project.xml +++ a/java.hints/nbproject/project.xml @@ -112,6 +112,14 @@ + org.netbeans.modules.apisupport.ant + + + + 2.53 + + + org.netbeans.modules.code.analysis --- a/java.hints/src/org/netbeans/modules/java/hints/errors/Bundle.properties +++ a/java.hints/src/org/netbeans/modules/java/hints/errors/Bundle.properties @@ -195,3 +195,7 @@ FIX_ChangeMethodReturnType=Change method return type to {0} DN_MissingReturnStatement=Handling of Missing Return Statement compiler error FIX_AddReturnStatement=Add return statement + +LBL_Module_Dependency_Search_DisplayName=Add Module Dependency +FIX_Module_Dependency_Search=Search Module Dependency for {0} +FIX_Module_Dependency_UpdatingDependencies=Update dependencies --- a/java.hints/src/org/netbeans/modules/java/hints/errors/SearchModuleDependency.java +++ a/java.hints/src/org/netbeans/modules/java/hints/errors/SearchModuleDependency.java @@ -0,0 +1,405 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * theanuradha@netbeans.org + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + * Portions Copyrighted 2012 markiewb@netbeans.org + */ +package org.netbeans.modules.java.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.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.lang.model.element.Name; +import org.netbeans.api.java.lexer.JavaTokenId; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.lexer.Token; +import org.netbeans.api.lexer.TokenHierarchy; +import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.api.progress.ProgressUtils; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.modules.apisupport.project.ModuleDependency; +import org.netbeans.modules.apisupport.project.NbModuleProject; +import org.netbeans.modules.apisupport.project.ProjectXMLManager; +import org.netbeans.modules.apisupport.project.ui.customizer.AddModulePanel; +import org.netbeans.modules.apisupport.project.ui.customizer.SingleModuleProperties; +import org.netbeans.modules.java.hints.spi.ErrorRule.Data; +import org.netbeans.spi.editor.hints.ChangeInfo; +import org.netbeans.spi.editor.hints.EnhancedFix; +import org.netbeans.spi.editor.hints.Fix; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.filesystems.FileObject; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import static org.netbeans.modules.java.hints.errors.Bundle.*; + +/** + * Fixable hint for an unresolved class which opens the NetBeans plattform + * module dependency dialog. The dialog will be prefilled with the name of the + * unresolved class. + * + *
  • Hint activation code taken from + * {@link org.netbeans.modules.maven.hints.errors.SearchClassDependencyInRepo}
  • + *
  • Dialog opening code taken from + * {@link org.netbeans.modules.apisupport.project.ui.LibrariesNode.AddModuleDependencyAction}
  • + *
+ * + * @author Anuradha G + * @author markiewb + */ +public class SearchModuleDependency implements org.netbeans.modules.java.hints.spi.ErrorRule { + + private AtomicBoolean cancel = new AtomicBoolean(false); + + public SearchModuleDependency() { + } + + @Override + public Set getCodes() { + return new HashSet(Arrays.asList( + "compiler.err.cant.resolve",//NOI18N + "compiler.err.cant.resolve.location",//NOI18N + "compiler.err.doesnt.exist",//NOI18N + "compiler.err.not.stmt"));//NOI18N + + } + + private boolean isHintEnabled() { + //TODO provide an option to disable this hint + return true; + } + + @Override + public List run(final CompilationInfo info, String diagnosticKey, + final int offset, TreePath treePath, Data data) { + cancel.set(false); + if (!isHintEnabled()) { + return Collections.emptyList(); + } + //copyed from ImportClass + int errorPosition = offset + 1; //TODO: +1 required to work OK, rethink + + if (errorPosition == (-1)) { + + return Collections.emptyList(); + } + //copyed from ImportClass-end + FileObject fileObject = info.getFileObject(); + Project project = FileOwnerQuery.getOwner(fileObject); + if (project == null) { + return Collections.emptyList(); + } + NbModuleProject nbModuleProject = project.getLookup().lookup(NbModuleProject.class); + + + //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.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.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.emptyList(); + } + simpleOrQualifiedName = ident.text().toString(); + } catch (IOException e) { + Exceptions.printStackTrace(e); + return Collections.emptyList(); + } + } + + //copyed from ImportClass-end + if (cancel.get()) { + return Collections.emptyList(); + } + //#212331 star static imports need to be stripped of the .* part. + if (simpleOrQualifiedName.endsWith(".*")) { + simpleOrQualifiedName = simpleOrQualifiedName.substring(0, simpleOrQualifiedName.length() - ".*".length()); + } + + List fixes = new ArrayList(); + fixes.add(new OpenDependencyDialogFix(nbModuleProject, simpleOrQualifiedName)); + return fixes; + } + + //copyed from ImportClass + private static Token findUnresolvedElementToken(CompilationInfo info, int offset) throws IOException { + TokenHierarchy th = info.getTokenHierarchy(); + TokenSequence 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 "NBM_MISSING_CLASS";//NOI18N + } + + @Override + public String getDisplayName() { + return NbBundle.getMessage(SearchModuleDependency.class, "LBL_Module_Dependency_Search_DisplayName"); + } + + @Override + public void cancel() { + //cancel task + cancel.set(true); + } + + static final class OpenDependencyDialogFix implements EnhancedFix { + + private NbModuleProject project; + private String clazz; + + public OpenDependencyDialogFix(NbModuleProject project, String clazz) { + this.project = project; + this.clazz = clazz; + } + + @Override + public CharSequence getSortText() { + return getText(); + } + + @Override + public String getText() { + return NbBundle.getMessage(OpenDependencyDialogFix.class, "FIX_Module_Dependency_Search", clazz); + } + + @Override + public ChangeInfo implement() throws Exception { + SingleModuleProperties props = SingleModuleProperties.getInstance(project); + final ModuleDependency[] newDeps = AddModulePanel.selectDependencies(props, clazz); + final AtomicBoolean cancel = new AtomicBoolean(); + ProgressUtils.runOffEventDispatchThread(new Runnable() { + public @Override + void run() { + ProjectXMLManager pxm = new ProjectXMLManager(project); + try { + pxm.addDependencies(new HashSet(Arrays.asList(newDeps))); // XXX cannot cancel + ProjectManager.getDefault().saveProject(project); + } catch (IOException e) { +// LOG.log(Level.INFO, "Cannot add selected dependencies: " + Arrays.asList(newDeps), e); + } catch (ProjectXMLManager.CyclicDependencyException ex) { + NotifyDescriptor.Message msg = new NotifyDescriptor.Message(ex.getLocalizedMessage(), NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(msg); + } + } + }, NbBundle.getMessage(SearchModuleDependency.class, "FIX_Module_Dependency_UpdatingDependencies"), cancel, false); + return null; + } + } +} --- a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml +++ a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml @@ -184,6 +184,7 @@ +