/******************************************************************************* * Copyright (c) 2016 Anton Tanasenko. * 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: * Anton Tanasenko - initial API and implementation *******************************************************************************/ package org.eclipse.m2e.core.internal.lifecyclemapping; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import org.codehaus.plexus.util.ReaderFactory; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.util.xml.pull.MXParser; import org.codehaus.plexus.util.xml.pull.XmlPullParser; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.apache.maven.model.InputLocation; import org.apache.maven.model.InputLocationTracker; import org.apache.maven.model.InputSource; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginContainer; import org.apache.maven.model.PluginExecution; import org.apache.maven.project.MavenProject; import org.eclipse.m2e.core.internal.Messages; import org.eclipse.m2e.core.internal.lifecyclemapping.model.LifecycleMappingMetadata; import org.eclipse.m2e.core.internal.lifecyclemapping.model.LifecycleMappingMetadataSource; import org.eclipse.m2e.core.internal.lifecyclemapping.model.PluginExecutionFilter; import org.eclipse.m2e.core.internal.lifecyclemapping.model.PluginExecutionMetadata; import org.eclipse.m2e.core.internal.markers.SourceLocation; import org.eclipse.m2e.core.lifecyclemapping.model.PluginExecutionAction; import org.eclipse.m2e.core.project.configurator.MojoExecutionKey; /** * AnnotationMappingMetadataSource * * @author atgsl */ public class AnnotationMappingMetadataSource implements MappingMetadataSource { private static final String SELF = ""; //$NON-NLS-1$ private final MavenProject project; private final String projectId; private final List<PI> pis; public static AnnotationMappingMetadataSource get(MavenProject project) { List<PI> pis = parsePIs(project); if(!pis.isEmpty()) { return new AnnotationMappingMetadataSource(project, pis); } return null; } private AnnotationMappingMetadataSource(MavenProject project, List<PI> pis) { this.project = project; this.pis = pis; projectId = project.getModel().getLocation(SELF).getSource().getModelId(); } public List<PluginExecutionMetadata> getPluginExecutionMetadata(MojoExecutionKey execution) { Xpp3Dom action = getAction(execution); if(action != null) { return Collections.singletonList(createMetadata(action)); } return Collections.emptyList(); } private Xpp3Dom getAction(MojoExecutionKey execution) { String key = Plugin.constructKey(execution.getGroupId(), execution.getArtifactId()); Plugin plugin = getPlugin(project.getBuild(), key); Plugin mplugin = getPlugin(project.getPluginManagement(), key); String executionId = execution.getExecutionId(); if(executionId != null) { // find pi for the executionId if(plugin != null) { PluginExecution pluginExecution = plugin.getExecutionsAsMap().get(executionId); if(pluginExecution != null) { Xpp3Dom action = getAction(pluginExecution); if(action != null) return action; } } if(mplugin != null) { PluginExecution pluginExecution = mplugin.getExecutionsAsMap().get(executionId); if(pluginExecution != null) { Xpp3Dom action = getAction(pluginExecution); if(action != null) return action; } } // find pi for the whole plugin if(plugin != null) { Xpp3Dom action = getAction(plugin); if(action != null) return action; } if(mplugin != null) { Xpp3Dom action = getAction(mplugin); if(action != null) return action; } } return null; } private Plugin getPlugin(PluginContainer plugins, String key) { return plugins == null ? null : plugins.getPluginsAsMap().get(key); } public LifecycleMappingMetadata getLifecycleMappingMetadata(String packagingType) throws DuplicateMappingException { return null; } private Xpp3Dom getAction(InputLocationTracker tracker) { InputLocation location = tracker.getLocation(SELF); if(location != null && location.getSource() != null && projectId.equals(location.getSource().getModelId())) { int l = location.getLineNumber(); int c = location.getColumnNumber(); for(PI pi : pis) { if(pi.l == l && pi.c == c) { return pi.action; } } } return null; } private PluginExecutionMetadata createMetadata(Xpp3Dom action) { Xpp3Dom actionDom = new Xpp3Dom("action"); //$NON-NLS-1$ actionDom.addChild(action); PluginExecutionMetadata md = new PluginExecutionMetadata(); md.setActionDom(actionDom); LifecycleMappingMetadataSource source = new LifecycleMappingMetadataSource(); source.setSource(project); md.setSource(source); md.setFilter(new PluginExecutionFilter()); return md; } private static List<PI> parsePIs(MavenProject project) { File pom = project.getFile(); InputSource source = project.getModel().getLocation(SELF).getSource(); List<PI> pis = new ArrayList<>(); XmlPullParser parser = new MXParser(); try (InputStream in = new FileInputStream(pom)) { parser.setInput(ReaderFactory.newXmlReader(in)); Deque<State> stack = new LinkedList<>(); int eventType = parser.getEventType(); while(eventType != XmlPullParser.END_DOCUMENT) { if(eventType == XmlPullParser.START_TAG) { stack.push(new State(parser.getLineNumber(), parser.getColumnNumber())); } else if(eventType == XmlPullParser.END_TAG) { stack.pop(); } else if(eventType == XmlPullParser.PROCESSING_INSTRUCTION && !stack.isEmpty()) { String text = parser.getText(); if(text.startsWith("m2e ")) { //$NON-NLS-1$ // found it Xpp3Dom dom = parse(text.substring(4)); if(dom == null) { SourceLocation location = new SourceLocation(source.getLocation(), source.getModelId(), parser.getLineNumber(), parser.getColumnNumber(), text.length() + 4); throw new LifecycleMappingConfigurationException(Messages.AnnotationMappingMetadataSource_UnsupportedInstructionFormat, location); } State s = stack.peek(); PI pi = new PI(s.l, s.c, dom); pis.add(pi); } } eventType = parser.nextToken(); } } catch(XmlPullParserException | IOException ex) { SourceLocation location = new SourceLocation(source.getLocation(), source.getModelId(), parser.getLineNumber(), parser.getColumnNumber(), 1); throw new LifecycleMappingConfigurationException(Messages.AnnotationMappingMetadataSource_ErrorParsingInstruction, location); } return pis; } private static final Splitter PI_SPLITTER = Splitter.on(CharMatcher.WHITESPACE).omitEmptyStrings().limit(2); private static final Splitter EXECUTE_SPLITTER = Splitter.on(',').omitEmptyStrings(); private static final Map<String, String> EXECUTE_OPTIONS = new ImmutableMap.Builder<String, String>() .put("onConfiguration", LifecycleMappingFactory.ELEMENT_RUN_ON_CONFIGURATION) //$NON-NLS-1$ .put("onIncremental", LifecycleMappingFactory.ELEMENT_RUN_ON_INCREMENTAL).build(); //$NON-NLS-1$ private static Xpp3Dom parse(String pi) { List<String> split = PI_SPLITTER.splitToList(pi); PluginExecutionAction a = getAction(split.get(0)); if(a == null) { return null; } switch(a) { case ignore: return new Xpp3Dom("ignore"); //$NON-NLS-1$ case configurator: if(split.size() != 2) { return null; } Xpp3Dom conf = new Xpp3Dom("configurator"); //$NON-NLS-1$ Xpp3Dom id = new Xpp3Dom("id"); //$NON-NLS-1$ id.setValue(split.get(1)); conf.addChild(id); return conf; case execute: Xpp3Dom exec = new Xpp3Dom("execute"); //$NON-NLS-1$ if(split.size() > 1) { for(String option : EXECUTE_SPLITTER.split(split.get(1))) { String value = EXECUTE_OPTIONS.get(option); if(value == null) { return null; } Xpp3Dom opt = new Xpp3Dom(value); opt.setValue("true"); //$NON-NLS-1$ exec.addChild(opt); } } return exec; default: return null; } } private static PluginExecutionAction getAction(String value) { for(PluginExecutionAction a : PluginExecutionAction.values()) { if(value.toLowerCase().equals(a.name())) { return a; } } return null; } private static class State { final int l; final int c; State(int l, int c) { this.l = l; this.c = c; } } private static class PI { final int l; final int c; final Xpp3Dom action; PI(int l, int c, Xpp3Dom action) { this.l = l; this.c = c; this.action = action; } } }