1   /*
2    *
3    *  Copyright (c) 1998-2004, The University of Sheffield.
4    *
5    *  This file is part of GATE (see http://gate.ac.uk/), and is free
6    *  software, licenced under the GNU Library General Public License,
7    *  Version 2, June 1991 (in the distribution as file licence.html,
8    *  and also available at http://gate.ac.uk/gate/licence.html).
9    *
10   *  Valentin Tablan, 18/Feb/2002
11   *
12   *  $Id: Javac.java,v 1.19 2004/07/21 17:10:09 akshay Exp $
13   */
14  package gate.util;
15  
16  import java.io.*;
17  import java.util.*;
18  
19  import com.sun.tools.javac.Main;
20  
21  import gate.Gate;
22  import gate.GateConstants;
23  import gate.creole.ExecutionException;
24  
25  /**
26   * This class copiles a set of java sources by accessing the java compiler
27   * from tools.jar file in the jdk.
28   * All processing is done without touching the disk.
29   */
30  public class Javac implements GateConstants{
31  
32    /**
33     * Compiles a set of java sources and loads the compiled classes in the gate
34     * class loader.
35     * @param sources a map from fully qualified classname to java source
36     * @throws GateException in case of a compilation error or warning.
37     * In the case of warnings the compiled classes are loaded before the error is
38     * raised.
39     */
40    public static void loadClasses(Map sources)throws GateException{
41      if(classLoader == null) classLoader = Gate.getClassLoader();
42      File workDir;
43      File srcDir;
44      File classesDir;
45      try{
46        workDir = File.createTempFile("gate", "");
47        if(!workDir.delete()) throw new GateRuntimeException(
48              "Cannot delete a temporary file!");
49        if(! workDir.mkdir())throw new GateRuntimeException(
50              "Cannot create a temporary directory!");
51        srcDir = new File(workDir, "src");
52        if(! srcDir.mkdir())throw new GateRuntimeException(
53              "Cannot create a temporary directory!");
54        classesDir = new File(workDir, "classes");
55        if(! classesDir.mkdir())throw new GateRuntimeException(
56              "Cannot create a temporary directory!");
57      }catch(IOException ioe){
58        throw new ExecutionException(ioe);
59      }
60  
61      List sourceFiles = new ArrayList();
62      List sourceListings = new ArrayList();
63  
64      Iterator fileIter = sources.keySet().iterator();
65      while(fileIter.hasNext()){
66        String className = (String)fileIter.next();
67        List pathComponents = getPathComponents(className);
68        String source = (String)sources.get(className);
69        File directory = getDirectory(srcDir, pathComponents);
70        String fileName = (String) pathComponents.get(pathComponents.size() - 1);
71        File srcFile = new File(directory, fileName + ".java");
72        try{
73          //we need to use the same encoding for writing the files and for
74          //compiling them: UTF-8 sounds like a good choice
75          Writer fw = new OutputStreamWriter(new FileOutputStream(srcFile, false),
76                                             "UTF-8");
77          fw.write(source);
78          fw.flush();fw.close();
79          sourceFiles.add(srcFile.getCanonicalPath());
80          sourceListings.add(source);
81        }catch(IOException ioe){
82          throw new GateException(ioe);
83        }
84      }
85      //all source files have now been saved to disk
86      //Prepare the arguments for the javac invocation
87      List args = new ArrayList();
88      args.add("-sourcepath");
89      args.add(srcDir.getAbsolutePath());
90      args.add("-encoding");
91      args.add("UTF-8");
92      args.add("-d");
93      args.add(classesDir.getAbsolutePath());
94      //make a copy of the arguments in case we need to call calss by class
95      List argsSave = new ArrayList(args);
96      args.addAll(sourceFiles);
97      //steal the Err stream
98      PrintStream oldErr = System.err;
99      System.setErr(new PrintStream(new ByteArrayOutputStream()));
100     //call the compiler for all the classes at once
101     int res = Main.compile((String[])args.toArray(new String[args.size()]));
102     //restore the err stream
103     System.setErr(oldErr);
104 
105     boolean errors = res != 0;
106     if(errors){
107       //we got errors: call class by class
108       args = argsSave;
109       for(int i = 0; i < sourceFiles.size(); i++){
110         String aSourceFile = (String)sourceFiles.get(i);
111         args.add(aSourceFile);
112         //call the compiler
113         res = Main.compile((String[])args.toArray(new String[args.size()]));
114         if(res != 0){
115           //javac writes the error to System.err; let's print the source as well
116           Err.prln("\nThe offending input was:\n");
117           String source = (String)sourceListings.get(i);
118           source = Strings.addLineNumbers(source);
119           Err.prln(source);
120         }
121         args.remove(args.size() -1);
122       }
123 
124     }
125 
126     //load the newly compiled classes
127     //load all classes from the classes directory
128     try{
129       loadAllClasses(classesDir, null);
130     }catch(IOException ioe){
131       throw new GateException(ioe);
132     }
133 
134     //delete the work directory
135     Files.rmdir(workDir);
136 
137     if(errors) throw new GateException(
138           "There were errors; see error log for details!");
139   }
140 
141   /**
142    * Breaks a class name into path components.
143    * @param classname
144    * @return a {@link List} of {@link String}s.
145    */
146   protected static List getPathComponents(String classname){
147     //break the classname into pieces
148     StringTokenizer strTok = new StringTokenizer(classname, ".", false);
149     List pathComponents = new ArrayList();
150     while(strTok.hasMoreTokens()){
151       String pathComponent = strTok.nextToken();
152       pathComponents.add(pathComponent);
153     }
154     return pathComponents;
155   }
156 
157   /**
158    * Gets a file inside a parent directory from a list of path components.
159    * @param workDir
160    * @param pathComponents
161    * @return a {@link File} value.
162    */
163   protected static File getDirectory(File workDir, List pathComponents){
164     File currentDir = workDir;
165     for(int i = 0; i < pathComponents.size() - 1; i++){
166       String dirName = (String)pathComponents.get(i);
167       //create a new dir in the current directory
168       currentDir = new File(currentDir, dirName);
169       if(currentDir.exists()){
170         if(currentDir.isDirectory()){
171           //nothing to do
172         }else{
173           throw new GateRuntimeException(
174             "Path exists but is not a directory ( " +
175             currentDir.toString() + ")!");
176         }
177       }else{
178         if (!currentDir.mkdir())
179           throw new GateRuntimeException(
180               "Cannot create a temporary directory!");
181       }
182     }
183     return currentDir;
184   }
185 
186   /**
187    * Loads the entire hierarchy of classes found in a parent directory.
188    * @param classesDirectory
189    */
190   protected static void loadAllClasses(File classesDirectory,
191                                        String packageName) throws IOException{
192     File[] files = classesDirectory.listFiles();
193     //adjust the package name
194     if(packageName == null){
195       //top level directory -> not a package name
196       packageName = "";
197     }else{
198       //internal directory -> a package name
199       packageName += packageName.length() == 0 ?
200                      classesDirectory.getName() :
201                      "." + classesDirectory.getName();
202     }
203 
204     for(int i = 0; i < files.length; i++){
205       if(files[i].isDirectory()) loadAllClasses(files[i], packageName);
206       else{
207         String filename = files[i].getName();
208         if(filename.endsWith(".class")){
209           String className = packageName + "." +
210                              filename.substring(0, filename.length() - 6);
211           //load the class from the file
212           byte[] bytes = Files.getByteArray(files[i]);
213           classLoader.defineGateClass(className, bytes, 0, bytes.length);
214         }
215       }
216     }
217 
218   }
219   protected static GateClassLoader classLoader;
220 }
221