Sun.java
001 /*
002  *
003  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
004  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
005  *
006  *  This file is part of GATE (see http://gate.ac.uk/), and is free
007  *  software, licenced under the GNU Library General Public License,
008  *  Version 2, June 1991 (in the distribution as file licence.html,
009  *  and also available at http://gate.ac.uk/gate/licence.html).
010  *
011  *  Valentin Tablan, 18/Feb/2002
012  *  Modified by Ian Roberts, 11/Dec/2004
013  *
014  *  $Id: Sun.java 12006 2009-12-01 17:24:28Z thomas_heitz $
015  */
016 package gate.util.compilers;
017 
018 import java.io.*;
019 import java.util.*;
020 
021 import com.sun.tools.javac.Main;
022 
023 import gate.util.*;
024 import gate.Gate;
025 import gate.GateConstants;
026 import gate.creole.ExecutionException;
027 
028 /**
029  * This class copiles a set of java sources by accessing the java
030  * compiler from tools.jar file in the jdk. As such, it will not run on
031  * a JRE alone, but requires a JDK.
032  */
033 public class Sun extends gate.util.Javac {
034 
035   public Sun() throws ClassNotFoundException {
036     // attempt to load the sun javac Main class. If this fails then the
037     // compiler selection algorithm in gate.util.Javac will fall back on
038     // the default Eclipse compiler.
039     Class.forName("com.sun.tools.javac.Main");
040   }
041 
042   /**
043    * Compiles a set of java sources and loads the compiled classes in
044    * the gate class loader.
045    
046    @param sources a map from fully qualified classname to java source
047    @throws GateException in case of a compilation error or warning. In
048    *           the case of warnings the compiled classes are loaded
049    *           before the error is raised.
050    */
051   public void compile(Map sourcesthrows GateException {
052     if(classLoader == nullclassLoader = Gate.getClassLoader();
053     File workDir;
054     File srcDir;
055     File classesDir;
056     try {
057       workDir = File.createTempFile("gate""");
058       if(!workDir.delete())
059         throw new GateRuntimeException("Cannot delete a temporary file!");
060       if(!workDir.mkdir())
061         throw new GateRuntimeException("Cannot create a temporary directory!");
062       srcDir = new File(workDir, "src");
063       if(!srcDir.mkdir())
064         throw new GateRuntimeException("Cannot create a temporary directory!");
065       classesDir = new File(workDir, "classes");
066       if(!classesDir.mkdir())
067         throw new GateRuntimeException("Cannot create a temporary directory!");
068     }
069     catch(IOException ioe) {
070       throw new ExecutionException(ioe);
071     }
072 
073     List sourceFiles = new ArrayList();
074     List sourceListings = new ArrayList();
075 
076     Iterator fileIter = sources.keySet().iterator();
077     while(fileIter.hasNext()) {
078       String className = (String)fileIter.next();
079       List pathComponents = getPathComponents(className);
080       String source = (String)sources.get(className);
081       File directory = getDirectory(srcDir, pathComponents);
082       String fileName = (String)pathComponents.get(pathComponents.size() 1);
083       File srcFile = new File(directory, fileName + ".java");
084       try {
085         // we need to use the same encoding for writing the files and
086         // for
087         // compiling them: UTF-8 sounds like a good choice
088         Writer fw = new OutputStreamWriter(
089                 new FileOutputStream(srcFile, false)"UTF-8");
090         fw.write(source);
091         fw.flush();
092         fw.close();
093         sourceFiles.add(srcFile.getCanonicalPath());
094         sourceListings.add(source);
095       }
096       catch(IOException ioe) {
097         throw new GateException(ioe);
098       }
099     }
100     // all source files have now been saved to disk
101     // Prepare the arguments for the javac invocation
102     List args = new ArrayList();
103     args.add("-sourcepath");
104     args.add(srcDir.getAbsolutePath());
105     args.add("-encoding");
106     args.add("UTF-8");
107     args.add("-d");
108     args.add(classesDir.getAbsolutePath());
109     // make a copy of the arguments in case we need to call class by
110     // class
111     List argsSave = new ArrayList(args);
112     args.addAll(sourceFiles);
113     // save the Err stream
114     PrintStream oldErr = System.err;
115     // call the compiler for all the classes at once
116     int res = -1;
117     try {
118       // steal the err stream to avoid repeating error messages.
119       // if there are errors they will be shown when compiling classes
120       // individually
121 
122       // an initial size of 10K should be plenty; it grows if required
123       // anyway
124       System.setErr(new PrintStream(new ByteArrayOutputStream(10 1024)));
125       res = Main.compile((String[])args.toArray(new String[args.size()]));
126     }
127     catch(Throwable t) {
128       // if this throws exceptions then there's nothing else we can do.
129       // restore the err stream
130       System.setErr(oldErr);
131       throw new GateRuntimeException(t);
132     }
133     finally {
134       // restore the err stream
135       System.setErr(oldErr);
136     }
137 
138     boolean errors = res != 0;
139     if(errors) {
140       // we got errors: call class by class
141       args = argsSave;
142       for(int i = 0; i < sourceFiles.size(); i++) {
143         String aSourceFile = (String)sourceFiles.get(i);
144         args.add(aSourceFile);
145         // call the compiler
146         res = Main.compile((String[])args.toArray(new String[args.size()]));
147         if(res != 0) {
148           // javac writes the error to System.err; let's print the
149           // source as well
150           Err.prln("\nThe offending input was:\n");
151           String source = (String)sourceListings.get(i);
152           source = Strings.addLineNumbers(source);
153           Err.prln(source);
154         }
155         args.remove(args.size() 1);
156       }
157 
158     }
159 
160     // load the newly compiled classes
161     // load all classes from the classes directory
162     try {
163       loadAllClasses(classesDir, null);
164     }
165     catch(IOException ioe) {
166       throw new GateException(ioe);
167     }
168 
169     // delete the work directory
170     Files.rmdir(workDir);
171 
172     if(errors)
173       throw new GateException("There were errors; see error log for details!");
174   }
175 
176   /**
177    * Breaks a class name into path components.
178    
179    @param classname
180    @return {@link List} of {@link String}s.
181    */
182   protected static List getPathComponents(String classname) {
183     // break the classname into pieces
184     StringTokenizer strTok = new StringTokenizer(classname, "."false);
185     List pathComponents = new ArrayList();
186     while(strTok.hasMoreTokens()) {
187       String pathComponent = strTok.nextToken();
188       pathComponents.add(pathComponent);
189     }
190     return pathComponents;
191   }
192 
193   /**
194    * Gets a file inside a parent directory from a list of path
195    * components.
196    
197    @param workDir
198    @param pathComponents
199    @return {@link File} value.
200    */
201   protected static File getDirectory(File workDir, List pathComponents) {
202     File currentDir = workDir;
203     for(int i = 0; i < pathComponents.size() 1; i++) {
204       String dirName = (String)pathComponents.get(i);
205       // create a new dir in the current directory
206       currentDir = new File(currentDir, dirName);
207       if(currentDir.exists()) {
208         if(currentDir.isDirectory()) {
209           // nothing to do
210         }
211         else {
212           throw new GateRuntimeException(
213                   "Path exists but is not a directory ( "
214                           + currentDir.toString() ")!");
215         }
216       }
217       else {
218         if(!currentDir.mkdir())
219           throw new GateRuntimeException("Cannot create a temporary directory!");
220       }
221     }
222     return currentDir;
223   }
224 
225   /**
226    * Loads the entire hierarchy of classes found in a parent directory.
227    
228    @param classesDirectory
229    */
230   protected static void loadAllClasses(File classesDirectory, String packageName)
231           throws IOException {
232     File[] files = classesDirectory.listFiles();
233     // adjust the package name
234     if(packageName == null) {
235       // top level directory -> not a package name
236       packageName = "";
237     }
238     else {
239       // internal directory -> a package name
240       packageName += packageName.length() == 0
241               ? classesDirectory.getName()
242               "." + classesDirectory.getName();
243     }
244 
245     for(int i = 0; i < files.length; i++) {
246       if(files[i].isDirectory())
247         loadAllClasses(files[i], packageName);
248       else {
249         String filename = files[i].getName();
250         if(filename.endsWith(".class")) {
251           String className = packageName + "."
252                   + filename.substring(0, filename.length() 6);
253           // load the class from the file
254           byte[] bytes = Files.getByteArray(files[i]);
255           classLoader.defineGateClass(className, bytes, 0, bytes.length);
256         }
257       }
258     }
259 
260   }
261 
262   protected static GateClassLoader classLoader;
263 }