(ecj)Eclipse的Java編譯器分析之一——ecj介紹

Mr.Johness發表於2014-01-18

  Java是一個開放的平臺,對於除釋出編譯器/直譯器/基礎類庫之外,該語言的負責機構更多的是制定一系列標準,任何符合標準的廠商產品均可用於市場投放。甚至包括其編譯器及直譯器。

  (比如Hibernate提供了JPA實現;Tomcat實現了Java EE伺服器標準,其Servlet容器通過了Java認證;各資料庫或中介軟體廠商也根據JDBC介面開發驅動。說白了,Java基本就是都提供介面,然後讓廠商開發實現,因此有時候我會罵,邊罵邊編碼!)

  GCC有java編譯器,可以看看。

  我們主要主要介紹Eclipse自己開發和使用的針對Java的編譯器:(ecj) the Eclipse Compiler for Java。Eclipse沒有使用JDK自帶的編譯器,而是自己開發的,ecj也通過了java的驗證。

 

  除了Eclipse之外,Tomcat也用到了Ecj,用於動態編譯jsp檔案。我們安裝Tomcat後可在lib資料夾下找到ecj:

  

  現在問題來了:怎麼取得ecj原始碼呢?

  別急,我們從tomcat原始碼中檢視一下:

  

  

  雖然我不熟ant,但我也能知道,Tomcat6.0.37中使用的ecj下載路徑是:

  http://archive.eclipse.org/eclipse/downloads/drops4/R-4.2.2-201302041200/ecj-4.2.2.jar

  這個還是class檔案,剛開始我也沒轍,不過後來我靈機一動:在ecj後面加個src竟然成了!-_-

  http://archive.eclipse.org/eclipse/downloads/drops4/R-4.2.2-201302041200/ecjsrc-4.2.2.jar

  下面是我下載好後倒入專案檔案後截圖:

  

  

  這個檔案報錯,不過可以把他刪除了看,我先沒有刪除,因為這個檔案是ecj與ant的橋樑。從原始碼可以看出這個JDTCompilerAdapter是繼承自ant的DefaultCompilerAdapter,用於ant的編譯器介面卡。個人感覺ecj從程式碼(技術)上並沒有耦合任何一個呼叫者,這裡的ant也只是一個介面卡,你刪除或者留著沒有任何影響。Tomcat裡也沒有使用ant。

  我從這裡主要是想看看高層怎麼呼叫ecj來編譯程式碼,我們看看關鍵程式碼:

  

 1 private static String compilerClass = "org.eclipse.jdt.internal.compiler.batch.Main"; //$NON-NLS-1$
 2 
 3 /**
 4      * Performs a compile using the JDT batch compiler
 5      * @throws BuildException if anything wrong happen during the compilation
 6      * @return boolean true if the compilation is ok, false otherwise
 7      */
 8     public boolean execute() throws BuildException {
 9         this.attributes.log(AntAdapterMessages.getString("ant.jdtadapter.info.usingJDTCompiler"), Project.MSG_VERBOSE); //$NON-NLS-1$
10         Commandline cmd = setupJavacCommand();
11 
12         try {
13             Class c = Class.forName(compilerClass);
14             Constructor batchCompilerConstructor = 
15                     c.getConstructor(new Class[] { 
16                             PrintWriter.class, 
17                             PrintWriter.class, 
18                             Boolean.TYPE, 
19                             Map.class});
20             Object batchCompilerInstance = 
21                     batchCompilerConstructor.newInstance(new Object[] {
22                             new PrintWriter(System.out), 
23                             new PrintWriter(System.err), 
24                             Boolean.TRUE, 
25                             this.customDefaultOptions});
26             Method compile = 
27                     c.getMethod("compile", new Class[] {String[].class}); //$NON-NLS-1$
28             Object result = 
29                     compile.invoke(batchCompilerInstance, new Object[] { 
30                             cmd.getArguments()});
31             final boolean resultValue = ((Boolean) result).booleanValue();
32             if (!resultValue && this.logFileName != null) {
33                 this.attributes.log(AntAdapterMessages.getString("ant.jdtadapter.error.compilationFailed", this.logFileName)); //$NON-NLS-1$
34             }
35             return resultValue;
36         } catch (ClassNotFoundException cnfe) {
37             throw new BuildException(AntAdapterMessages.getString("ant.jdtadapter.error.cannotFindJDTCompiler")); //$NON-NLS-1$
38         } catch (Exception ex) {
39             throw new BuildException(ex);
40         }
41     }

  我把程式碼換了下行,大家看13和26行,可以看出這裡使用了

  org.eclipse.jdt.internal.compiler.batch.Main#compile(String[])方法來進行編譯,我們可以稍微看看:

  從原始碼上來看1664是配置,1684可能是編譯,不過我們先不細看。

  我們再看看Tomcat怎麼使用ecj的,我們檢視org.apache.jasper.compiler.JDTCompiler原始碼(我貼出了原始碼,不過有點長):

  

  1 /*
  2  * Licensed to the Apache Software Foundation (ASF) under one or more
  3  * contributor license agreements.  See the NOTICE file distributed with
  4  * this work for additional information regarding copyright ownership.
  5  * The ASF licenses this file to You under the Apache License, Version 2.0
  6  * (the "License"); you may not use this file except in compliance with
  7  * the License.  You may obtain a copy of the License at
  8  * 
  9  *      http://www.apache.org/licenses/LICENSE-2.0
 10  * 
 11  * Unless required by applicable law or agreed to in writing, software
 12  * distributed under the License is distributed on an "AS IS" BASIS,
 13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  * See the License for the specific language governing permissions and
 15  * limitations under the License.
 16  */
 17 
 18 package org.apache.jasper.compiler;
 19 
 20 import java.io.BufferedOutputStream;
 21 import java.io.BufferedReader;
 22 import java.io.ByteArrayOutputStream;
 23 import java.io.File;
 24 import java.io.FileInputStream;
 25 import java.io.FileNotFoundException;
 26 import java.io.FileOutputStream;
 27 import java.io.IOException;
 28 import java.io.InputStream;
 29 import java.io.InputStreamReader;
 30 import java.io.Reader;
 31 import java.util.ArrayList;
 32 import java.util.HashMap;
 33 import java.util.Locale;
 34 import java.util.Map;
 35 import java.util.StringTokenizer;
 36 
 37 import org.apache.jasper.JasperException;
 38 import org.eclipse.jdt.core.compiler.IProblem;
 39 import org.eclipse.jdt.internal.compiler.ClassFile;
 40 import org.eclipse.jdt.internal.compiler.CompilationResult;
 41 import org.eclipse.jdt.internal.compiler.Compiler;
 42 import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
 43 import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
 44 import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
 45 import org.eclipse.jdt.internal.compiler.IProblemFactory;
 46 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
 47 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
 48 import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
 49 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
 50 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 51 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
 52 
 53 /**
 54  * JDT class compiler. This compiler will load source dependencies from the
 55  * context classloader, reducing dramatically disk access during 
 56  * the compilation process.
 57  *
 58  * @author Cocoon2
 59  * @author Remy Maucherat
 60  */
 61 public class JDTCompiler extends org.apache.jasper.compiler.Compiler {
 62 
 63     
 64     /** 
 65      * Compile the servlet from .java file to .class file
 66      */
 67     protected void generateClass(String[] smap)
 68         throws FileNotFoundException, JasperException, Exception {
 69 
 70         long t1 = 0;
 71         if (log.isDebugEnabled()) {
 72             t1 = System.currentTimeMillis();
 73         }
 74         
 75         final String sourceFile = ctxt.getServletJavaFileName();
 76         final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
 77         String packageName = ctxt.getServletPackageName();
 78         final String targetClassName = 
 79             ((packageName.length() != 0) ? (packageName + ".") : "") 
 80                     + ctxt.getServletClassName();
 81         final ClassLoader classLoader = ctxt.getJspLoader();
 82         String[] fileNames = new String[] {sourceFile};
 83         String[] classNames = new String[] {targetClassName};
 84         final ArrayList problemList = new ArrayList();
 85         
 86         class CompilationUnit implements ICompilationUnit {
 87 
 88             String className;
 89             String sourceFile;
 90 
 91             CompilationUnit(String sourceFile, String className) {
 92                 this.className = className;
 93                 this.sourceFile = sourceFile;
 94             }
 95 
 96             public char[] getFileName() {
 97                 return sourceFile.toCharArray();
 98             }
 99             
100             public char[] getContents() {
101                 char[] result = null;
102                 FileInputStream is = null;
103                 try {
104                     is = new FileInputStream(sourceFile);
105                     Reader reader = 
106                         new BufferedReader(new InputStreamReader(is, ctxt.getOptions().getJavaEncoding()));
107                     if (reader != null) {
108                         char[] chars = new char[8192];
109                         StringBuffer buf = new StringBuffer();
110                         int count;
111                         while ((count = reader.read(chars, 0, 
112                                                     chars.length)) > 0) {
113                             buf.append(chars, 0, count);
114                         }
115                         result = new char[buf.length()];
116                         buf.getChars(0, result.length, result, 0);
117                     }
118                 } catch (IOException e) {
119                     log.error("Compilation error", e);
120                 } finally {
121                     if (is != null) {
122                         try {
123                             is.close();
124                         } catch (IOException exc) {
125                             // Ignore
126                         }
127                     }
128                 }
129                 return result;
130             }
131             
132             public char[] getMainTypeName() {
133                 int dot = className.lastIndexOf('.');
134                 if (dot > 0) {
135                     return className.substring(dot + 1).toCharArray();
136                 }
137                 return className.toCharArray();
138             }
139             
140             public char[][] getPackageName() {
141                 StringTokenizer izer = 
142                     new StringTokenizer(className, ".");
143                 char[][] result = new char[izer.countTokens()-1][];
144                 for (int i = 0; i < result.length; i++) {
145                     String tok = izer.nextToken();
146                     result[i] = tok.toCharArray();
147                 }
148                 return result;
149             }
150 
151             public boolean ignoreOptionalProblems() {
152                 return false;
153             }
154         }
155 
156         final INameEnvironment env = new INameEnvironment() {
157 
158                 public NameEnvironmentAnswer 
159                     findType(char[][] compoundTypeName) {
160                     String result = "";
161                     String sep = "";
162                     for (int i = 0; i < compoundTypeName.length; i++) {
163                         result += sep;
164                         result += new String(compoundTypeName[i]);
165                         sep = ".";
166                     }
167                     return findType(result);
168                 }
169 
170                 public NameEnvironmentAnswer 
171                     findType(char[] typeName, 
172                              char[][] packageName) {
173                         String result = "";
174                         String sep = "";
175                         for (int i = 0; i < packageName.length; i++) {
176                             result += sep;
177                             result += new String(packageName[i]);
178                             sep = ".";
179                         }
180                         result += sep;
181                         result += new String(typeName);
182                         return findType(result);
183                 }
184                 
185                 private NameEnvironmentAnswer findType(String className) {
186 
187                     InputStream is = null;
188                     try {
189                         if (className.equals(targetClassName)) {
190                             ICompilationUnit compilationUnit = 
191                                 new CompilationUnit(sourceFile, className);
192                             return 
193                                 new NameEnvironmentAnswer(compilationUnit, null);
194                         }
195                         String resourceName = 
196                             className.replace('.', '/') + ".class";
197                         is = classLoader.getResourceAsStream(resourceName);
198                         if (is != null) {
199                             byte[] classBytes;
200                             byte[] buf = new byte[8192];
201                             ByteArrayOutputStream baos = 
202                                 new ByteArrayOutputStream(buf.length);
203                             int count;
204                             while ((count = is.read(buf, 0, buf.length)) > 0) {
205                                 baos.write(buf, 0, count);
206                             }
207                             baos.flush();
208                             classBytes = baos.toByteArray();
209                             char[] fileName = className.toCharArray();
210                             ClassFileReader classFileReader = 
211                                 new ClassFileReader(classBytes, fileName, 
212                                                     true);
213                             return 
214                                 new NameEnvironmentAnswer(classFileReader, null);
215                         }
216                     } catch (IOException exc) {
217                         log.error("Compilation error", exc);
218                     } catch (org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException exc) {
219                         log.error("Compilation error", exc);
220                     } finally {
221                         if (is != null) {
222                             try {
223                                 is.close();
224                             } catch (IOException exc) {
225                                 // Ignore
226                             }
227                         }
228                     }
229                     return null;
230                 }
231 
232                 private boolean isPackage(String result) {
233                     if (result.equals(targetClassName)) {
234                         return false;
235                     }
236                     String resourceName = result.replace('.', '/') + ".class";
237                     InputStream is = 
238                         classLoader.getResourceAsStream(resourceName);
239                     return is == null;
240                 }
241 
242                 public boolean isPackage(char[][] parentPackageName, 
243                                          char[] packageName) {
244                     String result = "";
245                     String sep = "";
246                     if (parentPackageName != null) {
247                         for (int i = 0; i < parentPackageName.length; i++) {
248                             result += sep;
249                             String str = new String(parentPackageName[i]);
250                             result += str;
251                             sep = ".";
252                         }
253                     }
254                     String str = new String(packageName);
255                     if (Character.isUpperCase(str.charAt(0))) {
256                         if (!isPackage(result)) {
257                             return false;
258                         }
259                     }
260                     result += sep;
261                     result += str;
262                     return isPackage(result);
263                 }
264 
265                 public void cleanup() {
266                 }
267 
268             };
269 
270         final IErrorHandlingPolicy policy = 
271             DefaultErrorHandlingPolicies.proceedWithAllProblems();
272 
273         final Map settings = new HashMap();
274         settings.put(CompilerOptions.OPTION_LineNumberAttribute,
275                      CompilerOptions.GENERATE);
276         settings.put(CompilerOptions.OPTION_SourceFileAttribute,
277                      CompilerOptions.GENERATE);
278         settings.put(CompilerOptions.OPTION_ReportDeprecation,
279                      CompilerOptions.IGNORE);
280         if (ctxt.getOptions().getJavaEncoding() != null) {
281             settings.put(CompilerOptions.OPTION_Encoding,
282                     ctxt.getOptions().getJavaEncoding());
283         }
284         if (ctxt.getOptions().getClassDebugInfo()) {
285             settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
286                          CompilerOptions.GENERATE);
287         }
288 
289         // Source JVM
290         if(ctxt.getOptions().getCompilerSourceVM() != null) {
291             String opt = ctxt.getOptions().getCompilerSourceVM();
292             if(opt.equals("1.1")) {
293                 settings.put(CompilerOptions.OPTION_Source,
294                              CompilerOptions.VERSION_1_1);
295             } else if(opt.equals("1.2")) {
296                 settings.put(CompilerOptions.OPTION_Source,
297                              CompilerOptions.VERSION_1_2);
298             } else if(opt.equals("1.3")) { 
299                 settings.put(CompilerOptions.OPTION_Source,
300                              CompilerOptions.VERSION_1_3);
301             } else if(opt.equals("1.4")) {
302                 settings.put(CompilerOptions.OPTION_Source,
303                              CompilerOptions.VERSION_1_4);
304             } else if(opt.equals("1.5")) {
305                 settings.put(CompilerOptions.OPTION_Source,
306                              CompilerOptions.VERSION_1_5);
307             } else if(opt.equals("1.6")) {
308                 settings.put(CompilerOptions.OPTION_Source,
309                              CompilerOptions.VERSION_1_6);
310             } else if(opt.equals("1.7")) {
311                 settings.put(CompilerOptions.OPTION_Source,
312                              CompilerOptions.VERSION_1_7);
313             } else {
314                 log.warn("Unknown source VM " + opt + " ignored.");
315                 settings.put(CompilerOptions.OPTION_Source,
316                         CompilerOptions.VERSION_1_5);
317             }
318         } else {
319             // Default to 1.5
320             settings.put(CompilerOptions.OPTION_Source,
321                     CompilerOptions.VERSION_1_5);
322         }
323         
324         // Target JVM
325         if(ctxt.getOptions().getCompilerTargetVM() != null) {
326             String opt = ctxt.getOptions().getCompilerTargetVM();
327             if(opt.equals("1.1")) {
328                 settings.put(CompilerOptions.OPTION_TargetPlatform,
329                              CompilerOptions.VERSION_1_1);
330             } else if(opt.equals("1.2")) {
331                 settings.put(CompilerOptions.OPTION_TargetPlatform,
332                              CompilerOptions.VERSION_1_2);
333             } else if(opt.equals("1.3")) { 
334                 settings.put(CompilerOptions.OPTION_TargetPlatform,
335                              CompilerOptions.VERSION_1_3);
336             } else if(opt.equals("1.4")) {
337                 settings.put(CompilerOptions.OPTION_TargetPlatform,
338                              CompilerOptions.VERSION_1_4);
339             } else if(opt.equals("1.5")) {
340                 settings.put(CompilerOptions.OPTION_TargetPlatform,
341                              CompilerOptions.VERSION_1_5);
342                 settings.put(CompilerOptions.OPTION_Compliance,
343                         CompilerOptions.VERSION_1_5);
344             } else if(opt.equals("1.6")) {
345                 settings.put(CompilerOptions.OPTION_TargetPlatform,
346                              CompilerOptions.VERSION_1_6);
347                 settings.put(CompilerOptions.OPTION_Compliance,
348                         CompilerOptions.VERSION_1_6);
349             } else if(opt.equals("1.7")) {
350                 settings.put(CompilerOptions.OPTION_TargetPlatform,
351                              CompilerOptions.VERSION_1_7);
352                 settings.put(CompilerOptions.OPTION_Compliance,
353                         CompilerOptions.VERSION_1_7);
354             } else {
355                 log.warn("Unknown target VM " + opt + " ignored.");
356                 settings.put(CompilerOptions.OPTION_TargetPlatform,
357                         CompilerOptions.VERSION_1_5);
358             }
359         } else {
360             // Default to 1.5
361             settings.put(CompilerOptions.OPTION_TargetPlatform,
362                     CompilerOptions.VERSION_1_5);
363             settings.put(CompilerOptions.OPTION_Compliance,
364                     CompilerOptions.VERSION_1_5);
365         }
366 
367         final IProblemFactory problemFactory = 
368             new DefaultProblemFactory(Locale.getDefault());
369         
370         final ICompilerRequestor requestor = new ICompilerRequestor() {
371                 public void acceptResult(CompilationResult result) {
372                     try {
373                         if (result.hasProblems()) {
374                             IProblem[] problems = result.getProblems();
375                             for (int i = 0; i < problems.length; i++) {
376                                 IProblem problem = problems[i];
377                                 if (problem.isError()) {
378                                     String name = 
379                                         new String(problems[i].getOriginatingFileName());
380                                     try {
381                                         problemList.add(ErrorDispatcher.createJavacError
382                                                 (name, pageNodes, new StringBuffer(problem.getMessage()), 
383                                                         problem.getSourceLineNumber(), ctxt));
384                                     } catch (JasperException e) {
385                                         log.error("Error visiting node", e);
386                                     }
387                                 }
388                             }
389                         }
390                         if (problemList.isEmpty()) {
391                             ClassFile[] classFiles = result.getClassFiles();
392                             for (int i = 0; i < classFiles.length; i++) {
393                                 ClassFile classFile = classFiles[i];
394                                 char[][] compoundName = 
395                                     classFile.getCompoundName();
396                                 String className = "";
397                                 String sep = "";
398                                 for (int j = 0; 
399                                      j < compoundName.length; j++) {
400                                     className += sep;
401                                     className += new String(compoundName[j]);
402                                     sep = ".";
403                                 }
404                                 byte[] bytes = classFile.getBytes();
405                                 String outFile = outputDir + "/" + 
406                                     className.replace('.', '/') + ".class";
407                                 FileOutputStream fout = 
408                                     new FileOutputStream(outFile);
409                                 BufferedOutputStream bos = 
410                                     new BufferedOutputStream(fout);
411                                 bos.write(bytes);
412                                 bos.close();
413                             }
414                         }
415                     } catch (IOException exc) {
416                         log.error("Compilation error", exc);
417                     }
418                 }
419             };
420 
421         ICompilationUnit[] compilationUnits = 
422             new ICompilationUnit[classNames.length];
423         for (int i = 0; i < compilationUnits.length; i++) {
424             String className = classNames[i];
425             compilationUnits[i] = new CompilationUnit(fileNames[i], className);
426         }
427         Compiler compiler = new Compiler(env,
428                                          policy,
429                                          settings,
430                                          requestor,
431                                          problemFactory,
432                                          true);
433         compiler.compile(compilationUnits);
434 
435         if (!ctxt.keepGenerated()) {
436             File javaFile = new File(ctxt.getServletJavaFileName());
437             javaFile.delete();
438         }
439     
440         if (!problemList.isEmpty()) {
441             JavacErrorDetail[] jeds = 
442                 (JavacErrorDetail[]) problemList.toArray(new JavacErrorDetail[0]);
443             errDispatcher.javacError(jeds);
444         }
445         
446         if( log.isDebugEnabled() ) {
447             long t2=System.currentTimeMillis();
448             log.debug("Compiled " + ctxt.getServletJavaFileName() + " "
449                       + (t2-t1) + "ms");
450         }
451 
452         if (ctxt.isPrototypeMode()) {
453             return;
454         }
455 
456         // JSR45 Support
457         if (! options.isSmapSuppressed()) {
458             SmapUtil.installSmap(smap);
459         }
460         
461     }
462     
463     
464 }
TomcatAdapter

  

  從427可以知道,Tomcat使用了org.eclipse.jdt.internal.compiler.Compiler#compile(ICompilationUnit[])

  當然,在這之前使用了很多程式碼來進行配置。

 

  第一篇文章就到這裡。

 

 歡迎您移步我們的交流群,無聊的時候大家一起打發時間:Programmer Union

 或者通過QQ與我聯絡:點選這裡給我發訊息

 (最後編輯時間2014-01-18 13:59:01)

相關文章