Groovy-Eclipse compiler support back-end (IDEA-52379)
[idea/community.git] / plugins / groovy / jps-plugin / src / org / jetbrains / jps / incremental / groovy / EclipseOutputParser.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.jps.incremental.groovy;
17
18 import com.intellij.util.containers.ContainerUtil;
19 import org.jetbrains.annotations.Nullable;
20 import org.jetbrains.jps.ModuleChunk;
21 import org.jetbrains.jps.incremental.messages.BuildMessage;
22 import org.jetbrains.jps.incremental.messages.CompilerMessage;
23
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28
29 /**
30  * Adapted from org.codehaus.groovy.eclipse.compiler.GroovyEclipseCompiler, part of maven groovy-eclipse-compiler plugin.
31  *
32  * The source is distributed under Eclipse Public License (http://www.eclipse.org/legal/epl-v10.html, eclipse_license.txt).
33  *
34  * @author peter
35  */
36 class EclipseOutputParser {
37   private final String myBuilderName;
38   private final ModuleChunk myChunk;
39
40   public EclipseOutputParser(String builderName, ModuleChunk chunk) {
41     myBuilderName = builderName;
42     myChunk = chunk;
43   }
44
45   private static final String PROB_SEPARATOR = "----------\n";
46
47   List<CompilerMessage> parseMessages(String input) throws IOException {
48     if (input.contains("The type groovy.lang.GroovyObject cannot be resolved. It is indirectly referenced from required .class files")) {
49       return Arrays.asList(new CompilerMessage(myBuilderName, BuildMessage.Kind.ERROR,
50                                                "Cannot compile Groovy files: no Groovy library is defined for module '" + myChunk.representativeTarget().getModule().getName() + "'"));
51     }
52
53     List<CompilerMessage> parsedMessages = new ArrayList<CompilerMessage>();
54
55     String[] msgs = input.split(PROB_SEPARATOR);
56     for (String msg : msgs) {
57       if (msg.length() > 1) {
58         // add the error bean
59         CompilerMessage message = parseMessage(msg);
60         if (message != null) {
61           parsedMessages.add(message);
62         }
63         else {
64           // assume that there are one or more non-normal messages here
65           // All messages start with <num>. ERROR or <num>. WARNING
66           String[] extraMsgs = msg.split("\n");
67           StringBuilder sb = new StringBuilder();
68           for (String extraMsg : extraMsgs) {
69             if (extraMsg.indexOf(". WARNING") > 0 || extraMsg.indexOf(". ERROR") > 0) {
70               handleCurrentMessage(parsedMessages, sb);
71               sb = new StringBuilder("\n").append(extraMsg).append("\n");
72             }
73             else {
74               if (!PROB_SEPARATOR.equals(extraMsg)) {
75                 sb.append(extraMsg).append("\n");
76               }
77             }
78           }
79           handleCurrentMessage(parsedMessages, sb);
80         }
81       }
82     }
83     return parsedMessages;
84   }
85
86   private void handleCurrentMessage(final List<CompilerMessage> parsedMessages, final StringBuilder sb) {
87     if (sb.length() > 0) {
88       ContainerUtil.addIfNotNull(parsedMessages, parseMessage(sb.toString()));
89     }
90   }
91
92   @Nullable
93   private CompilerMessage parseMessage(String msgText) {
94     // message should look like this:
95     //        1. WARNING in /Users/andrew/git-repos/foo/src/main/java/packAction.java (at line 47)
96     //            public abstract class AbstractScmTagAction extends TaskAction implements BuildBadgeAction {
97     //                                  ^^^^^^^^^^^^^^^^^^^^
98
99     // But there will also be messages contributed from annotation processors that will look non-normal
100     int dotIndex = msgText.indexOf('.');
101     BuildMessage.Kind kind;
102     boolean isNormal = false;
103     if (dotIndex > 0) {
104       if (msgText.substring(dotIndex, dotIndex + ". WARNING".length()).equals(". WARNING")) {
105         kind = BuildMessage.Kind.WARNING;
106         isNormal = true;
107         dotIndex += ". WARNING in ".length();
108       } else if (msgText.substring(dotIndex, dotIndex + ". ERROR".length()).equals(". ERROR")) {
109         kind = BuildMessage.Kind.ERROR;
110         isNormal = true;
111         dotIndex += ". ERROR in ".length();
112       } else {
113         kind = BuildMessage.Kind.INFO;
114       }
115     } else {
116       kind = BuildMessage.Kind.INFO;
117     }
118
119     int firstNewline = msgText.indexOf('\n');
120     String firstLine = firstNewline > 0 ? msgText.substring(0, firstNewline) : msgText;
121     String rest = firstNewline > 0 ? msgText.substring(firstNewline+1).trim() : "";
122
123     if (isNormal) {
124       try {
125         int parenIndex = firstLine.indexOf(" (");
126         String file = firstLine.substring(dotIndex, parenIndex);
127         int line = Integer.parseInt(firstLine.substring(parenIndex + " (at line ".length(), firstLine.indexOf(')')));
128         int lastLineIndex = rest.lastIndexOf("\n");
129         return new CompilerMessage(myBuilderName, kind, rest.substring(lastLineIndex + 1), file, -1, -1, -1, line, -1);
130       }
131       catch (RuntimeException ignore) {
132       }
133     }
134
135     if (msgText.trim().matches("(\\d)+ problem(s)? \\((\\d)+ (error|warning)(s)?\\)")) {
136       return null;
137     }
138
139     return new CompilerMessage(myBuilderName, kind, msgText);
140   }
141
142 }