Skip to content

Commit

Permalink
Completion should also propose other packaging types available in Bui…
Browse files Browse the repository at this point in the history
…ld extensions #267
  • Loading branch information
vrubezhny committed Apr 12, 2023
1 parent 96e0f51 commit 5d7685e
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat Inc. and others.
* Copyright (c) 2021-2023 Red Hat Inc. and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand All @@ -18,6 +18,7 @@ private DOMConstants() {

}

public static final String PACKAGING_ELT = "packaging";
public static final String PROJECT_ELT = "project";
public static final String MODULE_ELT = "module";
public static final String RELATIVE_PATH_ELT = "relativePath";
Expand Down Expand Up @@ -59,4 +60,12 @@ private DOMConstants() {
public static final String FILE_ELT = "file";
public static final String EXISTS_ELT = "exists";
public static final String MISSING_ELT = "missing";

// Packaging values
public static final String PACKAGING_TYPE_JAR ="jar";
public static final String PACKAGING_TYPE_WAR = "war";
public static final String PACKAGING_TYPE_EJB = "ejb";
public static final String PACKAGING_TYPE_EAR = "ear";
public static final String PACKAGING_TYPE_POM = "pom";
public static final String PACKAGING_TYPE_MAVEN_PLUGIN = "maven-plugin";
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020-2022 Red Hat Inc. and others.
* Copyright (c) 2020-2023 Red Hat Inc. and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand Down Expand Up @@ -202,14 +202,18 @@ public InputStream getInputStream() throws IOException {
}

private ProjectBuildingRequest newProjectBuildingRequest() {
return newProjectBuildingRequest(true);
}

private ProjectBuildingRequest newProjectBuildingRequest(boolean resolveDependencies) {
ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
request.setSystemProperties(mavenRequest.getSystemProperties());
request.setLocalRepository(mavenRequest.getLocalRepository());
request.setRemoteRepositories(mavenRequest.getRemoteRepositories());
request.setPluginArtifactRepositories(mavenRequest.getPluginArtifactRepositories());
// TODO more to transfer from mavenRequest to ProjectBuildingRequest?
request.setRepositorySession(lemminxMavenPlugin.getMavenSession().getRepositorySession());
request.setResolveDependencies(true);
request.setResolveDependencies(resolveDependencies);
return request;
}

Expand All @@ -234,8 +238,12 @@ public Collection<MavenProject> getProjects() {
}

public MavenProject getSnapshotProject(DOMDocument document, String profileId) {
return getSnapshotProject(document, profileId, true);
}

public MavenProject getSnapshotProject(DOMDocument document, String profileId, boolean resolveDependencies) {
// it would be nice to directly rebuild from Model instead of reparsing text
ProjectBuildingRequest request = newProjectBuildingRequest();
ProjectBuildingRequest request = newProjectBuildingRequest(resolveDependencies);
if (profileId != null) {
request.setActiveProfileIds(List.of(profileId));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static org.eclipse.lemminx.extensions.maven.DOMConstants.MISSING_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.MODULE_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.OUTPUT_DIRECTORY_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PACKAGING_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PARENT_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PHASE_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PLUGINS_ELT;
Expand All @@ -35,6 +36,13 @@
import static org.eclipse.lemminx.extensions.maven.DOMConstants.TEST_SOURCE_DIRECTORY_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.VERSION_ELT;

import static org.eclipse.lemminx.extensions.maven.DOMConstants.PACKAGING_TYPE_JAR;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PACKAGING_TYPE_WAR;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PACKAGING_TYPE_EJB;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PACKAGING_TYPE_EAR;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PACKAGING_TYPE_POM;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PACKAGING_TYPE_MAVEN_PLUGIN;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
Expand All @@ -45,7 +53,9 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -58,17 +68,24 @@
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.maven.Maven;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.PluginDescriptorParsingException;
Expand All @@ -77,6 +94,8 @@
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.artifact.DefaultArtifactType;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
Expand Down Expand Up @@ -106,6 +125,10 @@
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MavenCompletionParticipant extends CompletionParticipantAdapter {

Expand All @@ -117,7 +140,15 @@ public class MavenCompletionParticipant extends CompletionParticipantAdapter {
private static final String FILE_TYPE = "File";
private static final String STRING_TYPE = "File";
private static final String DIRECTORY_STRING_LC = "directory";


// Extension packaging types: components.xml path and element names
private static final String COMPONENTS_PATH = "META-INF/plexus/components.xml";
private static final String JAR_EXT = ".jar";
private static final String COMPONENTS_COMPONENT_ELT = "component";
private static final String COMPONENTS_ROLE_ELT = "role";
private static final String COMPONENTS_CONFIGURATION_ELT = "configuration";
private static final String COMPONENTS_TYPE_ELT = "type";

static interface GAVInsertionStrategy {
/**
* set current element value and add siblings as addition textEdits
Expand Down Expand Up @@ -361,6 +392,9 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons
case GOAL_ELT:
collectGoals(request).forEach(response::addCompletionItem);
break;
case PACKAGING_ELT:
collectPackaging(request).forEach(response::addCompletionItem);
break;
default:
Set<MojoParameter> parameters = MavenPluginUtils.collectPluginConfigurationMojoParameters(request, plugin)
.stream().filter(p -> p.name.equals(parent.getLocalName()))
Expand Down Expand Up @@ -490,6 +524,95 @@ private Collection<CompletionItem> collectGoals(ICompletionRequest request) {
return Collections.emptySet();
}

private Collection<CompletionItem> collectPackaging(ICompletionRequest request) {
Set<String> packagingTypes = new LinkedHashSet<>();
packagingTypes.add(PACKAGING_TYPE_JAR);
packagingTypes.add(PACKAGING_TYPE_WAR);
packagingTypes.add(PACKAGING_TYPE_EAR);
packagingTypes.add(PACKAGING_TYPE_EJB);
packagingTypes.add(PACKAGING_TYPE_POM);
packagingTypes.add(PACKAGING_TYPE_MAVEN_PLUGIN);

// dynamically load available packaging types from build plugins
updateAvailablePackagingTypes(packagingTypes, request);

return packagingTypes.stream().map(type -> {
try {
CompletionItem item = toTextCompletionItem(request, type);
item.setDocumentation("Packagng Type: " + (type != null ? type : "unknown"));
item.setKind(CompletionItemKind.Value);
item.setSortText(type != null ? type : "zzz");
return item;
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
return toErrorCompletionItem(e);
}
}).collect(Collectors.toList());
}

private void updateAvailablePackagingTypes(Set<String> packagingTypes, ICompletionRequest request) {
MavenProject project = plugin.getProjectCache().getSnapshotProject(request.getXMLDocument(), null, false);
if (project == null) {
return;
}

for (Plugin plugin : project.getBuildPlugins()) {
if (plugin.isExtensions()) {
Artifact artifact = new DefaultArtifact(
plugin.getGroupId(), plugin.getArtifactId(),
null, null, plugin.getVersion(),
new HashMap<String, String>(),
new DefaultArtifactType("maven-plugin"));
addPluginPackagingTypes(packagingTypes, artifact);
}
}
}

/**
* Parses the plugin's META-INF/plexus/components.xml file for available
* packaging types
*
* @param packagingTypes Set of packaging types that this method will add to
* @param artifact The artifact of the build plugin
* @apiNote If any exceptions occur during this method, such as an XML parsing
* exception or file not found, this method will immediately stop. It
* is assumed that there is something wrong with the user's project or
* repository setup which prevents this method from completing.
*/
private void addPluginPackagingTypes(Set<String> packagingTypes, Artifact artifact) {
File artifactFile = plugin.getLocalRepositorySearcher().findLocalFile(artifact);
if (artifactFile == null) {
return;
}

try (JarFile jarFile = new JarFile(artifactFile.getAbsoluteFile() + JAR_EXT)) {
DocumentBuilder db = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder();
JarEntry componentsxml = jarFile.getJarEntry(COMPONENTS_PATH);
if (componentsxml != null) {
Document doc = db.parse(jarFile.getInputStream(componentsxml));
doc.getDocumentElement().normalize();
NodeList components = doc.getElementsByTagName(COMPONENTS_COMPONENT_ELT);
for (int i = 0; i < components.getLength(); i++) {
Node component = components.item(i);
if (component.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) component;
String role = element.getElementsByTagName(COMPONENTS_ROLE_ELT).item(0).getTextContent();
if (ArtifactHandler.ROLE.equals(role)) {
Node config = element.getElementsByTagName(COMPONENTS_CONFIGURATION_ELT).item(0);
if (config.getNodeType() == Node.ELEMENT_NODE) {
Element configEl = (Element) config;
String name = configEl.getElementsByTagName(COMPONENTS_TYPE_ELT).item(0).getTextContent();
packagingTypes.add(name);
}
}
}
}
}
} catch (Exception e) {
// Broken XML, file not found, etc. Can't add packaging types.
}
}

private CompletionItem toGAVCompletionItem(ArtifactWithDescription artifactInfo, ICompletionRequest request,
GAVInsertionStrategy strategy) {
boolean hasGroupIdSet = DOMUtils.findChildElementText(request.getParentElement().getParentElement(), GROUP_ID_ELT).isPresent()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021-2022 Red Hat Inc. and others.
* Copyright (c) 2021-2023 Red Hat Inc. and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand Down Expand Up @@ -311,4 +311,29 @@ public void testPluginParameterCompletion() throws Exception {
"<target>$0</target>"),
"<target>$0</target>", null));
}

@Test
public void testPackagingyCompletion() throws Exception {
String pom = """
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>pom-with-packaging</artifactId>
<groupId>org.eclipse.lemminx.extention.maven.tests</groupId>
<version>0.1.0</version>
<packaging>|</packaging>
</project>
""";
testCompletionFor(pom, null, "file:///pom.xml", null, //
c("jar", te(10, 13, 10, 13, "jar"), "jar"),
c("war", te(10, 13, 10, 13, "war"), "war"),
c("ear", te(10, 13, 10, 13, "ear"), "ear"),
c("ejb", te(10, 13, 10, 13, "ejb"), "ejb"),
c("pom", te(10, 13, 10, 13, "pom"), "pom"),
c("maven-plugin", te(10, 13, 10, 13, "maven-plugin"), "maven-plugin"));
}
}

0 comments on commit 5d7685e

Please sign in to comment.