--- 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 @@
+