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              return (CompilerResult) getInvoker().invoke( null, new Object[]{ args } );
92          }
93          catch ( Exception e )
94          {
95              throw new CompilerException( e.getMessage(), e );
96          }
97      }
98  
99      private static class NonDelegatingClassLoader
100         extends URLClassLoader
101     {
102         ClassLoader original;
103 
104         public NonDelegatingClassLoader( URL[] urls, ClassLoader original )
105             throws MalformedURLException
106         {
107             super( urls, null );
108             this.original = original;
109         }
110 
111         @Override
112         public Class<?> loadClass( String name, boolean complete )
113             throws ClassNotFoundException
114         {
115             if ( name.contentEquals( CompilerResult.class.getName() ) )
116             {
117                 return original.loadClass( name );
118             }
119 
120             try
121             {
122                 synchronized ( getClassLoadingLock( name ) )
123                 {
124                     Class c = findLoadedClass( name );
125                     if ( c != null )
126                     {
127                         return c;
128                     }
129                     return findClass( name );
130                 }
131             }
132             catch ( ClassNotFoundException e )
133             {
134                 return super.loadClass( name, complete );
135             }
136         }
137     }
138 
139     private Method invokerMethod;
140 
141     private Method getInvoker()
142         throws CompilerException
143     {
144         if ( invokerMethod == null )
145         {
146             ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
147             URL[] urls = ( (URLClassLoader) contextClassLoader ).getURLs();
148             ClassLoader loader;
149             try
150             {
151                 loader = new NonDelegatingClassLoader( urls, contextClassLoader );
152                 Class<?> clazz = Class.forName( CompilerInvoker.class.getName(), true, loader );
153                 invokerMethod = clazz.getMethod( "compile", String[].class );
154             }
155             catch ( Exception e )
156             {
157                 throw new CompilerException( e.getMessage(), e );
158             }
159         }
160         return invokerMethod;
161     }
162 
163     /**
164      * A wrapper for all of the error-prone specific classes. Loading this class with a
165      * non-delegating classloader ensures that error-prone's javac loads javax.tools.* classes from
166      * javac.jar instead of from the bootclasspath.
167      */
168     public static class CompilerInvoker
169     {
170         private static class MessageListener
171             implements DiagnosticListener<JavaFileObject>
172         {
173             private final List<CompilerMessage> messages;
174 
175             MessageListener( List<CompilerMessage> messages )
176             {
177                 this.messages = messages;
178             }
179 
180             public static CompilerMessage.Kind convertKind( Diagnostic<? extends JavaFileObject> diagnostic )
181             {
182                 switch ( diagnostic.getKind() )
183                 {
184                     case ERROR:
185                         return CompilerMessage.Kind.ERROR;
186                     case WARNING:
187                         return CompilerMessage.Kind.WARNING;
188                     case MANDATORY_WARNING:
189                         return CompilerMessage.Kind.MANDATORY_WARNING;
190                     case NOTE:
191                         return CompilerMessage.Kind.NOTE;
192                     default:
193                         return CompilerMessage.Kind.OTHER;
194                 }
195             }
196 
197             public void report( Diagnostic<? extends JavaFileObject> diagnostic )
198             {
199                 CompilerMessage compilerMessage =
200                     new CompilerMessage( diagnostic.getSource() == null ? null : diagnostic.getSource().getName(), //
201                                          convertKind( diagnostic ), //
202                                          (int) diagnostic.getLineNumber(), //
203                                          (int) diagnostic.getColumnNumber(), //
204                                          -1, //
205                                          -1,
206                                          // end pos line:column is hard to calculate
207                                          diagnostic.getMessage( Locale.getDefault() ) );
208                 messages.add( compilerMessage );
209             }
210         }
211 
212         public static CompilerResult compile( String[] args )
213         {
214             List<CompilerMessage> messages = new ArrayList<CompilerMessage>();
215             ErrorProneCompiler compiler = //
216                 new ErrorProneCompiler.Builder() //
217                     .listenToDiagnostics( new MessageListener( messages ) ) //
218                     .build();
219             return new CompilerResult( compiler.compile( args ).isOK(), messages );
220         }
221     }
222 }