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 sources) throws GateException {
052 if(classLoader == null) classLoader = 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 a {@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 a {@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 }
|