blob: 61d701eec82495efaf524070659624104daa9e07 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.components.language.programming.java;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.components.language.programming.CompilerError;
import org.apache.cocoon.components.thread.RunnableManager;
import EDU.oswego.cs.dl.util.concurrent.CountDown;
/**
* This class wraps IBM's <i>Jikes</i> Java compiler
* NOTE: inspired by the Apache Jasper implementation.
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @version CVS $Id$
* @since 2.0
*/
public class Jikes extends AbstractJavaCompiler implements Serviceable {
static final int OUTPUT_BUFFER_SIZE = 1024;
static final int BUFFER_SIZE = 512;
private ServiceManager m_serviceManager;
private static class StreamPumper implements Runnable {
private BufferedInputStream stream;
private boolean endOfStream = false;
private int SLEEP_TIME = 5;
private OutputStream out;
private CountDown m_done;
public StreamPumper(BufferedInputStream is, OutputStream out, CountDown done) {
this.stream = is;
this.out = out;
m_done = done;
}
public void pumpStream() throws IOException {
byte[] buf = new byte[BUFFER_SIZE];
if (!endOfStream) {
int bytesRead = stream.read(buf, 0, BUFFER_SIZE);
if (bytesRead > 0) {
out.write(buf, 0, bytesRead);
} else if (bytesRead == -1) {
endOfStream = true;
}
}
}
public void run() {
try {
while (!endOfStream) {
pumpStream();
Thread.sleep(SLEEP_TIME);
}
} catch (Exception e) {
// getLogger().warn("Jikes.run()", e);
}
m_done.release(); // signal 'we are finished'
}
}
/**
* Set the {@link ServiceManager}
*/
public void service( ServiceManager serviceManager )
throws ServiceException
{
m_serviceManager = serviceManager;
}
/**
* Copy arguments to a string array
*
* @param arguments The compiler arguments
* @return A string array containing compilation arguments
*/
protected String[] toStringArray(List arguments) {
int i;
for (i = 0; i < arguments.size(); i++) {
String arg = (String) arguments.get(i);
if (arg.equals("-sourcepath")) {
// Remove -sourcepath option. Jikes does not understand that.
arguments.remove(i);
arguments.remove(i);
break;
}
}
String[] args = new String[arguments.size() + 1];
for (i = 0; i < arguments.size(); i++) {
args[i] = (String) arguments.get(i);
}
args[i] = file;
return args;
}
/**
* Execute the compiler
*/
public boolean compile() throws IOException {
List args = new ArrayList();
// command line name
args.add("jikes");
// indicate Emacs output mode must be used
args.add("+E");
// avoid warnings
// Option nowarn with one hyphen only
args.add("-nowarn");
int exitValue;
ByteArrayOutputStream tmpErr = new ByteArrayOutputStream(OUTPUT_BUFFER_SIZE);
try {
Process p = Runtime.getRuntime().exec(toStringArray(fillArguments(args)));
BufferedInputStream compilerErr = new BufferedInputStream(p.getErrorStream());
RunnableManager runnableManager = null;
try
{
runnableManager = (RunnableManager)m_serviceManager.lookup( RunnableManager.ROLE );
}
catch( final ServiceException se )
{
getLogger().error( "Cannot get RunnableManager", se );
throw new IOException( "Cannot get RunnableManager" );
}
final CountDown done = new CountDown( 1 );
StreamPumper errPumper = new StreamPumper(compilerErr, tmpErr, done);
runnableManager.execute( errPumper );
m_serviceManager.release( runnableManager );
p.waitFor();
exitValue = p.exitValue();
done.acquire(); // Wait for StreadmPumper to finish
compilerErr.close();
p.destroy();
tmpErr.close();
this.errors = new ByteArrayInputStream(tmpErr.toByteArray());
} catch (InterruptedException somethingHappened) {
getLogger().debug("Jikes.compile():SomethingHappened", somethingHappened);
return false;
}
// Jikes returns 0 even when there are some types of errors.
// Check if any error output as well
// Return should be OK when both exitValue and
// tmpErr.size() are 0 ?!
return ((exitValue == 0) && (tmpErr.size() == 0));
}
/**
* Parse the compiler error stream to produce a list of
* <code>CompilerError</code>s
*
* @param input The error stream
* @return The list of compiler error messages
* @exception IOException If an error occurs during message collection
*/
protected List parseStream(BufferedReader input) throws IOException {
List errors = null;
String line = null;
StringBuffer buffer = null;
while (true) {
// cleanup the buffer
buffer = new StringBuffer(); // this is faster than clearing it
// first line is not space-starting
if (line == null) line = input.readLine();
if (line == null) return errors;
buffer.append(line);
// all other space-starting lines are one error
while (true) {
line = input.readLine();
// EOF
if (line == null)
break;
// Continuation of previous error starts with ' '
if (line.length() > 0 && line.charAt(0) != ' ')
break;
buffer.append('\n');
buffer.append(line);
}
// if error is found create the vector
if (errors == null) errors = new ArrayList();
// add the error bean
errors.add(parseError(buffer.toString()));
}
}
/**
* Parse an individual compiler error message
*
* @param error The error text
* @return A mssaged <code>CompilerError</code>
*/
private CompilerError parseError(String error) {
StringTokenizer tokens = new StringTokenizer(error, ":");
String file = tokens.nextToken();
if (file.length() == 1) file = new StringBuffer(file).append(":").append(tokens.nextToken()).toString();
StringBuffer message = new StringBuffer();
String type = "";
int startline = 0;
int startcolumn = 0;
int endline = 0;
int endcolumn = 0;
try {
startline = Integer.parseInt(tokens.nextToken());
startcolumn = Integer.parseInt(tokens.nextToken());
endline = Integer.parseInt(tokens.nextToken());
endcolumn = Integer.parseInt(tokens.nextToken());
} catch (Exception e) {
// FIXME: VG: This is not needed anymore?
message.append("Please ensure that you have your JDK's rt.jar listed in your classpath. Jikes needs it to operate.");
type="error";
getLogger().error(message.toString(), e);
}
if ("".equals(message.toString())) {
type = tokens.nextToken().trim().toLowerCase();
message.append(tokens.nextToken("\n").substring(1).trim());
while (tokens.hasMoreTokens())
message.append("\n").append(tokens.nextToken());
}
return new CompilerError(file, type.equals("error"), startline, startcolumn, endline, endcolumn, message.toString());
}
public String toString() {
return "IBM Jikes Compiler";
}
}