View Javadoc
1   package org.codehaus.plexus.compiler.javac;
2   /*
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   */
20  
21  import javax.inject.Named;
22  import javax.tools.Diagnostic;
23  import javax.tools.DiagnosticCollector;
24  import javax.tools.JavaCompiler;
25  import javax.tools.JavaFileObject;
26  import javax.tools.StandardJavaFileManager;
27  import javax.tools.ToolProvider;
28  
29  import java.nio.charset.Charset;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.concurrent.CopyOnWriteArrayList;
36  
37  import org.codehaus.plexus.compiler.CompilerConfiguration;
38  import org.codehaus.plexus.compiler.CompilerException;
39  import org.codehaus.plexus.compiler.CompilerMessage;
40  import org.codehaus.plexus.compiler.CompilerResult;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * @author Olivier Lamy
46   * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
47   * @since 2.0
48   */
49  @Named
50  public class JavaxToolsCompiler implements InProcessCompiler {
51      private final Logger log = LoggerFactory.getLogger(getClass());
52      /**
53       * is that thread safe ???
54       */
55      @SuppressWarnings("restriction")
56      private final JavaCompiler COMPILER = newJavaCompiler();
57  
58      protected JavaCompiler newJavaCompiler() {
59          return ToolProvider.getSystemJavaCompiler();
60      }
61  
62      private final List<JavaCompiler> JAVA_COMPILERS = new CopyOnWriteArrayList<>();
63  
64      private JavaCompiler getJavaCompiler(CompilerConfiguration compilerConfiguration) {
65          switch (compilerConfiguration.getCompilerReuseStrategy()) {
66              case AlwaysNew:
67                  return newJavaCompiler();
68              case ReuseCreated:
69                  JavaCompiler javaCompiler;
70                  synchronized (JAVA_COMPILERS) {
71                      if (JAVA_COMPILERS.size() > 0) {
72                          javaCompiler = JAVA_COMPILERS.get(0);
73                          JAVA_COMPILERS.remove(javaCompiler);
74                          return javaCompiler;
75                      }
76                  }
77                  javaCompiler = newJavaCompiler();
78                  return javaCompiler;
79              case ReuseSame:
80              default:
81                  return COMPILER;
82          }
83      }
84  
85      private void releaseJavaCompiler(JavaCompiler javaCompiler, CompilerConfiguration compilerConfiguration) {
86          if (javaCompiler == null) {
87              return;
88          }
89          if (compilerConfiguration.getCompilerReuseStrategy()
90                  == CompilerConfiguration.CompilerReuseStrategy.ReuseCreated) {
91              JAVA_COMPILERS.add(javaCompiler);
92          }
93      }
94  
95      public CompilerResult compileInProcess(String[] args, final CompilerConfiguration config, String[] sourceFiles)
96              throws CompilerException {
97          JavaCompiler compiler = getJavaCompiler(config);
98          try {
99              if (compiler == null) {
100                 CompilerMessage message = new CompilerMessage(
101                         "No compiler is provided in this environment. "
102                                 + "Perhaps you are running on a JRE rather than a JDK?",
103                         CompilerMessage.Kind.ERROR);
104                 return new CompilerResult(false, Collections.singletonList(message));
105             }
106             String sourceEncoding = config.getSourceEncoding();
107             Charset sourceCharset = sourceEncoding == null ? null : Charset.forName(sourceEncoding);
108             DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
109             try (StandardJavaFileManager standardFileManager =
110                     compiler.getStandardFileManager(collector, null, sourceCharset)) {
111 
112                 Iterable<? extends JavaFileObject> fileObjects =
113                         standardFileManager.getJavaFileObjectsFromStrings(Arrays.asList(sourceFiles));
114 
115                 /*(Writer out,
116                 JavaFileManager fileManager,
117                 DiagnosticListener<? super JavaFileObject> diagnosticListener,
118                 Iterable<String> options,
119                 Iterable<String> classes,
120                 Iterable<? extends JavaFileObject> compilationUnits)*/
121 
122                 List<String> arguments = Arrays.asList(args);
123 
124                 JavaCompiler.CompilationTask task =
125                         compiler.getTask(null, standardFileManager, collector, arguments, null, fileObjects);
126                 Boolean result = task.call();
127                 List<CompilerMessage> compilerMsgs = new ArrayList<>();
128 
129                 for (Diagnostic<? extends JavaFileObject> diagnostic : collector.getDiagnostics()) {
130                     CompilerMessage.Kind kind = convertKind(diagnostic);
131 
132                     String baseMessage;
133                     try {
134                         baseMessage = diagnostic.getMessage(Locale.getDefault());
135                     } catch (Throwable e) // ignore any possible error from jdk
136                     {
137                         // workaround for https://bugs.openjdk.java.net/browse/JDK-8210649
138                         // workaround for https://bugs.openjdk.java.net/browse/JDK-8216202
139                         log.debug(
140                                 "Ignore Issue get JavaCompiler Diagnostic message (see https://bugs.openjdk.java.net/browse/JDK-8210649):"
141                                         + e.getMessage(),
142                                 e);
143                         // in this case we try to replace the baseMessage with toString (hoping this does not throw a
144                         // new exception..
145                         baseMessage = diagnostic.toString();
146                     }
147                     if (baseMessage == null) {
148                         continue;
149                     }
150                     JavaFileObject source = diagnostic.getSource();
151                     String longFileName = source == null ? null : source.toUri().getPath();
152                     String shortFileName = source == null ? null : source.getName();
153                     String formattedMessage = baseMessage;
154                     int lineNumber = Math.max(0, (int) diagnostic.getLineNumber());
155                     int columnNumber = Math.max(0, (int) diagnostic.getColumnNumber());
156                     if (source != null && lineNumber > 0) {
157                         // Some compilers like to copy the file name into the message, which makes it appear twice.
158                         String possibleTrimming = longFileName + ":" + lineNumber + ": ";
159                         if (formattedMessage.startsWith(possibleTrimming)) {
160                             formattedMessage = formattedMessage.substring(possibleTrimming.length());
161                         } else {
162                             possibleTrimming = shortFileName + ":" + lineNumber + ": ";
163                             if (formattedMessage.startsWith(possibleTrimming)) {
164                                 formattedMessage = formattedMessage.substring(possibleTrimming.length());
165                             }
166                         }
167                     }
168                     compilerMsgs.add(new CompilerMessage(
169                             longFileName, kind, lineNumber, columnNumber, lineNumber, columnNumber, formattedMessage));
170                 }
171                 if (result != Boolean.TRUE && compilerMsgs.isEmpty()) {
172                     compilerMsgs.add(
173                             new CompilerMessage("An unknown compilation problem occurred", CompilerMessage.Kind.ERROR));
174                 }
175 
176                 return new CompilerResult(result, compilerMsgs);
177             }
178         } catch (Exception e) {
179             throw new CompilerException(e.getMessage(), e);
180         } finally {
181             releaseJavaCompiler(compiler, config);
182         }
183     }
184 
185     private CompilerMessage.Kind convertKind(Diagnostic<? extends JavaFileObject> diagnostic) {
186         CompilerMessage.Kind kind;
187         switch (diagnostic.getKind()) {
188             case ERROR:
189                 kind = CompilerMessage.Kind.ERROR;
190                 break;
191             case WARNING:
192                 kind = CompilerMessage.Kind.WARNING;
193                 break;
194             case MANDATORY_WARNING:
195                 kind = CompilerMessage.Kind.MANDATORY_WARNING;
196                 break;
197             case NOTE:
198                 kind = CompilerMessage.Kind.NOTE;
199                 break;
200             default:
201                 kind = CompilerMessage.Kind.OTHER;
202                 break;
203         }
204         return kind;
205     }
206 }