Skip to content

Commit

Permalink
Add a Quick-Fix to add a dependency on missing types
Browse files Browse the repository at this point in the history
Currently if a type is missing a user needs to search the workspace,
then probably find the project and then go to the pom and add the
desired coordinates.

This now adds a Quick-Fix to automate this and offers the user to add a
dependency to a workspace project that contains the missing type.
  • Loading branch information
laeubi committed Nov 9, 2022
1 parent 9416e75 commit a743960
Show file tree
Hide file tree
Showing 13 changed files with 334 additions and 13 deletions.
12 changes: 12 additions & 0 deletions org.eclipse.m2e.core/src/org/eclipse/m2e/core/MavenPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@

package org.eclipse.m2e.core;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;

import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.embedder.IMavenConfiguration;
import org.eclipse.m2e.core.embedder.MavenModelManager;
import org.eclipse.m2e.core.internal.IMavenConstants;
import org.eclipse.m2e.core.internal.MavenPluginActivator;
import org.eclipse.m2e.core.project.IMavenProjectFacade;
import org.eclipse.m2e.core.project.IMavenProjectRegistry;
Expand Down Expand Up @@ -71,4 +75,12 @@ public static IWorkspaceClassifierResolverManager getWorkspaceClassifierResolver
return MavenPluginActivator.getDefault().getWorkspaceClassifierResolverManager();
}

public static boolean isMavenProject(IProject project) {
try {
return project != null && project.isAccessible() && project.hasNature(IMavenConstants.NATURE_ID);
} catch(CoreException ex) {
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;

import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.IMaven;
import org.eclipse.m2e.core.embedder.IMavenConfiguration;
import org.eclipse.m2e.core.internal.IMavenConstants;
import org.eclipse.m2e.core.internal.Messages;
import org.eclipse.m2e.core.internal.embedder.MavenImpl;
import org.eclipse.m2e.core.internal.jobs.IBackgroundProcessingQueue;
Expand Down Expand Up @@ -199,7 +199,7 @@ public void resourceChanged(IResourceChangeEvent event) {

if(IResourceChangeEvent.PRE_CLOSE == type || IResourceChangeEvent.PRE_DELETE == type) {
IProject project = (IProject) event.getResource();
if(isMavenProject(project)) {
if(MavenPlugin.isMavenProject(project)) {
queue(new MavenUpdateRequest(project, offline, forceDependencyUpdate));
}
} else {
Expand All @@ -211,7 +211,7 @@ public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta[] projectDeltas = delta.getAffectedChildren();
for(IResourceDelta projectDelta : projectDeltas) {
IProject project = (IProject) projectDelta.getResource();
if(!isMavenProject(project)) {
if(!MavenPlugin.isMavenProject(project)) {
continue;
}
//Bug 436679: queue update request only for reopened projects.
Expand Down Expand Up @@ -253,12 +253,4 @@ public boolean isEmpty() {
}
}

private boolean isMavenProject(IProject project) {
try {
return project != null && project.isAccessible() && project.hasNature(IMavenConstants.NATURE_ID);
} catch(CoreException ex) {
log.error(ex.getMessage(), ex);
}
return false;
}
}
3 changes: 2 additions & 1 deletion org.eclipse.m2e.jdt.ui/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Require-Bundle: org.eclipse.m2e.jdt;bundle-version="[2.0.0,3.0.0)",
org.eclipse.m2e.maven.runtime;bundle-version="[3.8.6,4.0.0)",
org.eclipse.m2e.core;bundle-version="[2.0.0,3.0.0)",
org.eclipse.m2e.core.ui;bundle-version="[2.0.0,3.0.0)",
org.eclipse.ui
org.eclipse.ui,
org.eclipse.jface.text;bundle-version="3.21.0"
Bundle-ActivationPolicy: lazy
Bundle-Activator: org.eclipse.m2e.jdt.ui.internal.MavenJdtUiPlugin
Bundle-RequiredExecutionEnvironment: JavaSE-17
Expand Down
8 changes: 8 additions & 0 deletions org.eclipse.m2e.jdt.ui/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -335,5 +335,13 @@
id="org.eclipse.m2e.jdt.keyword"
label="java">
</keyword>
</extension>
<extension
point="org.eclipse.jdt.ui.quickFixProcessors">
<quickFixProcessor
class="org.eclipse.m2e.jdt.ui.internal.AddDependencyQuickFixProcessor"
id="org.eclipse.m2e.jdt.ui.quickFixProcessor1"
name="M2Eclipse">
</quickFixProcessor>
</extension>
</fragment>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*******************************************************************************
* Copyright (c) 2022 Christoph Läubrich and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/

package org.eclipse.m2e.jdt.ui.internal;

import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.DEPENDENCIES;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.getChild;
import static org.eclipse.m2e.core.ui.internal.editing.PomEdits.performOnDOMDocument;

import java.io.IOException;

import org.w3c.dom.Element;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;

import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.ui.internal.editing.PomEdits.Operation;
import org.eclipse.m2e.core.ui.internal.editing.PomEdits.OperationTuple;
import org.eclipse.m2e.core.ui.internal.editing.PomHelper;


@SuppressWarnings("restriction")
public class AddDependencyJavaCompletionProposal implements IJavaCompletionProposal {

private ArtifactKey artifactKey;

private IFile pomfile;

public AddDependencyJavaCompletionProposal(ArtifactKey artifactKey, IFile pomfile) {
this.artifactKey = artifactKey;
this.pomfile = pomfile;
}

public void apply(IDocument javaDocument) {
try {
performOnDOMDocument(new OperationTuple(pomfile, (Operation) document -> {
Element depsEl = getChild(document.getDocumentElement(), DEPENDENCIES);
PomHelper.addOrUpdateDependency(depsEl, artifactKey.groupId(), artifactKey.artifactId(), artifactKey.version(),
null, "compile", null);
}));
} catch(IOException ex) {
MavenJdtUiPlugin.getDefault().getLog().error("Can't modify file " + pomfile, ex);
} catch(CoreException ex) {
MavenJdtUiPlugin.getDefault().getLog().log(ex.getStatus());
}

}

public Point getSelection(IDocument document) {
return null;
}

public String getAdditionalProposalInfo() {
return null;
}

public String getDisplayString() {
return "Add " + artifactKey.groupId() + ":" + artifactKey.artifactId() + ":" + artifactKey.version()
+ " as dependency";
}

public Image getImage() {
return MavenJdtUiPlugin.getDefault().getImageRegistry().get(MavenJdtUiPlugin.M2E_ICON);
}

public IContextInformation getContextInformation() {
return null;
}

public int getRelevance() {
return 100;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*******************************************************************************
* Copyright (c) 2022 Christoph Läubrich and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/

package org.eclipse.m2e.jdt.ui.internal;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.IQuickFixProcessor;

import org.eclipse.m2e.core.MavenPlugin;
import org.eclipse.m2e.core.embedder.ArtifactKey;
import org.eclipse.m2e.core.project.IMavenProjectFacade;


public class AddDependencyQuickFixProcessor implements IQuickFixProcessor {

public boolean hasCorrections(ICompilationUnit unit, int problemId) {
switch(problemId) {
case IProblem.UndefinedName:
case IProblem.ImportNotFound:
case IProblem.UndefinedType:
case IProblem.UnresolvedVariable:
case IProblem.MissingTypeInMethod:
case IProblem.MissingTypeInConstructor:
IJavaElement parent = unit.getParent();
if(parent != null) {
IJavaProject project = parent.getJavaProject();
if(project != null) {
return MavenPlugin.isMavenProject(project.getProject());
}
}
}
return false;
}

public IJavaCompletionProposal[] getCorrections(IInvocationContext context, IProblemLocation[] locations)
throws CoreException {
Map<IMavenProjectFacade, Set<ArtifactKey>> possibleKeys = new HashMap<>();
for(IProblemLocation location : locations) {
switch(location.getProblemId()) {
case IProblem.ImportNotFound:
case IProblem.UndefinedName:
case IProblem.UndefinedType:
case IProblem.UnresolvedVariable:
case IProblem.MissingTypeInMethod:
case IProblem.MissingTypeInConstructor:
handleImportNotFound(context, location, possibleKeys);
}
}
return possibleKeys.entrySet().stream()
.flatMap(entry -> entry.getValue().stream()
.map(key -> new AddDependencyJavaCompletionProposal(key, entry.getKey().getPom())))
.toArray(IJavaCompletionProposal[]::new);
}

private void handleImportNotFound(IInvocationContext context, IProblemLocation problemLocation,
Map<IMavenProjectFacade, Set<ArtifactKey>> possibleKeys) throws CoreException {
CompilationUnit cu = context.getASTRoot();
ASTNode selectedNode = problemLocation.getCoveringNode(cu);
if(selectedNode != null) {
String className = getClassName(selectedNode);
if(className != null) {
IMavenProjectFacade currentFacade = Adapters.adapt(cu.getJavaElement().getJavaProject(),
IMavenProjectFacade.class);
if(currentFacade != null) {
Set<ArtifactKey> artifacts = findMatchingArtifacts(className, currentFacade);
if(!artifacts.isEmpty()) {
possibleKeys.computeIfAbsent(currentFacade, x -> new HashSet<>()).addAll(artifacts);
}
}
}
}
}

static Set<ArtifactKey> findMatchingArtifacts(String className, IMavenProjectFacade currentFacade)
throws CoreException {
Set<ArtifactKey> possibleKey = new HashSet<ArtifactKey>();
SearchPattern typePattern = SearchPattern.createPattern(className, IJavaSearchConstants.TYPE,
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);
IJavaSearchScope workspaceScope = SearchEngine.createWorkspaceScope();
SearchEngine searchEngine = new SearchEngine();
SearchRequestor requestor = new SearchRequestor() {

@Override
public void acceptSearchMatch(SearchMatch aMatch) {
Object element = aMatch.getElement();
if(element instanceof IType) {
IType type = (IType) element;
IMavenProjectFacade facade = Adapters.adapt(type.getJavaProject(), IMavenProjectFacade.class);
if(facade != null) {
ArtifactKey artifactKey = facade.getArtifactKey();
if(!artifactKey.equals(currentFacade.getArtifactKey())) {
possibleKey.add(artifactKey);
}
}
}
}
};
searchEngine.search(typePattern, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()},
workspaceScope, requestor, null);
return possibleKey;
}

private String getClassName(ASTNode selectedNode) {
String className = null;
if(selectedNode instanceof Name) {
ITypeBinding typeBinding = ((Name) selectedNode).resolveTypeBinding();
if(typeBinding != null) {
className = typeBinding.getBinaryName();
}
if(className == null && selectedNode instanceof SimpleName) { // fallback if the type cannot be resolved
className = ((SimpleName) selectedNode).getIdentifier();
}
}
return className;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@

package org.eclipse.m2e.jdt.ui.internal;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.ui.plugin.AbstractUIPlugin;


public class MavenJdtUiPlugin extends AbstractUIPlugin {

public static final String M2E_ICON = "M2E_ICON";

public static final String PLUGIN_ID = "org.eclipse.m2e.jdt.ui"; //$NON-NLS-1$

private static MavenJdtUiPlugin instance;
Expand All @@ -25,4 +29,8 @@ public MavenJdtUiPlugin() {
public static MavenJdtUiPlugin getDefault() {
return instance;
}

protected void initializeImageRegistry(ImageRegistry reg) {
reg.put(M2E_ICON, ImageDescriptor.createFromURL(MavenJdtUiPlugin.class.getResource("/icons/m2.gif")));
}
}
5 changes: 5 additions & 0 deletions org.eclipse.m2e.jdt/.project
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ds.core.builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dsVersion=V1_3
eclipse.preferences.version=1
enabled=true
generateBundleActivationPolicyLazy=true
path=OSGI-INF
validationErrorLevel=error
validationErrorLevel.missingImplicitUnbindMethod=error
1 change: 1 addition & 0 deletions org.eclipse.m2e.jdt/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Require-Bundle: org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.m2e.maven.runtime;bundle-version="[3.8.6,4.0.0)",
org.eclipse.m2e.core;bundle-version="[2.0.0,3.0.0)"
Service-Component: OSGI-INF/org.eclipse.m2e.jdt.internal.JavaProjectAdapterFactory.xml
Bundle-ActivationPolicy: lazy
Bundle-Activator: org.eclipse.m2e.jdt.MavenJdtPlugin
Bundle-RequiredExecutionEnvironment: JavaSE-17
Expand Down
1 change: 1 addition & 0 deletions org.eclipse.m2e.jdt/OSGI-INF/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/org.eclipse.m2e.*.xml
3 changes: 2 additions & 1 deletion org.eclipse.m2e.jdt/build.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ bin.includes = META-INF/,\
.,\
about.html,\
lifecycle-mapping-metadata.xml,\
schema/
schema/,\
OSGI-INF/org.eclipse.m2e.jdt.internal.JavaProjectAdapterFactory.xml
source.. = src/
output.. = bin/
src.includes = about.html
Loading

0 comments on commit a743960

Please sign in to comment.