001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.language.programming.java;
018:
019: import java.io.BufferedInputStream;
020: import java.io.BufferedReader;
021: import java.io.ByteArrayInputStream;
022: import java.io.ByteArrayOutputStream;
023: import java.io.IOException;
024: import java.io.OutputStream;
025: import java.util.ArrayList;
026: import java.util.List;
027: import java.util.StringTokenizer;
028:
029: import org.apache.avalon.framework.service.ServiceException;
030: import org.apache.avalon.framework.service.ServiceManager;
031: import org.apache.avalon.framework.service.Serviceable;
032: import org.apache.cocoon.components.language.programming.CompilerError;
033: import org.apache.cocoon.components.thread.RunnableManager;
034: import EDU.oswego.cs.dl.util.concurrent.CountDown;
035:
036: /**
037: * This class wraps IBM's <i>Jikes</i> Java compiler
038: * NOTE: inspired by the Apache Jasper implementation.
039: * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
040: * @version CVS $Id: Jikes.java 433543 2006-08-22 06:22:54Z crossley $
041: * @since 2.0
042: */
043:
044: public class Jikes extends AbstractJavaCompiler implements Serviceable {
045:
046: static final int OUTPUT_BUFFER_SIZE = 1024;
047: static final int BUFFER_SIZE = 512;
048:
049: private ServiceManager m_serviceManager;
050:
051: private static class StreamPumper implements Runnable {
052:
053: private BufferedInputStream stream;
054: private boolean endOfStream = false;
055: private int SLEEP_TIME = 5;
056: private OutputStream out;
057: private CountDown m_done;
058:
059: public StreamPumper(BufferedInputStream is, OutputStream out,
060: CountDown done) {
061: this .stream = is;
062: this .out = out;
063: m_done = done;
064: }
065:
066: public void pumpStream() throws IOException {
067: byte[] buf = new byte[BUFFER_SIZE];
068: if (!endOfStream) {
069: int bytesRead = stream.read(buf, 0, BUFFER_SIZE);
070:
071: if (bytesRead > 0) {
072: out.write(buf, 0, bytesRead);
073: } else if (bytesRead == -1) {
074: endOfStream = true;
075: }
076: }
077: }
078:
079: public void run() {
080: try {
081: while (!endOfStream) {
082: pumpStream();
083: Thread.sleep(SLEEP_TIME);
084: }
085: } catch (Exception e) {
086: // getLogger().warn("Jikes.run()", e);
087: }
088: m_done.release(); // signal 'we are finished'
089: }
090: }
091:
092: /**
093: * Set the {@link ServiceManager}
094: */
095: public void service(ServiceManager serviceManager)
096: throws ServiceException {
097: m_serviceManager = serviceManager;
098: }
099:
100: /**
101: * Copy arguments to a string array
102: *
103: * @param arguments The compiler arguments
104: * @return A string array containing compilation arguments
105: */
106: protected String[] toStringArray(List arguments) {
107: int i;
108:
109: for (i = 0; i < arguments.size(); i++) {
110: String arg = (String) arguments.get(i);
111: if (arg.equals("-sourcepath")) {
112: // Remove -sourcepath option. Jikes does not understand that.
113: arguments.remove(i);
114: arguments.remove(i);
115: break;
116: }
117: }
118:
119: String[] args = new String[arguments.size() + 1];
120: for (i = 0; i < arguments.size(); i++) {
121: args[i] = (String) arguments.get(i);
122: }
123:
124: args[i] = file;
125:
126: return args;
127: }
128:
129: /**
130: * Execute the compiler
131: */
132: public boolean compile() throws IOException {
133:
134: List args = new ArrayList();
135: // command line name
136: args.add("jikes");
137: // indicate Emacs output mode must be used
138: args.add("+E");
139: // avoid warnings
140: // Option nowarn with one hyphen only
141: args.add("-nowarn");
142:
143: int exitValue;
144: ByteArrayOutputStream tmpErr = new ByteArrayOutputStream(
145: OUTPUT_BUFFER_SIZE);
146:
147: try {
148: Process p = Runtime.getRuntime().exec(
149: toStringArray(fillArguments(args)));
150:
151: BufferedInputStream compilerErr = new BufferedInputStream(p
152: .getErrorStream());
153:
154: RunnableManager runnableManager = null;
155: try {
156: runnableManager = (RunnableManager) m_serviceManager
157: .lookup(RunnableManager.ROLE);
158: } catch (final ServiceException se) {
159: getLogger().error("Cannot get RunnableManager", se);
160: throw new IOException("Cannot get RunnableManager");
161: }
162:
163: final CountDown done = new CountDown(1);
164: StreamPumper errPumper = new StreamPumper(compilerErr,
165: tmpErr, done);
166: runnableManager.execute(errPumper);
167: m_serviceManager.release(runnableManager);
168:
169: p.waitFor();
170: exitValue = p.exitValue();
171:
172: done.acquire(); // Wait for StreadmPumper to finish
173: compilerErr.close();
174:
175: p.destroy();
176:
177: tmpErr.close();
178: this .errors = new ByteArrayInputStream(tmpErr.toByteArray());
179:
180: } catch (InterruptedException somethingHappened) {
181: getLogger().debug("Jikes.compile():SomethingHappened",
182: somethingHappened);
183: return false;
184: }
185:
186: // Jikes returns 0 even when there are some types of errors.
187: // Check if any error output as well
188: // Return should be OK when both exitValue and
189: // tmpErr.size() are 0 ?!
190: return ((exitValue == 0) && (tmpErr.size() == 0));
191: }
192:
193: /**
194: * Parse the compiler error stream to produce a list of
195: * <code>CompilerError</code>s
196: *
197: * @param input The error stream
198: * @return The list of compiler error messages
199: * @exception IOException If an error occurs during message collection
200: */
201: protected List parseStream(BufferedReader input) throws IOException {
202: List errors = null;
203: String line = null;
204: StringBuffer buffer = null;
205:
206: while (true) {
207: // cleanup the buffer
208: buffer = new StringBuffer(); // this is faster than clearing it
209:
210: // first line is not space-starting
211: if (line == null)
212: line = input.readLine();
213: if (line == null)
214: return errors;
215: buffer.append(line);
216:
217: // all other space-starting lines are one error
218: while (true) {
219: line = input.readLine();
220: // EOF
221: if (line == null)
222: break;
223: // Continuation of previous error starts with ' '
224: if (line.length() > 0 && line.charAt(0) != ' ')
225: break;
226: buffer.append('\n');
227: buffer.append(line);
228: }
229:
230: // if error is found create the vector
231: if (errors == null)
232: errors = new ArrayList();
233:
234: // add the error bean
235: errors.add(parseError(buffer.toString()));
236: }
237: }
238:
239: /**
240: * Parse an individual compiler error message
241: *
242: * @param error The error text
243: * @return A mssaged <code>CompilerError</code>
244: */
245: private CompilerError parseError(String error) {
246: StringTokenizer tokens = new StringTokenizer(error, ":");
247: String file = tokens.nextToken();
248: if (file.length() == 1)
249: file = new StringBuffer(file).append(":").append(
250: tokens.nextToken()).toString();
251: StringBuffer message = new StringBuffer();
252: String type = "";
253: int startline = 0;
254: int startcolumn = 0;
255: int endline = 0;
256: int endcolumn = 0;
257:
258: try {
259: startline = Integer.parseInt(tokens.nextToken());
260: startcolumn = Integer.parseInt(tokens.nextToken());
261: endline = Integer.parseInt(tokens.nextToken());
262: endcolumn = Integer.parseInt(tokens.nextToken());
263: } catch (Exception e) {
264: // FIXME: VG: This is not needed anymore?
265: message
266: .append("Please ensure that you have your JDK's rt.jar listed in your classpath. Jikes needs it to operate.");
267: type = "error";
268: getLogger().error(message.toString(), e);
269: }
270:
271: if ("".equals(message.toString())) {
272: type = tokens.nextToken().trim().toLowerCase();
273: message.append(tokens.nextToken("\n").substring(1).trim());
274:
275: while (tokens.hasMoreTokens())
276: message.append("\n").append(tokens.nextToken());
277: }
278:
279: return new CompilerError(file, type.equals("error"), startline,
280: startcolumn, endline, endcolumn, message.toString());
281: }
282:
283: public String toString() {
284: return "IBM Jikes Compiler";
285: }
286: }
|