View Javadoc
1   /*
2    * Copyright 2011 Google Inc. All Rights Reserved.
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  
17  package org.codehaus.plexus.compiler.javac.errorprone;
18  
19  import com.google.errorprone.ErrorProneCompiler;
20  import org.codehaus.plexus.compiler.AbstractCompiler;
21  import org.codehaus.plexus.compiler.CompilerConfiguration;
22  import org.codehaus.plexus.compiler.CompilerException;
23  import org.codehaus.plexus.compiler.CompilerMessage;
24  import org.codehaus.plexus.compiler.CompilerOutputStyle;
25  import org.codehaus.plexus.compiler.CompilerResult;
26  import org.codehaus.plexus.compiler.javac.JavacCompiler;
27  
28  import javax.tools.Diagnostic;
29  import javax.tools.DiagnosticListener;
30  import javax.tools.JavaFileObject;
31  import java.io.File;
32  import java.lang.reflect.Method;
33  import java.net.MalformedURLException;
34  import java.net.URL;
35  import java.net.URLClassLoader;
36  import java.util.ArrayList;
37  import java.util.List;
38  import java.util.Locale;
39  
40  /**
41   * This class overrides JavacCompiler with modifications to use the error-prone
42   * entry point into Javac.
43   *
44   * @author <a href="mailto:alexeagle@google.com">Alex Eagle</a>
45   * @plexus.component role="org.codehaus.plexus.compiler.Compiler" role-hint="javac-with-errorprone"
46   */
47  public class JavacCompilerWithErrorProne
48      extends AbstractCompiler
49  {
50      public JavacCompilerWithErrorProne()
51      {
52          super( CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE, ".java", ".class", null );
53      }
54  
55      public String[] createCommandLine( CompilerConfiguration config )
56          throws CompilerException
57      {
58          return new String[0];
59      }
60  
61      @Override
62      public CompilerResult performCompile( CompilerConfiguration config )
63          throws CompilerException
64      {
65          File destinationDir = new File( config.getOutputLocation() );
66  
67          if ( !destinationDir.exists() )
68          {
69              destinationDir.mkdirs();
70          }
71  
72          String[] sourceFiles = getSourceFiles( config );
73  
74          if ( ( sourceFiles == null ) || ( sourceFiles.length == 0 ) )
75          {
76              return new CompilerResult();
77          }
78  
79          if ( ( getLogger() != null ) && getLogger().isInfoEnabled() )
80          {
81              getLogger().info( "Compiling " + sourceFiles.length + " " //
82                                    + "source file" //
83                                    + ( sourceFiles.length == 1 ? "" : "s" ) //
84                                    + " to " + destinationDir.getAbsolutePath() );
85          }
86  
87          String[] args = JavacCompiler.buildCompilerArguments( config, sourceFiles );
88  
89          try
90          {
91              CompilerResult compilerResult = (CompilerResult) getInvoker().invoke( null, new Object[]{ args } );
92              return compilerResult;
93          }
94          catch ( Exception e )
95          {
96              throw new CompilerException( e.getMessage(), e );
97          }
98      }
99  
100     private static class NonDelegatingClassLoader
101         extends URLClassLoader
102     {
103         ClassLoader original;
104 
105         public NonDelegatingClassLoader( URL[] urls, ClassLoader original )
106             throws MalformedURLException
107         {
108             super( urls, null );
109             this.original = original;
110         }
111 
112         @Override
113         public Class<?> loadClass( String name, boolean complete )
114             throws ClassNotFoundException
115         {
116             // Classes loaded inside CompilerInvoker that need to reach back to the caller
117             if ( name.contentEquals( CompilerResult.class.getName() )
118                 || name.contentEquals( CompilerMessage.class.getName() )
119                 || name.contentEquals( CompilerMessage.Kind.class.getName() ) )
120             {
121                 return original.loadClass( name );
122             }
123 
124             try
125             {
126                 synchronized ( getClassLoadingLock( name ) )
127                 {
128                     Class c = findLoadedClass( name );
129                     if ( c != null )
130                     {
131                         return c;
132                     }
133                     return findClass( name );
134                 }
135             }
136             catch ( ClassNotFoundException e )
137             {
138                 return super.loadClass( name, complete );
139             }
140         }
141     }
142 
143     private Method invokerMethod;
144 
145     private Method getInvoker()
146         throws CompilerException
147     {
148         if ( invokerMethod == null )
149         {
150             ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
151             URL[] urls = ( (URLClassLoader) contextClassLoader ).getURLs();
152             ClassLoader loader;
153             try
154             {
155                 loader = new NonDelegatingClassLoader( urls, contextClassLoader );
156                 Class<?> clazz = Class.forName( CompilerInvoker.class.getName(), true, loader );
157                 invokerMethod = clazz.getMethod( "compile", String[].class );
158             }
159             catch ( Exception e )
160             {
161                 throw new CompilerException( e.getMessage(), e );
162             }
163         }
164         return invokerMethod;
165     }
166 
167     /**
168      * A wrapper for all of the error-prone specific classes. Loading this class with a
169      * non-delegating classloader ensures that error-prone's javac loads javax.tools.* classes from
170      * javac.jar instead of from the bootclasspath.
171      */
172     public static class CompilerInvoker
173     {
174         private static class MessageListener
175             implements DiagnosticListener<JavaFileObject>
176         {
177             private final List<CompilerMessage> messages;
178 
179             MessageListener( List<CompilerMessage> messages )
180             {
181                 this.messages = messages;
182             }
183 
184             public static CompilerMessage.Kind convertKind( Diagnostic<? extends JavaFileObject> diagnostic )
185             {
186                 switch ( diagnostic.getKind() )
187                 {
188                     case ERROR:
189                         return CompilerMessage.Kind.ERROR;
190                     case WARNING:
191                         return CompilerMessage.Kind.WARNING;
192                     case MANDATORY_WARNING:
193                         return CompilerMessage.Kind.MANDATORY_WARNING;
194                     case NOTE:
195                         return CompilerMessage.Kind.NOTE;
196                     default:
197                         return CompilerMessage.Kind.OTHER;
198                 }
199             }
200 
201             public void report( Diagnostic<? extends JavaFileObject> diagnostic )
202             {
203                 CompilerMessage compilerMessage =
204                     new CompilerMessage( diagnostic.getSource() == null ? null : diagnostic.getSource().getName(), //
205                                          convertKind( diagnostic ), //
206                                          (int) diagnostic.getLineNumber(), //
207                                          (int) diagnostic.getColumnNumber(), //
208                                          -1, //
209                                          -1,
210                                          // end pos line:column is hard to calculate
211                                          diagnostic.getMessage( Locale.getDefault() ) );
212                 messages.add( compilerMessage );
213             }
214         }
215 
216         public static CompilerResult compile( String[] args )
217         {
218             List<CompilerMessage> messages = new ArrayList<>();
219             ErrorProneCompiler compiler = //
220                 ErrorProneCompiler.builder() //
221                     .listenToDiagnostics( new MessageListener( messages ) ) //
222                     .build();
223             return new CompilerResult( compiler.run( args ).isOK(), messages );
224         }
225     }
226 }