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.22 2004/11/17 11:05:07 valyt 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 class by class
95      List argsSave = new ArrayList(args);
96      args.addAll(sourceFiles);
97      //save the Err stream
98      PrintStream oldErr = System.err;
99      //call the compiler for all the classes at once
100     int res = -1;
101     try{
102       //steal the err stream to avoid repeating error messages.
103       //if there are errors they will be shown when compiling classes 
104       //individually
105       
106       //an initial size of 10K should be plenty; it grows if required anyway
107       System.setErr(new PrintStream(new ByteArrayOutputStream(10 * 1024)));
108       res = Main.compile((String[])args.toArray(new String[args.size()]));
109     }catch(Throwable t){
110       //if this throws exceptions then there's nothing else we can do.
111       //restore the err stream
112       System.setErr(oldErr);
113       throw new GateRuntimeException(t);
114     }finally{
115       //restore the err stream
116       System.setErr(oldErr);
117     }
118     
119     boolean errors = res != 0;
120     if(errors){
121       //we got errors: call class by class
122       args = argsSave;
123       for(int i = 0; i < sourceFiles.size(); i++){
124         String aSourceFile = (String)sourceFiles.get(i);
125         args.add(aSourceFile);
126         //call the compiler
127         res = Main.compile((String[])args.toArray(new String[args.size()]));
128         if(res != 0){
129           //javac writes the error to System.err; let's print the source as well
130           Err.prln("\nThe offending input was:\n");
131           String source = (String)sourceListings.get(i);
132           source = Strings.addLineNumbers(source);
133           Err.prln(source);
134         }
135         args.remove(args.size() -1);
136       }
137 
138     }
139 
140     //load the newly compiled classes
141     //load all classes from the classes directory
142     try{
143       loadAllClasses(classesDir, null);
144     }catch(IOException ioe){
145       throw new GateException(ioe);
146     }
147 
148     //delete the work directory
149     Files.rmdir(workDir);
150 
151     if(errors) throw new GateException(
152           "There were errors; see error log for details!");
153   }
154 
155   /**
156    * Breaks a class name into path components.
157    * @param classname
158    * @return a {@link List} of {@link String}s.
159    */
160   protected static List getPathComponents(String classname){
161     //break the classname into pieces
162     StringTokenizer strTok = new StringTokenizer(classname, ".", false);
163     List pathComponents = new ArrayList();
164     while(strTok.hasMoreTokens()){
165       String pathComponent = strTok.nextToken();
166       pathComponents.add(pathComponent);
167     }
168     return pathComponents;
169   }
170 
171   /**
172    * Gets a file inside a parent directory from a list of path components.
173    * @param workDir
174    * @param pathComponents
175    * @return a {@link File} value.
176    */
177   protected static File getDirectory(File workDir, List pathComponents){
178     File currentDir = workDir;
179     for(int i = 0; i < pathComponents.size() - 1; i++){
180       String dirName = (String)pathComponents.get(i);
181       //create a new dir in the current directory
182       currentDir = new File(currentDir, dirName);
183       if(currentDir.exists()){
184         if(currentDir.isDirectory()){
185           //nothing to do
186         }else{
187           throw new GateRuntimeException(
188             "Path exists but is not a directory ( " +
189             currentDir.toString() + ")!");
190         }
191       }else{
192         if (!currentDir.mkdir())
193           throw new GateRuntimeException(
194               "Cannot create a temporary directory!");
195       }
196     }
197     return currentDir;
198   }
199 
200   /**
201    * Loads the entire hierarchy of classes found in a parent directory.
202    * @param classesDirectory
203    */
204   protected static void loadAllClasses(File classesDirectory,
205                                        String packageName) throws IOException{
206     File[] files = classesDirectory.listFiles();
207     //adjust the package name
208     if(packageName == null){
209       //top level directory -> not a package name
210       packageName = "";
211     }else{
212       //internal directory -> a package name
213       packageName += packageName.length() == 0 ?
214                      classesDirectory.getName() :
215                      "." + classesDirectory.getName();
216     }
217 
218     for(int i = 0; i < files.length; i++){
219       if(files[i].isDirectory()) loadAllClasses(files[i], packageName);
220       else{
221         String filename = files[i].getName();
222         if(filename.endsWith(".class")){
223           String className = packageName + "." +
224                              filename.substring(0, filename.length() - 6);
225           //load the class from the file
226           byte[] bytes = Files.getByteArray(files[i]);
227           classLoader.defineGateClass(className, bytes, 0, bytes.length);
228         }
229       }
230     }
231 
232   }
233   protected static GateClassLoader classLoader;
234 }
235