/******************************************************************************* * Copyright (c) 2008-2010 Sonatype, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sonatype, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.m2e.editor.xml; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.templates.Template; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.graphics.Image; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.model.IWorkbenchAdapter; import org.codehaus.plexus.interpolation.InterpolationException; import org.codehaus.plexus.interpolation.PrefixedObjectValueSource; import org.codehaus.plexus.interpolation.PropertiesBasedValueSource; import org.codehaus.plexus.interpolation.RegexBasedInterpolator; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginManagement; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.project.MavenProject; import org.eclipse.m2e.core.embedder.ArtifactKey; import org.eclipse.m2e.core.ui.internal.M2EUIPluginActivator; import org.eclipse.m2e.core.ui.internal.search.util.ArtifactInfo; import org.eclipse.m2e.core.ui.internal.search.util.Packaging; import org.eclipse.m2e.core.ui.internal.search.util.SearchEngine; import org.eclipse.m2e.editor.xml.internal.Messages; import org.eclipse.m2e.editor.xml.internal.XmlUtils; import org.eclipse.m2e.editor.xml.internal.mojo.MojoParameterMetadataProvider; import org.eclipse.m2e.editor.xml.mojo.IMojoParameterMetadataProvider; import org.eclipse.m2e.editor.xml.mojo.MojoParameter; /** * Context types. * * @author Lukas Krecan * @author Eugene Kuleshov */ @SuppressWarnings("restriction") public enum PomTemplateContext { UNKNOWN("unknown"), // //$NON-NLS-1$ DOCUMENT("#document"), // //$NON-NLS-1$ PROJECT("project"), // //$NON-NLS-1$ BUILD("build"), // //$NON-NLS-1$ PARENT("parent"), // //$NON-NLS-1$ RELATIVE_PATH("relativePath"), // //$NON-NLS-1$ DEPENDENCIES("dependencies"), // //$NON-NLS-1$ DEPENDENCY_MANAGEMENT("dependencyManagement"), // //$NON-NLS-1$ EXCLUSIONS("exclusions"), // //$NON-NLS-1$ PLUGINS("plugins"), // //$NON-NLS-1$ PLUGIN("plugin"), // //$NON-NLS-1$ PLUGIN_MANAGEMENT("pluginManagement"), // //$NON-NLS-1$ EXECUTIONS("executions"), // //$NON-NLS-1$ PROFILES("profiles"), // //$NON-NLS-1$ PROFILE("profile"), // //$NON-NLS-1$ REPOSITORIES("repositories"), // //$NON-NLS-1$ PROPERTIES("properties") { // //$NON-NLS-1$ protected void addTemplates(MavenProject project, IProject eclipsePrj, Collection<Template> templates, Node currentNode, String prefix) { // propose overridable properties Set<String> currentNodeProps = new HashSet<>(); NodeList nodes = currentNode.getChildNodes(); for(int i = 0; i < nodes.getLength(); i++ ) { currentNodeProps.add(nodes.item(i).getNodeName()); } if(project != null) { Properties props = project.getProperties(); if(props != null) { for(Map.Entry<Object, Object> e : props.entrySet()) { String name = e.getKey().toString(); String value = e.getValue().toString(); if(currentNodeProps.contains(name)) continue; if(!name.startsWith(prefix)) continue; String template = "<" + name + ">${" + value + "}</" + name + ">"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ String desc = NLS.bind(Messages.PomTemplateContext_property_override, value); templates.add(new PomTemplate(name, desc, getContextTypeId(), template, false).image(MvnImages.IMG_PROPERTY) .relevance(2000)); } } } } }, CONFIGURATION("configuration") { //$NON-NLS-1$ public boolean handlesSubtree() { return true; } @Override protected void addTemplates(final MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) throws CoreException { // find configuration ancestor List<String> pathElements = new ArrayList<String>(); String configImpl = null; Element configNode = (Element) node; while(configNode != null && !getNodeName().equals(configNode.getNodeName())) { String impl = configNode.getAttribute("implementation"); if(impl != null && !impl.trim().isEmpty()) { configImpl = impl; } if(configImpl == null) { pathElements.add(configNode.getNodeName()); } configNode = (Element) configNode.getParentNode(); } if(configNode == null) { return; } Collections.reverse(pathElements); String[] configPath = pathElements.toArray(new String[pathElements.size()]); Node configContainer = null; Node pluginSubNode = configNode; String containerName = pluginSubNode.getParentNode().getNodeName(); if("execution".equals(containerName) //$NON-NLS-1$ || "reportSet".equals(containerName)) { //$NON-NLS-1$ configContainer = pluginSubNode.getParentNode(); pluginSubNode = configContainer.getParentNode(); } String groupId = getGroupId(pluginSubNode); if(groupId == null) { groupId = "org.apache.maven.plugins"; // TODO support other default groups //$NON-NLS-1$ } String artifactId = getArtifactId(pluginSubNode); String version = extractVersion(project, eclipseprj, getVersion(pluginSubNode), groupId, artifactId, EXTRACT_STRATEGY_PLUGIN | EXTRACT_STRATEGY_SEARCH); if(version == null) { return; } // collect used mojo goals final Set<String> usedMojos = new HashSet<>(); if("execution".equals(containerName)) { //$NON-NLS-1$ Node goalsNode = getChildWithName(configContainer, "goals"); //$NON-NLS-1$ if(goalsNode != null) { NodeList children = goalsNode.getChildNodes(); int l = children.getLength(); for(int i = 0; i < l; i++ ) { Node goalNode = children.item(i); if("goal".equals(goalNode.getNodeName())) { //$NON-NLS-1$ String goal = XmlUtils.getTextValue(goalNode); if(goal != null && !goal.isEmpty()) { usedMojos.add(goal); } } } } } ArtifactKey pluginKey = new ArtifactKey(groupId, artifactId, version, null); IMojoParameterMetadataProvider prov = new MojoParameterMetadataProvider(); MojoParameter result; if(configImpl != null) { result = prov.getClassConfiguration(pluginKey, configImpl); } else if(usedMojos.isEmpty()) { result = prov.getMojoConfiguration(pluginKey); } else { result = prov.getMojoConfiguration(pluginKey, usedMojos); } MojoParameter param = result.getContainer(configPath); if(param != null) { List<MojoParameter> nestedParameters = param.getNestedParameters(); for(MojoParameter parameter : nestedParameters) { String name = parameter.getName(); if(name.startsWith(prefix)) { String text = NLS.bind(Messages.PomTemplateContext_param, parameter.isRequired(), parameter.getType()); String expression = parameter.getExpression(); if(expression != null) { text += NLS.bind(Messages.PomTemplateContext_param_expr, expression); } String defaultValue = parameter.getDefaultValue(); if(defaultValue != null) { text += NLS.bind(Messages.PomTemplateContext_param_def, defaultValue); } String description = parameter.getDescription(); if(description != null) { String desc = description.trim(); text += desc.startsWith("<p>") ? desc : "<br>" + desc; //$NON-NLS-1$ //$NON-NLS-2$ } proposals.add(new PomTemplate(name, text, getContextTypeId(), // "<" + name + ">${cursor}</" + name + ">", false) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ .image(MvnImages.IMG_PARAMETER).relevance(1900)); } } if(param.isMap()) { if(prefix != null && !prefix.trim().isEmpty()) { proposals.add(new PomTemplate(NLS.bind(Messages.PomTemplateContext_insertParameter, prefix), "", //$NON-NLS-1$ getContextTypeId(), "<" + prefix + ">${cursor}</" + prefix + ">", true) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ .image(MvnImages.IMG_PARAMETER).relevance(1500)); } } if(nestedParameters.size() == 1) { MojoParameter nestedParam = nestedParameters.get(0); if(nestedParam.isMultiple()) { boolean containsFiles = File.class.getSimpleName().equals(nestedParam.getType()) || PomTemplateContext.fromNodeName(nestedParam.getName()).handlesFiles(); if(containsFiles) { addFileTemplates(project, eclipseprj, proposals, node, prefix, param.getName().toLowerCase().endsWith("directory"), nestedParam.getName()); } } } boolean containsFiles = File.class.getSimpleName().equals(param.getType()) || PomTemplateContext.fromNodeName(param.getName()).handlesFiles(); if(containsFiles) { addFileTemplates(project, eclipseprj, proposals, node, prefix, param.getName().toLowerCase().endsWith("directory"), null); } } } }, GROUP_ID("groupId") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) throws CoreException { for(String groupId : getSearchEngine(eclipseprj).findGroupIds(prefix, getPackaging(node), getContainingArtifact(node))) { checkAndAdd(proposals, prefix, groupId); } } }, ARTIFACT_ID("artifactId") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) throws CoreException { String groupId = getGroupId(node); //#MNGECLIPSE-1832 if((groupId == null || groupId.trim().length() == 0) && "plugin".equals(node.getParentNode().getNodeName())) { groupId = "org.apache.maven.plugins"; //$NON-NLS-1$ } if(groupId != null) { for(String artifactId : getSearchEngine(eclipseprj).findArtifactIds(groupId, prefix, getPackaging(node), getContainingArtifact(node))) { checkAndAdd(proposals, prefix, artifactId, groupId + ":" + artifactId); } } } }, VERSION("version") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) throws CoreException { String groupId = getGroupId(node); //#MNGECLIPSE-1832 if((groupId == null || groupId.trim().length() == 0) && "plugin".equals(node.getParentNode().getNodeName())) { groupId = "org.apache.maven.plugins"; //$NON-NLS-1$ } String artifactId = getArtifactId(node); if(groupId != null && artifactId != null) { for(String version : getSearchEngine(eclipseprj).findVersions(groupId, artifactId, prefix, getPackaging(node))) { checkAndAdd(proposals, prefix, version, groupId + ":" + artifactId + ":" + version); } } //mkleint: this concept that all versions out there are equal is questionable.. if("dependency".equals(node.getParentNode().getNodeName())) { //$NON-NLS-1$ //see if we can complete the properties ending with .version List<String> keys = new ArrayList<String>(); String contextTypeId = getContextTypeId(); MavenProject mvn = project; if(mvn != null) { //if groupid is the same, suggest ${project.version} if(groupId != null && groupId.equals(mvn.getGroupId())) { proposals.add(new Template("${project.version}", Messages.PomTemplateContext_project_version_hint, //$NON-NLS-1$ contextTypeId, "$${project.version}", false)); //$NON-NLS-3$ } Properties props = mvn.getProperties(); if(props != null) { for(Object key : props.keySet()) { //only add the properties following the .version convention if(key.toString().endsWith(".version") || key.toString().endsWith("Version")) { //$NON-NLS-1$ //$NON-NLS-2$ keys.add(key.toString()); } } //sort just properties Collections.sort(keys); if(keys.size() > 0) { for(String key : keys) { String expr = "${" + key + "}"; //$NON-NLS-1$ //$NON-NLS-2$ proposals.add(new Template(expr, Messages.PomTemplateContext_expression_description, contextTypeId, "$" + expr, false)); //$NON-NLS-2$ //$NON-NLS-1$ } } } } else { //if we don't have the maven facade, it means the pom is probably broken. //all we can do is to try guess the groupid and come up with the project.version proposal eventually Element root = node.getOwnerDocument().getDocumentElement(); if(root != null && "project".equals(root.getNodeName())) {//$NON-NLS-1$ String currentgroupid = XmlUtils.getTextValue(XmlUtils.findChild(root, "groupId"));//$NON-NLS-1$ if(currentgroupid == null) { Element parEl = XmlUtils.findChild(root, "parent");//$NON-NLS-1$ if(parEl != null) { currentgroupid = XmlUtils.getTextValue(XmlUtils.findChild(parEl, "groupId"));//$NON-NLS-1$ } } if(groupId != null && groupId.equals(currentgroupid)) { proposals.add(new Template("${project.version}", Messages.PomTemplateContext_project_version_hint, //$NON-NLS-1$ contextTypeId, "$${project.version}", false)); //$NON-NLS-3$ } } } } } }, CLASSIFIER("classifier") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) throws CoreException { String groupId = getGroupId(node); String artifactId = getArtifactId(node); String version = getVersion(node); if(groupId != null && artifactId != null && version != null) { for(String classifier : getSearchEngine(eclipseprj).findClassifiers(groupId, artifactId, version, prefix, getPackaging(node))) { checkAndAdd(proposals, prefix, classifier, groupId + ":" + artifactId + ":" + version + ":" + classifier); } } } }, TYPE("type") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) throws CoreException { String groupId = getGroupId(node); String artifactId = getArtifactId(node); String version = getVersion(node); if(groupId != null && artifactId != null && version != null) { for(String type : getSearchEngine(eclipseprj).findTypes(groupId, artifactId, version, prefix, getPackaging(node))) { checkAndAdd(proposals, prefix, type, groupId + ":" + artifactId + ":" + version + ":" + type); } } } }, PACKAGING("packaging") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) { // TODO only show "pom" packaging in root section checkAndAdd(proposals, prefix, "pom"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "jar"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "war"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "ear"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "ejb"); //$NON-NLS-1$ // checkAndAdd(proposals, prefix, "eclipse-plugin"); //$NON-NLS-1$ // checkAndAdd(proposals, prefix, "eclipse-feature"); //$NON-NLS-1$ // checkAndAdd(proposals, prefix, "eclipse-update-site"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "maven-plugin"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "maven-archetype"); //$NON-NLS-1$ } }, SCOPE("scope") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) { checkAndAdd(proposals, prefix, "compile"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "test"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "provided"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "runtime"); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "system"); //$NON-NLS-1$ if(getAncestor(node, "dependency", "dependencies", "dependencyManagement") != null) {// $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ checkAndAdd(proposals, prefix, "import"); //$NON-NLS-1$ } } }, SYSTEM_PATH("systemPath"), //$NON-NLS-1$ PHASE("phase") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) { // TODO the following list should be derived from the packaging handler (the actual lifecycle) // Clean Lifecycle checkAndAdd(proposals, prefix, "pre-clean", Messages.PomTemplateContext_preclean); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "clean", Messages.PomTemplateContext_clean); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "post-clean", Messages.PomTemplateContext_postclean); //$NON-NLS-1$ // Default Lifecycle checkAndAdd(proposals, prefix, "validate", Messages.PomTemplateContext_validate); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "generate-sources", Messages.PomTemplateContext_generatesources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "process-sources", Messages.PomTemplateContext_processsources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "generate-resources", Messages.PomTemplateContext_generateresources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "process-resources", Messages.PomTemplateContext_processresources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "compile", Messages.PomTemplateContext_compile); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "process-classes", Messages.PomTemplateContext_processclasses); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "generate-test-sources", Messages.PomTemplateContext_generatetestsources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "process-test-sources", Messages.PomTemplateContext_processtestsources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "generate-test-resources", Messages.PomTemplateContext_generatetestresources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "process-test-resources", Messages.PomTemplateContext_processtestresources); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "test-compile", Messages.PomTemplateContext_testcompile); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "process-test-classes", Messages.PomTemplateContext_processtestclasses); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "test", Messages.PomTemplateContext_test); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "prepare-package", Messages.PomTemplateContext_preparepackage); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "package", Messages.PomTemplateContext_package); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "pre-integration-test", Messages.PomTemplateContext_preintegrationtest); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "integration-test", Messages.PomTemplateContext_integrationtest); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "post-integration-test", Messages.PomTemplateContext_postintegrationtest); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "verify", Messages.PomTemplateContext_verify); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "install", Messages.PomTemplateContext_install); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "deploy", Messages.PomTemplateContext_deploy); //$NON-NLS-1$ // Site Lifecycle checkAndAdd(proposals, prefix, "pre-site", Messages.PomTemplateContext_presite); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "site", Messages.PomTemplateContext_site); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "post-site", Messages.PomTemplateContext_postsite); //$NON-NLS-1$ checkAndAdd(proposals, prefix, "site-deploy", Messages.PomTemplateContext_sitedeploy); //$NON-NLS-1$ } }, GOAL("goal") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) throws CoreException { if(!"goals".equals(node.getParentNode().getNodeName())) { //$NON-NLS-1$ return; } node = node.getParentNode(); if(!"execution".equals(node.getParentNode().getNodeName())) { //$NON-NLS-1$ return; } node = node.getParentNode(); if(!"executions".equals(node.getParentNode().getNodeName())) { //$NON-NLS-1$ return; } node = node.getParentNode(); String groupId = getGroupId(node); if(groupId == null) { groupId = "org.apache.maven.plugins"; //$NON-NLS-1$ } String artifactId = getArtifactId(node); String version = extractVersion(project, eclipseprj, getVersion(node), groupId, artifactId, EXTRACT_STRATEGY_PLUGIN | EXTRACT_STRATEGY_SEARCH); if(version == null) { return; } PluginDescriptor descriptor = PomTemplateContextUtil.INSTANCE.getPluginDescriptor(groupId, artifactId, version); if(descriptor != null) { List<MojoDescriptor> mojos = descriptor.getMojos(); if(mojos != null) { for(MojoDescriptor mojo : mojos) { checkAndAdd(proposals, prefix, mojo.getGoal(), mojo.getDescription()); } } } } }, MODULES("modules") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) { addModuleTemplates(project, eclipseprj, proposals, node, prefix, true); } }, MODULE("module") { //$NON-NLS-1$ @Override public void addTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix) { addModuleTemplates(project, eclipseprj, proposals, node, prefix, false); } }, SOURCEDIRECTORY("sourceDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ SCRIPTSOURCEDIRECTORY("scriptSourceDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ TESTSOURCEDIRECTORY("testSourceDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ OUTPUTDIRECTORY("outputDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ TESTOUTPUTDIRECTORY("testOutputDirectory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ DIRECTORY("directory", "file"), //$NON-NLS-1$ //$NON-NLS-2$ FILTER("filter", "file"), //$NON-NLS-1$ //$NON-NLS-2$ LICENSES("licenses"); //$NON-NLS-1$ private static final Logger log = LoggerFactory.getLogger(PomTemplateContext.class); private static final String PREFIX = MvnIndexPlugin.PLUGIN_ID + ".templates.contextType."; //$NON-NLS-1$ private final String nodeName; private final String contextSuffix; private PomTemplateContext(String nodeName) { this(nodeName, nodeName); } private PomTemplateContext(String nodeName, String contextSuffix) { this.nodeName = nodeName; this.contextSuffix = contextSuffix; } public boolean handlesSubtree() { return false; } public boolean handlesFiles() { return "file".equals(contextSuffix); //$NON-NLS-1$ } /** * Return templates depending on the context type. */ public Template[] getTemplates(MavenProject project, IProject eclipsePrj, Node node, String prefix) { Collection<Template> templates = new ArrayList<Template>(); try { addTemplates(project, eclipsePrj, templates, node, prefix); } catch(CoreException e) { log.error(e.getMessage(), e); } return templates.toArray(new Template[templates.size()]); } /** * @param project * @param eclipsePrj only here because getSearchEngine() requires it as parameter. * @param templates * @param currentNode * @param prefix * @throws CoreException */ protected void addTemplates(MavenProject project, IProject eclipsePrj, Collection<Template> templates, Node currentNode, String prefix) throws CoreException { if(handlesFiles()) { addFileTemplates(project, eclipsePrj, templates, currentNode, prefix, name().toLowerCase().endsWith("directory"), null); } } protected FileProposalContext getFileProposalContext(MavenProject project, IProject eclipsePrj, String prefix) { if(project == null && eclipsePrj == null) { return null; } File projectDir; if(project != null) { projectDir = project.getFile().getParentFile(); } else { projectDir = new File(eclipsePrj.getLocationURI()); } String parentPath; String prefixPath; int lastSep = prefix.lastIndexOf('/'); if(lastSep != -1) { prefixPath = prefix.substring(0, lastSep) + '/'; prefix = prefix.substring(lastSep + 1); } else { prefixPath = ""; } String interpolated = simpleInterpolate(project, prefixPath); parentPath = interpolated == null ? prefixPath : interpolated; File parentDir; if(!new File(parentPath).isAbsolute()) { parentDir = new File(projectDir, parentPath); } else { parentDir = new File(parentPath); } if(!parentDir.isDirectory()) { return null; } return new FileProposalContext(projectDir, parentDir, prefixPath, prefix); } protected void addFileTemplates(MavenProject project, IProject eclipsePrj, Collection<Template> templates, Node currentNode, String prefix, boolean dirsOnly, String wrapperNode) { FileProposalContext pctx = getFileProposalContext(project, eclipsePrj, prefix); if(pctx == null) { return; } List<File> files = Arrays.asList(pctx.parentDir.listFiles()); Collections.sort(files, Comparator.<File, Integer> comparing(r -> r.isDirectory() ? 0 : 1) .thenComparing(Comparator.comparing(r -> r.getName()))); int rel = 4000; for(File f : files) { if(f.getName().startsWith(pctx.prefix)) { String value = pctx.prefixPath + f.getName(); String template = value; boolean retrigger = false; if(f.isDirectory()) { if(hasContents(f, dirsOnly)) { template += '/'; retrigger = true; } } else if(dirsOnly) { continue; } template = template.replace("$", "$$"); if(wrapperNode != null) { template = '<' + wrapperNode + '>' + template + "${cursor}</" + wrapperNode + '>'; } templates.add(new PomTemplate(f.getName(), "", getContextTypeId(), template, false).image(getFileIcon(f)) .matchValue(value).relevance(rel-- ).retriggerOnApply(retrigger)); } } } private boolean hasContents(File f, boolean dirsOnly) { // using nio is faster for large dirs compared to File#listFiles() boolean[] res = new boolean[] {false}; Path thisPath = f.toPath(); try { Files.walkFileTree(thisPath, new SimpleFileVisitor<Path>() { public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if(thisPath.equals(dir)) { return FileVisitResult.CONTINUE; } res[0] = true; return FileVisitResult.TERMINATE; } public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { if(dirsOnly) { return FileVisitResult.CONTINUE; } res[0] = true; return FileVisitResult.TERMINATE; } }); } catch(IOException ex) { } return res[0]; } protected void addModuleTemplates(MavenProject project, IProject eclipseprj, Collection<Template> proposals, Node node, String prefix, boolean wrap) { if(project == null) { //shall not happen just double check. return; } FileProposalContext pctx = getFileProposalContext(project, eclipseprj, prefix); if(pctx == null) { return; } //MNGECLIPSE-2204 collect the existing values from the surrounding xml content only.. // also, if it's a profile modules list, consider main modules as well Set<String> existings = new HashSet<>(); Node moduleNode = node; if(moduleNode != null) { Node modulesNode; if(moduleNode.getLocalName().equals("modules")) { modulesNode = moduleNode; } else { modulesNode = moduleNode.getParentNode(); } while(modulesNode != null) { for(Element el : XmlUtils.findChilds((Element) modulesNode, "module")) { if(el != moduleNode) { String val = XmlUtils.getTextValue(el); if(val != null) { existings.add(val); } } } Node profileProjectNode = getAncestor(modulesNode, "profile", "profiles", "project"); if(profileProjectNode != null) { modulesNode = getChildWithName(profileProjectNode, "modules"); } else { modulesNode = null; } } } Set<String> subProjects = new LinkedHashSet<>(); try { Path projectPath = pctx.projectDir.toPath().toRealPath(); Path parentPath = pctx.parentDir.toPath().toRealPath(); FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() { boolean submodulesSearch; boolean submodulesFound; Path submodulesSearchBase; public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { if(submodulesSearch && submodulesFound) { // drop out quickly if we were searching for submodules and at least one was already found return FileVisitResult.SKIP_SIBLINGS; } String name = dir.getFileName().toString(); if(name.startsWith(".")) { // skip dotfiles return FileVisitResult.SKIP_SUBTREE; } if(parentPath.equals(dir)) { // don't propose the dir we are looking under return FileVisitResult.CONTINUE; } if(projectPath.equals(dir) && pctx.prefixPath.startsWith("../")) { // skip this project's dir entirely when looking for modules in parent dir return FileVisitResult.SKIP_SUBTREE; } if(projectPath.startsWith(dir)) { // skip ancestors of current dir return FileVisitResult.CONTINUE; } //TODO polyglot? if(Arrays.asList("src", "target", "bin").contains(name) && dir.resolve("../pom.xml").toFile().exists()) { // skip recursing into certain the project dirs return FileVisitResult.SKIP_SUBTREE; } if(dir.resolve("pom.xml").toFile().exists()) { if(submodulesSearch) { // we were looking for submodules and found at least one submodulesFound = true; return FileVisitResult.SKIP_SIBLINGS; } // found a candidate String path = projectPath.relativize(dir).toString().replace('\\', '/'); if(!existings.contains(path)) { subProjects.add(path); } // now we need to check for submodules, and if there are any, add a <path>/ proposal submodulesSearch = true; submodulesSearchBase = dir; submodulesFound = false; } return FileVisitResult.CONTINUE; } public FileVisitResult postVisitDirectory(Path dir, IOException e) { if(submodulesSearch && dir.equals(submodulesSearchBase)) { // finish search for submodules if(submodulesFound) { String path = projectPath.relativize(dir).toString().replace('\\', '/'); subProjects.add(path + '/'); } submodulesSearch = false; submodulesSearchBase = null; submodulesFound = false; } return FileVisitResult.CONTINUE; } }; Files.walkFileTree(parentPath, EnumSet.of(FileVisitOption.FOLLOW_LINKS), 5, visitor); } catch(IOException e) { } subProjects.removeAll(existings); int moduleRel = 8000; int submoduleRel = 4000; for(String path : subProjects) { if(path.startsWith(prefix)) { String value = path; String template = value; template = template.replace("$", "$$"); Image image; String description; int rel; boolean retrigger = false; if(path.endsWith("/")) { image = MvnImages.IMG_DISCOVERY; description = NLS.bind(Messages.PomTemplateContext_submodules, path); rel = submoduleRel-- ; retrigger = true; } else { image = getFileIcon(new File(pctx.projectDir, path)); description = NLS.bind(Messages.PomTemplateContext_module, path); rel = moduleRel-- ; } if(wrap) { template = "<module>" + template + "${cursor}</module>"; } proposals.add(new PomTemplate(value, description, getContextTypeId(), template, false).image(image) .relevance(rel).retriggerOnApply(retrigger)); } } } protected static Image getFileIcon(File f) { IWorkspaceRoot wroot = ResourcesPlugin.getWorkspace().getRoot(); IResource[] resources; if(f.isDirectory()) { resources = wroot.findContainersForLocationURI(f.toURI()); } else { resources = wroot.findFilesForLocationURI(f.toURI()); } IResource res = resources.length > 0 ? resources[0] : null; if(res != null) { IWorkbenchAdapter wbAdapter = res.getAdapter(IWorkbenchAdapter.class); if(wbAdapter == null) { return null; } ImageDescriptor id = wbAdapter.getImageDescriptor(res); return id != null ? MvnImages.getImage(id) : null; } if(f.isDirectory()) { return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); } return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); } protected String getNodeName() { return nodeName; } public String getContextTypeId() { return PREFIX + contextSuffix; } public static PomTemplateContext fromId(String contextTypeId) { for(PomTemplateContext context : values()) { if(context.getContextTypeId().equals(contextTypeId)) { return context; } } return UNKNOWN; } public static PomTemplateContext fromNodeName(String idSuffix) { for(PomTemplateContext context : values()) { if(context.getNodeName().equals(idSuffix)) { return context; } } return UNKNOWN; } public static PomTemplateContext fromNode(Node node) { PomTemplateContext context = PomTemplateContext.fromNodeName(node.getNodeName()); // find an ancestor whose context impl can handle all of its subtree PomTemplateContext ancestorContext = context; while(!ancestorContext.handlesSubtree() && node != null) { ancestorContext = PomTemplateContext.fromNodeName(node.getNodeName()); node = node.getParentNode(); } if(ancestorContext.handlesSubtree()) { context = ancestorContext; } return context; } protected static SearchEngine getSearchEngine(IProject project) throws CoreException { if(searchEngineForTests != null) { return searchEngineForTests; } return M2EUIPluginActivator.getDefault().getSearchEngine(project); } private static SearchEngine searchEngineForTests; public static void setSearchEngineForTests(SearchEngine _searchEngineForTests) { searchEngineForTests = _searchEngineForTests; } /** * Returns containing artifactInfo for exclusions. Otherwise returns null. */ protected ArtifactInfo getContainingArtifact(Node currentNode) { if(isExclusion(currentNode)) { Node node = currentNode.getParentNode().getParentNode(); return getArtifactInfo(node); } return null; } /** * Returns artifact info from siblings of given node. */ private ArtifactInfo getArtifactInfo(Node node) { return new ArtifactInfo(getGroupId(node), getArtifactId(node), getVersion(node), // getSiblingTextValue(node, "classifier"), getSiblingTextValue(node, "type")); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Returns required packaging. */ protected Packaging getPackaging(Node currentNode) { if(isPlugin(currentNode)) { return Packaging.PLUGIN; } else if(isParent(currentNode)) { return Packaging.POM; } return Packaging.ALL; } /** * Returns true if user is editing plugin dependency. */ private boolean isPlugin(Node currentNode) { return "plugin".equals(currentNode.getParentNode().getNodeName()); //$NON-NLS-1$ } /** * Returns true if user is editing plugin dependency exclusion. */ private boolean isExclusion(Node currentNode) { return "exclusion".equals(currentNode.getParentNode().getNodeName()); //$NON-NLS-1$ } /** * Returns true if user is editing parent dependency. */ private boolean isParent(Node currentNode) { return "parent".equals(currentNode.getParentNode().getNodeName()); //$NON-NLS-1$ } protected String getGroupId(Node currentNode) { return getSiblingTextValue(currentNode, "groupId"); //$NON-NLS-1$ } protected void checkAndAdd(Collection<Template> proposals, String prefix, String name) { checkAndAdd(proposals, prefix, name, name); } protected void checkAndAdd(Collection<Template> proposals, String prefix, String name, String description) { checkAndAdd(proposals, prefix, name, name, -1); } protected void checkAndAdd(Collection<Template> proposals, String prefix, String name, String description, int rel) { if(name.startsWith(prefix)) { proposals.add(new PomTemplate(name, description, getContextTypeId(), name, false).relevance(rel)); } } /** * @param project * @param version * @param groupId * @param artifactId * @return * @throws CoreException */ static int EXTRACT_STRATEGY_PLUGIN = 1; static int EXTRACT_STRATEGY_DEPENDENCY = 2; static int EXTRACT_STRATEGY_SEARCH = 4; static String extractVersion(MavenProject mp, IProject project, String version, String groupId, String artifactId, int strategy) throws CoreException { assert mp != null; version = simpleInterpolate(mp, version); if(version == null) { Packaging pack = Packaging.ALL; if((strategy & EXTRACT_STRATEGY_PLUGIN) != 0) { version = searchPM(mp, groupId, artifactId); pack = Packaging.PLUGIN; } if((strategy & EXTRACT_STRATEGY_DEPENDENCY) != 0) { version = searchDM(mp, groupId, artifactId); } if(version == null && (strategy & EXTRACT_STRATEGY_SEARCH) != 0) { Collection<String> versions = getSearchEngine(project).findVersions(groupId, artifactId, "", pack); //$NON-NLS-1$ if(versions.isEmpty()) { return null; } version = versions.iterator().next(); } } return version; } // TODO copy of this resides in FormUtils static String simpleInterpolate(MavenProject project, String text) { if(text != null && text.contains("${")) { //$NON-NLS-1$ //when expression is in the version but no project instance around // just give up. if(project == null) { return null; } Properties props = project.getProperties(); RegexBasedInterpolator inter = new RegexBasedInterpolator(); if(props != null) { inter.addValueSource(new PropertiesBasedValueSource(props)); } inter.addValueSource( new PrefixedObjectValueSource(Arrays.asList(new String[] {"pom.", "project."}), project.getModel(), false)); //$NON-NLS-1$ //$NON-NLS-2$ try { text = inter.interpolate(text); } catch(InterpolationException e) { text = null; } } return text; } static String searchPM(MavenProject project, String groupId, String artifactId) { if(project == null) { return null; } String version = null; String id = Plugin.constructKey(groupId, artifactId); PluginManagement pm = project.getPluginManagement(); if(pm != null) { for(Plugin pl : pm.getPlugins()) { if(id.equals(pl.getKey())) { version = pl.getVersion(); break; } } } return version; } static String searchDM(MavenProject project, String groupId, String artifactId) { if(project == null) { return null; } String version = null; //see if we can find the dependency is in dependency management of resolved project. String id = groupId + ":" + artifactId + ":"; DependencyManagement dm = project.getDependencyManagement(); if(dm != null) { for(Dependency dep : dm.getDependencies()) { if(dep.getManagementKey().startsWith(id)) { version = dep.getVersion(); break; } } } return version; } protected static String getArtifactId(Node currentNode) { return getSiblingTextValue(currentNode, "artifactId"); //$NON-NLS-1$ } protected static String getVersion(Node currentNode) { return getSiblingTextValue(currentNode, "version"); //$NON-NLS-1$ } private static String getSiblingTextValue(Node sibling, String name) { Node node = getSiblingWithName(sibling, name); return XmlUtils.getTextValue(node); } /** * Returns sibling with given name. */ private static Node getSiblingWithName(Node node, String name) { return getChildWithName(node.getParentNode(), name); } /** * Returns child with given name */ protected static Node getChildWithName(Node node, String name) { NodeList nodeList = node.getChildNodes(); for(int i = 0; i < nodeList.getLength(); i++ ) { if(name.equals(nodeList.item(i).getNodeName())) { return nodeList.item(i); } } return null; } protected static Node getAncestor(Node node, String... names) { int i = 0; for(; i < names.length; i++ ) { Node parent = node.getParentNode(); if(parent == null || !names[i].equals(parent.getNodeName())) return null; node = parent; } return i == names.length ? node : null; } private static class FileProposalContext { final File projectDir; final File parentDir; final String prefixPath; final String prefix; FileProposalContext(File projectDir, File parentDir, String prefixPath, String prefix) { this.projectDir = projectDir; this.parentDir = parentDir; this.prefixPath = prefixPath; this.prefix = prefix; } } }