| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.maven.execute; |
| |
| import org.netbeans.modules.maven.execute.cmd.ExecutionEventObject; |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.Reader; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.EnumMap; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Logger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import org.apache.maven.execution.ExecutionEvent; |
| import org.apache.maven.project.MavenProject; |
| import org.json.simple.JSONObject; |
| import org.json.simple.parser.JSONParser; |
| import org.json.simple.parser.ParseException; |
| import org.netbeans.api.annotations.common.CheckForNull; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.modules.maven.api.execute.RunConfig; |
| import org.netbeans.api.progress.ProgressHandle; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.maven.api.NbMavenProject; |
| import org.netbeans.modules.maven.api.output.OutputUtils; |
| import org.netbeans.modules.maven.api.output.OutputVisitor; |
| import org.netbeans.modules.maven.execute.AbstractMavenExecutor.ResumeFromFinder; |
| import static org.netbeans.modules.maven.execute.AbstractOutputHandler.PRJ_EXECUTE; |
| import static org.netbeans.modules.maven.execute.AbstractOutputHandler.SESSION_EXECUTE; |
| import org.netbeans.modules.maven.execute.cmd.ExecMojo; |
| import org.netbeans.modules.maven.execute.cmd.ExecProject; |
| import org.netbeans.modules.maven.execute.cmd.ExecSession; |
| import org.netbeans.modules.maven.options.MavenSettings; |
| import org.netbeans.spi.project.ProjectContainerProvider; |
| import org.netbeans.spi.project.SubprojectProvider; |
| import org.openide.util.Exceptions; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.RequestProcessor.Task; |
| import org.openide.windows.IOPosition; |
| import org.openide.windows.InputOutput; |
| import org.openide.windows.OutputWriter; |
| |
| /** |
| * handling of output coming from maven commandline builds |
| * @author Milos Kleint |
| */ |
| public class CommandLineOutputHandler extends AbstractOutputHandler { |
| |
| //32 means 16 paralel builds, one for input, one for output. #229904 |
| private static final RequestProcessor PROCESSOR = new RequestProcessor("Maven ComandLine Output Redirection", Integer.getInteger("maven.concurrent.builds", 16) * 2); //NOI18N |
| private static final Logger LOG = Logger.getLogger(CommandLineOutputHandler.class.getName()); |
| private InputOutput inputOutput; |
| /*test*/ static final Pattern DOWNLOAD = Pattern.compile("^(\\d+(/\\d*)? ?(M|K|b|KB|B|\\?)\\s*)+$"); //NOI18N |
| private static final Pattern linePattern = Pattern.compile("\\[(DEBUG|INFO|WARNING|ERROR|FATAL)\\] (.*)"); // NOI18N |
| public static final Pattern startPatternM2 = Pattern.compile("\\[INFO\\] \\[([\\w]*):([\\w]*)[ ]?.*\\]"); // NOI18N |
| public static final Pattern startPatternM3 = Pattern.compile("\\[INFO\\] --- (\\S+):\\S+:(\\S+)(?: [(]\\S+[)])? @ \\S+ ---"); // ExecutionEventLogger.mojoStarted NOI18N |
| private static final Pattern mavenSomethingPlugin = Pattern.compile("maven-(.+)-plugin"); // NOI18N |
| private static final Pattern somethingMavenPlugin = Pattern.compile("(.+)-maven-plugin"); // NOI18N |
| /** @see org.apache.maven.cli.ExecutionEventLogger#logReactorSummary */ |
| static final Pattern reactorFailure = Pattern.compile("\\[INFO\\] (.+) [.]* FAILURE \\[.+\\]"); // NOI18N |
| private static final Pattern stackTraceElement = OutputUtils.linePattern; |
| |
| public static final Pattern reactorSummaryLine = Pattern.compile("(.+) [.]* (FAILURE|SUCCESS) (\\[.+\\])?"); // NOI18N |
| private OutputWriter stdOut; |
| private String currentProject; |
| private String currentTag; |
| Task outTask; |
| private Input inp; |
| private ProgressHandle handle; |
| /** {@link MavenProject#getName} of first project in reactor to fail, if any */ |
| String firstFailure; |
| private final JSONParser parser; |
| private ContextImpl contextImpl; |
| //the depth is as follows |
| //Session -> Project -> [Fork -> ForkedProject] -> Mojo |
| private final ExecutionEventObject.Tree executionTree = new ExecutionEventObject.Tree(null, null); |
| |
| private ExecutionEventObject.Tree currentTreeNode = executionTree; |
| private boolean inStackTrace = false; |
| private boolean addMojoFold = false; |
| private boolean addProjectFold = false; |
| private URL[] mavencoreurls; |
| |
| public CommandLineOutputHandler(InputOutput io, Project proj, ProgressHandle hand, RunConfig config, boolean createVisitorContext) { |
| super(proj, hand, config, createVisitorContext ? new OutputVisitor(new ContextImpl()) : new OutputVisitor()); |
| if (createVisitorContext) { |
| contextImpl = (ContextImpl) visitor.getContext(); |
| assert contextImpl != null; |
| contextImpl.setExecutionTree(executionTree); |
| } |
| this.parser = new JSONParser(); |
| handle = hand; |
| inputOutput = io; |
| stdOut = inputOutput.getOut(); |
| // logger = new Logger(); |
| initProcessorList(proj, config); |
| } |
| |
| /** |
| * |
| * @return null if tree is not being built (maven 2.x) |
| */ |
| public @CheckForNull ExecutionEventObject.Tree getExecutionTree() { |
| if (contextImpl != null) { |
| return executionTree; |
| } |
| return null; |
| } |
| |
| |
| @Override |
| protected final void checkSleepiness() { |
| if (contextImpl == null) { //only perform for maven 2.x now |
| handle.progress(currentProject == null ? "" : currentTag == null ? currentProject : currentProject + " " + currentTag); // NOI18N |
| } |
| super.checkSleepiness(); |
| } |
| |
| |
| void setStdOut(InputStream inStr) { |
| outTask = PROCESSOR.post(new Output(inStr)); |
| } |
| |
| void setStdIn(OutputStream in) { |
| inp = new Input(in, inputOutput); |
| PROCESSOR.post(inp); |
| } |
| |
| void waitFor() { |
| inp.stopInput(); |
| // if (inTask != null) { |
| // inTask.waitFinished(); |
| // } |
| if (outTask != null) { |
| outTask.waitFinished(); |
| } |
| } |
| |
| @Override |
| protected InputOutput getIO() { |
| return this.inputOutput; |
| } |
| |
| private static final String SEC_MOJO_EXEC = "mojo-execute"; //NOI18N |
| private void closeCurrentTag() { |
| assert contextImpl == null; |
| if (currentTag != null) { |
| CommandLineOutputHandler.this.processEnd(getEventId(SEC_MOJO_EXEC, currentTag), stdOut); |
| currentTag = null; |
| } |
| } |
| |
| private void mergeClasspath(ExecMojo exec, URL[] coreurls) { |
| if (coreurls != null) { |
| URL[] urls = exec.getClasspathURLs(); |
| if (urls == null) { |
| exec.setClasspathURLs(coreurls); |
| } else { |
| List<URL> newones = new ArrayList<URL>(); |
| newones.addAll(Arrays.asList(urls)); |
| newones.addAll(Arrays.asList(coreurls)); |
| exec.setClasspathURLs(newones.toArray(new URL[0])); |
| } |
| } |
| } |
| |
| |
| |
| |
| private class Output implements Runnable { |
| private static final String INFO_NETBEANS_EXEC_EVENT = "[INFO] NETBEANS-ExecEvent:"; |
| |
| private final BufferedReader str; |
| private boolean skipLF = false; |
| |
| public Output(InputStream instream) { |
| str = new BufferedReader(new InputStreamReader(instream)); |
| } |
| |
| private String readLine() throws IOException { |
| char[] char1 = new char[1]; |
| boolean isReady = true; |
| int count = 0; |
| StringBuilder buf = new StringBuilder(); |
| while (isReady) { |
| count = count + 1; |
| if (count > 20000) { //#239847 limit the number of bytes/characters read together in one lump, make sure the stringBuilder doesn't grow out of proportions. |
| break; //make it an incomplete line. |
| } |
| int ret = str.read(char1); |
| if (ret != 1) { |
| if (ret == -1 && buf.length() == 0) { |
| return null; |
| } |
| return buf.toString(); |
| } |
| if (skipLF) { |
| skipLF = false; |
| if (char1[0] == '\n') { //NOI18N |
| continue; |
| } |
| } |
| if (char1[0] == '\n') { //NOI18N |
| return buf.toString(); |
| } |
| if (char1[0] == '\r') { //NOI18N |
| skipLF = true; |
| buf.append(char1[0]); |
| return buf.toString(); |
| } |
| buf.append(char1[0]); |
| isReady = str.ready(); |
| if (!isReady) { |
| synchronized (this) { |
| try { |
| wait(500); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } finally { |
| if (!str.ready()) { |
| break; |
| } |
| isReady = true; |
| } |
| } |
| |
| } |
| } |
| return "&^#INCOMPLINE:" + buf.toString(); //NOI18N |
| |
| } |
| |
| public @Override void run() { |
| CommandLineOutputHandler.this.processStart(getEventId(SESSION_EXECUTE, null), stdOut); |
| if (contextImpl == null) { |
| CommandLineOutputHandler.this.processStart(getEventId(PRJ_EXECUTE,null), stdOut); |
| } |
| try { |
| |
| String line = readLine(); |
| while (line != null) { |
| if (line.startsWith("&^#INCOMPLINE:")) { //NOI18N |
| stdOut.print(line.substring("&^#INCOMPLINE:".length())); //NOI18N |
| line = readLine(); |
| continue; |
| } |
| int execEventIdx = line.indexOf(INFO_NETBEANS_EXEC_EVENT); |
| if (execEventIdx == 0) { |
| processExecEvent(parseExecEvent(line)); |
| // stdOut.println(line); //XXX temporary |
| line = readLine(); |
| continue; |
| } |
| |
| String nextLine = null; |
| if(execEventIdx > 0) { |
| nextLine = line.substring(execEventIdx); |
| line = line.substring(0, execEventIdx); |
| } |
| if (line.startsWith("[INFO] Final Memory:")) { //NOI18N |
| // previous value [INFO] --------------- is too early, the compilation errors don't get processed in this case. |
| //heuristics.. |
| if (contextImpl == null) { //only in m2 |
| closeCurrentTag(); |
| } |
| } |
| |
| String tag = null; |
| if (contextImpl == null) { |
| Matcher match = startPatternM3.matcher(line); |
| if (match.matches()) { |
| String mojoArtifact = match.group(1); |
| mojoArtifact = goalPrefixFromArtifactId(mojoArtifact); |
| tag = mojoArtifact + ':' + match.group(2); |
| } else { |
| match = startPatternM2.matcher(line); |
| if (match.matches()) { |
| tag = match.group(1) + ':' + match.group(2); |
| } |
| } |
| } |
| if (tag != null) { //only in m2 |
| closeCurrentTag(); |
| currentTag = tag; |
| CommandLineOutputHandler.this.processStart(getEventId(SEC_MOJO_EXEC, tag), stdOut); |
| checkSleepiness(); |
| } |
| |
| boolean isDownloadProgress = false; |
| if(line.length() > 0 && line.charAt(line.length() - 1) == '\r') { |
| // issue #252514 |
| if (DOWNLOAD.matcher(line).matches()) { |
| isDownloadProgress = true; |
| } else { |
| line = line.substring(0, line.length() - 1); |
| } |
| } |
| if(!isDownloadProgress) { |
| Matcher match = linePattern.matcher(line); |
| if (match.matches()) { |
| String levelS = match.group(1); |
| Level level = Level.valueOf(levelS); |
| String text = match.group(2); |
| updateFoldForException(text); |
| processLine(MavenSettings.getDefault().isShowLoggingLevel() ? line : text, stdOut, level); |
| if (level == Level.INFO && contextImpl == null) { //only perform for maven 2.x now |
| checkProgress(text); |
| } |
| } else { |
| // oh well.. |
| updateFoldForException(line); |
| processLine(line, stdOut, Level.INFO); |
| } |
| } |
| if (contextImpl == null && firstFailure == null) { |
| Matcher match = reactorFailure.matcher(line); |
| if (match.matches()) { |
| firstFailure = match.group(1); |
| } |
| } |
| //these two are a bit shaky and depend on output details that might not be available in the future. |
| //however there's no other way to have the proper line marked as beginning of a section (as the event comes first) |
| //without this, the last line of previous output would be marked as beginning of the fold. |
| if (addMojoFold && line.startsWith("[INFO] ---")) { //NOI18N |
| currentTreeNode.startFold(inputOutput); |
| addMojoFold = false; |
| } |
| if (addProjectFold && line.startsWith("[INFO] Building")) { |
| currentTreeNode.startFold(inputOutput); |
| addProjectFold = false; |
| } |
| line = nextLine != null ? nextLine : readLine(); |
| } |
| } catch (IOException ex) { |
| java.util.logging.Logger.getLogger(CommandLineOutputHandler.class.getName()).log(java.util.logging.Level.FINE, null, ex); |
| } finally { |
| if (contextImpl == null) { |
| CommandLineOutputHandler.this.processEnd(getEventId(PRJ_EXECUTE, null), stdOut); |
| } else { |
| completeTreeAtEnd(); |
| } |
| CommandLineOutputHandler.this.processEnd(getEventId(SESSION_EXECUTE, null), stdOut); |
| try { |
| str.close(); |
| } catch (IOException ex) { |
| ex.printStackTrace(); |
| } |
| } |
| } |
| |
| private ExecutionEventObject parseExecEvent(String line) { |
| String jsonContent = line.substring(INFO_NETBEANS_EXEC_EVENT.length()); |
| try { |
| Object o = parser.parse(jsonContent); |
| // System.out.println("o=" + o); |
| if (o instanceof JSONObject) { |
| JSONObject json = (JSONObject) o; |
| return ExecutionEventObject.create(json); |
| } |
| } catch (ParseException ex) { |
| Exceptions.printStackTrace(ex); |
| // System.out.println("exc=" + ex); |
| } |
| return null; |
| } |
| |
| |
| |
| /** |
| * Check whether the line is start of a stacktrace, inside a stacktrace, |
| * or is another text, and update folds accordingly. |
| */ |
| private void updateFoldForException(String line) { |
| if (stackTraceElement.matcher(line).find()) { |
| inStackTrace = true; |
| if (!currentTreeNode.hasInnerOutputFold()) { |
| currentTreeNode.startInnerOutputFold(inputOutput); |
| } |
| } else if (inStackTrace) { |
| currentTreeNode.finishInnerOutputFold(); |
| inStackTrace = false; |
| } |
| } |
| |
| |
| } |
| |
| private void completeTreeAtEnd() { |
| //#229877 at the end of the build, verify that the tree is complete. |
| for (ExecutionEventObject.Tree nd : executionTree.getChildrenNodes()) { |
| if (nd.getEndEvent() == null) { |
| ExecutionEventObject innerEnd = createEndForStart(nd.getStartEvent()); |
| trimTree(innerEnd); |
| } |
| } |
| } |
| |
| private void processExecEvent(ExecutionEventObject obj) { |
| if (obj == null) { |
| return; |
| } |
| checkProgress(obj); |
| |
| if (ExecutionEvent.Type.SessionStarted.equals(obj.type)) { |
| if (currentTreeNode != executionTree) { |
| //we are not at a real start, something restarted the build.. |
| completeTreeAtEnd(); |
| } |
| mavencoreurls = ((ExecSession)obj).getMnvcoreurls(); |
| } |
| if (ExecutionEvent.Type.MojoStarted.equals(obj.type)) { |
| growTree(obj); |
| addMojoFold = true; |
| ExecMojo exec = (ExecMojo) obj; |
| String tag = goalPrefixFromArtifactId(exec.plugin.artifactId) + ":" + exec.goal; |
| ExecutionEventObject.Tree prjNode = currentTreeNode.findParentNodeOfType(ExecutionEvent.Type.ProjectStarted); |
| assert prjNode != null; |
| ExecProject p = (ExecProject) prjNode.getStartEvent(); |
| handle.progress(p.gav.artifactId + " " + tag); |
| CommandLineOutputHandler.this.processStart(getEventId(SEC_MOJO_EXEC, tag), stdOut); |
| } |
| if (ExecutionEvent.Type.MojoSucceeded.equals(obj.type)) { |
| if (MavenSettings.getDefault().isCollapseSuccessFolds()) { |
| currentTreeNode.collapseFold(); |
| } |
| currentTreeNode.finishFold(); |
| ExecMojo exec = (ExecMojo) obj; |
| mergeClasspath(exec, mavencoreurls); |
| trimTree(exec); |
| String tag = goalPrefixFromArtifactId(exec.plugin.artifactId) + ":" + exec.goal; |
| CommandLineOutputHandler.this.processEnd(getEventId(SEC_MOJO_EXEC, tag), stdOut); |
| } |
| else if (ExecutionEvent.Type.MojoFailed.equals(obj.type)) { |
| currentTreeNode.finishFold(); |
| ExecMojo exec = (ExecMojo) obj; |
| mergeClasspath(exec, mavencoreurls); |
| trimTree(exec); |
| String tag = goalPrefixFromArtifactId(exec.plugin.artifactId) + ":" + exec.goal; |
| CommandLineOutputHandler.this.processFail(getEventId(SEC_MOJO_EXEC, tag), stdOut); |
| } |
| else if (ExecutionEvent.Type.ProjectStarted.equals(obj.type)) { |
| growTree(obj); |
| addProjectFold = true; |
| if (contextImpl != null) { |
| ExecProject pr = (ExecProject)obj; |
| Project project = pr.findProject(); |
| contextImpl.setCurrentProject(project); |
| CommandLineOutputHandler.this.processStart(getEventId(PRJ_EXECUTE, null), stdOut); |
| } |
| } |
| else if (ExecutionEvent.Type.ProjectSkipped.equals(obj.type)) { |
| //growTree(obj); |
| //trimTree(obj); |
| //GlobalOutputProcessor currently depens on skipped projects not being added to tree. |
| } |
| else if (ExecutionEvent.Type.ProjectSucceeded.equals(obj.type)) { |
| if (MavenSettings.getDefault().isCollapseSuccessFolds()) { |
| currentTreeNode.collapseFold(); |
| } |
| currentTreeNode.finishFold(); |
| trimTree(obj); |
| CommandLineOutputHandler.this.processEnd(getEventId(PRJ_EXECUTE, null), stdOut); |
| } |
| else if (ExecutionEvent.Type.ProjectFailed.equals(obj.type)) { |
| currentTreeNode.finishFold(); |
| trimTree(obj); |
| CommandLineOutputHandler.this.processEnd(getEventId(PRJ_EXECUTE, null), stdOut); |
| } else if (ExecutionEvent.Type.ForkStarted.equals(obj.type)) { |
| growTree(obj); |
| } else if (ExecutionEvent.Type.ForkedProjectStarted.equals(obj.type)) { |
| growTree(obj); |
| } else if (ExecutionEvent.Type.ForkFailed.equals(obj.type) || ExecutionEvent.Type.ForkSucceeded.equals(obj.type)) { |
| trimTree(obj); |
| } else if (ExecutionEvent.Type.ForkedProjectFailed.equals(obj.type) || ExecutionEvent.Type.ForkedProjectSucceeded.equals(obj.type)) { |
| trimTree(obj); |
| } else if (!MavenSettings.getDefault().isAlwaysShowOutput() && ExecutionEvent.Type.SessionEnded.equals(obj.type)) { |
| for (ExecutionEventObject.Tree node : executionTree.getChildrenNodes()) { |
| if (node.getEndEvent() != null && ExecutionEvent.Type.ProjectFailed.equals(node.getEndEvent().type)) { |
| getIO().select(); |
| break; |
| } |
| } |
| } |
| } |
| |
| private String goalPrefixFromArtifactId(String mojoArtifact) { |
| // XXX M3 reports artifactId of mojo whereas M2 reports goalPrefix; do not want to force every OutputProcessor to handle both |
| // XXX consider searching index on ArtifactInfo.PLUGIN_PREFIX instead |
| Matcher match2 = mavenSomethingPlugin.matcher(mojoArtifact); |
| if (match2.matches()) { |
| mojoArtifact = match2.group(1); |
| } else { |
| match2 = somethingMavenPlugin.matcher(mojoArtifact); |
| if (match2.matches()) { |
| mojoArtifact = match2.group(1); |
| } |
| } |
| return mojoArtifact; |
| } |
| |
| private void growTree(ExecutionEventObject obj) { |
| ExecutionEventObject.Tree tn = new ExecutionEventObject.Tree(obj, currentTreeNode); |
| //fork events come before the mojo events, we want them as childs, to know what form belongs to which mojo. |
| if (tn.getStartEvent().type.equals(ExecutionEvent.Type.MojoStarted) && !currentTreeNode.getChildrenNodes().isEmpty()) { |
| //check if the previous fork should be added to this event |
| ExecutionEventObject.Tree lastSibling = currentTreeNode.getChildrenNodes().get(currentTreeNode.getChildrenNodes().size() - 1 ); |
| while (lastSibling != null && lastSibling.getEndEvent() != null && (ExecutionEvent.Type.ForkFailed.equals(lastSibling.getEndEvent().type) || ExecutionEvent.Type.ForkSucceeded.equals(lastSibling.getEndEvent().type))) { |
| currentTreeNode.getChildrenNodes().remove(lastSibling); |
| tn.getChildrenNodes().add(0, lastSibling); |
| lastSibling.reassingParent(tn); |
| lastSibling = currentTreeNode.getChildrenNodes().isEmpty() ? null : currentTreeNode.getChildrenNodes().get(currentTreeNode.getChildrenNodes().size() - 1 ); |
| } |
| } |
| currentTreeNode.getChildrenNodes().add(tn); |
| currentTreeNode = tn; |
| currentTreeNode.setStartOffset(IOPosition.currentPosition(inputOutput)); |
| } |
| |
| private void trimTree(ExecutionEventObject obj) { |
| ExecutionEventObject start = currentTreeNode.getStartEvent(); |
| while (!matchingEvents(obj.type, start.type)) { //#229877 |
| ExecutionEventObject innerEnd = createEndForStart(start); |
| processExecEvent(innerEnd); |
| start = currentTreeNode.getStartEvent(); |
| //potentially never ending+ recursive loop, how to intercept? |
| } |
| currentTreeNode.setEndOffset(IOPosition.currentPosition(inputOutput)); |
| currentTreeNode.setEndEvent(obj); |
| currentTreeNode = currentTreeNode.getParentNode(); |
| } |
| |
| private boolean matchingEvents(ExecutionEvent.Type typeEnd, ExecutionEvent.Type typeStart) { |
| ExecutionEvent.Type match = END_TO_START_Mappings.get(typeEnd); |
| assert match != null : "unknown event type:" + typeEnd; |
| return typeStart.equals(match); |
| } |
| |
| // an artificial way of creating event objects for events that didn't come the natural way of parsing the output. |
| // typically happens when user stops the build or some other cases described in issue 229877 |
| private ExecutionEventObject createEndForStart(ExecutionEventObject start) { |
| ExecutionEventObject toRet; |
| if (start instanceof ExecMojo) { |
| ExecMojo startEx = (ExecMojo) start; |
| toRet = new ExecMojo(startEx.goal, startEx.plugin, startEx.phase, startEx.executionId, ExecutionEvent.Type.MojoFailed); |
| } else if (start instanceof ExecProject) { |
| ExecProject startPrj = (ExecProject) start; |
| toRet = new ExecProject(startPrj.gav, startPrj.currentProjectLocation, ExecutionEvent.Type.ProjectFailed); |
| } else if (start instanceof ExecSession) { |
| ExecSession ss = (ExecSession) start; |
| toRet = new ExecSession(ss.projectCount, ExecutionEvent.Type.SessionEnded); |
| } else { |
| ExecutionEvent.Type endType; |
| if (start.type.equals(ExecutionEvent.Type.ForkStarted)) { |
| endType = ExecutionEvent.Type.ForkFailed; |
| } else if (start.type.equals(ExecutionEvent.Type.ForkedProjectStarted)) { |
| endType = ExecutionEvent.Type.ForkedProjectFailed; |
| } else { |
| throw new RuntimeException("unknown event type: " + start.type); |
| } |
| toRet = new ExecutionEventObject(endType); |
| } |
| return toRet; |
| } |
| |
| private static final Map<ExecutionEvent.Type, ExecutionEvent.Type> END_TO_START_Mappings; |
| static { |
| END_TO_START_Mappings = new EnumMap<ExecutionEvent.Type, ExecutionEvent.Type>(ExecutionEvent.Type.class); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.ForkFailed, ExecutionEvent.Type.ForkStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.ForkSucceeded, ExecutionEvent.Type.ForkStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.ForkedProjectFailed, ExecutionEvent.Type.ForkedProjectStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.ForkedProjectSucceeded, ExecutionEvent.Type.ForkedProjectStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.MojoFailed, ExecutionEvent.Type.MojoStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.MojoSucceeded, ExecutionEvent.Type.MojoStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.ProjectFailed, ExecutionEvent.Type.ProjectStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.ProjectSucceeded, ExecutionEvent.Type.ProjectStarted); |
| END_TO_START_Mappings.put(ExecutionEvent.Type.SessionEnded, ExecutionEvent.Type.SessionEnded); |
| } |
| |
| |
| ResumeFromFinder createResumeFromFinder() { |
| if (contextImpl == null) { |
| if (firstFailure != null) { |
| return new FindByName(firstFailure); |
| } |
| return null; |
| } |
| for (ExecutionEventObject.Tree prj : executionTree.getChildrenNodes()) { |
| if (prj.getEndEvent() != null && ExecutionEvent.Type.ProjectFailed.equals(prj.getEndEvent().type)) { |
| //our first failure |
| return new FindByEvents( (ExecProject) prj.getStartEvent()); |
| } |
| } |
| return null; |
| } |
| |
| |
| //old school instance relying on output only.. currently only used in maven 2.x |
| private static class FindByName implements ResumeFromFinder { |
| |
| private final @NonNull String firstFailure; |
| |
| /** |
| * @param firstFailure {@link MavenProject#getName} |
| */ |
| private FindByName(@NonNull String firstFailure) { |
| this.firstFailure = firstFailure; |
| } |
| |
| @Override public @CheckForNull NbMavenProject find(@NonNull Project root) { |
| // XXX EventSpy (#194090) would make this more reliable and efficient |
| for (Project module : root.getLookup().lookup(ProjectContainerProvider.class).getContainedProjects().getProjects()) { |
| if (Thread.interrupted()) { |
| break; |
| } |
| NbMavenProject nbmp = module.getLookup().lookup(NbMavenProject.class); |
| if (nbmp == null) { |
| continue; |
| } |
| MavenProject mp = nbmp.getMavenProject(); |
| if (firstFailure.equals(mp.getName())) { |
| return nbmp; |
| } |
| } |
| return null; |
| } |
| |
| } |
| |
| private static class FindByEvents implements ResumeFromFinder { |
| private final ExecProject execProject; |
| |
| private FindByEvents(ExecProject execProject) { |
| this.execProject = execProject; |
| } |
| |
| |
| @Override |
| public @CheckForNull NbMavenProject find(@NonNull Project root) { |
| //we don't need the parameter, we have the exec tree |
| Project project = execProject.findProject(); |
| if (project != null) { |
| return project.getLookup().lookup(NbMavenProject.class); |
| } |
| return null; |
| } |
| |
| } |
| |
| |
| static class Input implements Runnable { |
| |
| private InputOutput inputOutput; |
| private OutputStream str; |
| private boolean stopIn = false; |
| |
| public Input(OutputStream out, InputOutput inputOutput) { |
| str = out; |
| this.inputOutput = inputOutput; |
| } |
| |
| public void stopInput() { |
| stopIn = true; |
| try { |
| inputOutput.getIn().close(); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| public @Override void run() { |
| Reader in = inputOutput.getIn(); |
| try { |
| while (true) { |
| int read = in.read(); |
| if (read != -1) { |
| str.write(read); |
| str.flush(); |
| } else { |
| str.close(); |
| return; |
| } |
| if (stopIn) { |
| return; |
| } |
| } |
| |
| } catch (IOException ex) { |
| ex.printStackTrace(); |
| } finally { |
| try { |
| str.close(); |
| } catch (IOException ex) { |
| ex.printStackTrace(); |
| } |
| } |
| } |
| } |
| |
| private void checkProgress(ExecutionEventObject eeo) { |
| if (ExecutionEvent.Type.ProjectDiscoveryStarted.equals(eeo.type)) { |
| handle.switchToIndeterminate(); |
| } else if (ExecutionEvent.Type.SessionStarted.equals(eeo.type)) { |
| reactorSize = ((ExecSession)eeo).projectCount + 1; |
| projectCount = 0; |
| handle.switchToDeterminate(reactorSize); |
| } else if (ExecutionEvent.Type.ProjectStarted.equals(eeo.type)) { |
| handle.progress(((ExecProject)eeo).gav.artifactId, Math.min(++projectCount, reactorSize)); |
| } else if (ExecutionEvent.Type.ProjectSkipped.equals(eeo.type)) { |
| handle.progress(((ExecProject)eeo).gav.artifactId + " " + ((ExecProject)eeo).gav.version, Math.min(++projectCount, reactorSize)); |
| } else if (ExecutionEvent.Type.SessionEnded.equals(eeo.type)) { |
| |
| } |
| } |
| |
| /** |
| * #192200: try to indicate progress esp. in a reactor build. |
| * @see org.apache.maven.cli.ExecutionEventLogger |
| */ |
| //only done for maven 2.x now. |
| private void checkProgress(String text) { |
| switch (state) { |
| case INITIAL: |
| if (text.equals("Reactor Build Order:")) { // NOI18N |
| state = ProgressState.GOT_REACTOR_BUILD_ORDER; |
| } |
| break; |
| case GOT_REACTOR_BUILD_ORDER: |
| if (text.trim().isEmpty()) { |
| state = ProgressState.GETTING_REACTOR_PROJECTS; |
| } else { |
| state = ProgressState.INITIAL; // ??? |
| } |
| break; |
| case GETTING_REACTOR_PROJECTS: |
| if (text.trim().isEmpty()) { |
| state = ProgressState.NORMAL; |
| reactorSize++; // so we do not show 100% completion while building last project |
| handle.switchToDeterminate(reactorSize); |
| LOG.log(java.util.logging.Level.FINE, "reactor size: {0}", reactorSize); |
| } else { |
| reactorSize++; |
| } |
| break; |
| case NORMAL: |
| if (forkCount == 0 && text.matches("-+")) { // NOI18N |
| state = ProgressState.GOT_DASHES; |
| } else if (text.startsWith(">>> ")) { // NOI18N |
| forkCount++; |
| LOG.log(java.util.logging.Level.FINE, "fork count up to {0}", forkCount); |
| } else if (forkCount > 0 && text.startsWith("<<< ")) { // NOI18N |
| forkCount--; |
| LOG.log(java.util.logging.Level.FINE, "fork count down to {0}", forkCount); |
| } |
| break; |
| case GOT_DASHES: |
| if (text.startsWith("Building ") && !text.startsWith("Building in ") || text.startsWith("Skipping ")) { // NOI18N |
| currentProject = text.substring(9); |
| closeCurrentTag(); |
| handle.progress(currentProject, Math.min(++projectCount, reactorSize)); |
| LOG.log(java.util.logging.Level.FINE, "got project #{0}: {1}", new Object[] {projectCount, currentProject}); |
| } |
| state = ProgressState.NORMAL; |
| break; |
| default: |
| assert false : state; |
| } |
| } |
| enum ProgressState { |
| INITIAL, |
| GOT_REACTOR_BUILD_ORDER, |
| GETTING_REACTOR_PROJECTS, |
| NORMAL, |
| GOT_DASHES, |
| } |
| private ProgressState state = ProgressState.INITIAL; |
| private int forkCount; |
| private int reactorSize; |
| private int projectCount; |
| |
| public static class ContextImpl implements OutputVisitor.Context { |
| |
| private Project currentProject; |
| private ExecutionEventObject.Tree executionTree; |
| |
| ContextImpl() { |
| } |
| |
| @Override |
| public @CheckForNull Project getCurrentProject() { |
| return currentProject; |
| } |
| |
| public void setCurrentProject(@NullAllowed Project currentProject) { |
| this.currentProject = currentProject; |
| } |
| |
| private void setExecutionTree(ExecutionEventObject.Tree executionTree) { |
| this.executionTree = executionTree; |
| } |
| |
| public ExecutionEventObject.Tree getExecutionTree() { |
| return executionTree; |
| } |
| |
| } |
| |
| } |
| |
| |