package com.maketechnologies.tools.rde.compiler.ant;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.helper.AntXMLContext;
import org.apache.tools.ant.helper.ProjectHelper2;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.JAXPUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.Locator2;
import org.xml.sax.ext.Locator2Impl;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.LocatorImpl;
/**
* A project helper that caches the results of parsing Ant files, so that
* <ant/>
and <antcall/>
don't need to
* re-parse the project file.
*
* @author dgreen
*
*/
public class CachingProjectHelper extends ProjectHelper2 {
private static final Class[] DEFAULT_HANDLER_INTERFACES = new Class[] { EntityResolver.class, DTDHandler.class, ContentHandler.class, ErrorHandler.class };
private static FileUtils fileUtils = FileUtils.newFileUtils();
private static Map _recordings = new ConcurrentHashMap();
private static class RecordedEvent {
private Method _method;
private Object[] _args;
private Throwable _throw;
public RecordedEvent(Method method, Object[] args) {
_method = method;
_args = args;
if (_args != null && _args.length == 1 && _args[0] instanceof SAXParseException && method.getName().equals("fatalError")) {
_throw = (SAXParseException) args[0];
}
}
public void playback(Object target) throws Throwable {
// System.out.print("Playback " + _method.getName() + " values=");
// if (_args != null) {
// int x = 0;
// for (Object o : _args) {
// if (x > 0) {
// System.out.print(", ");
// }
// ++x;
// System.out.print(o);
// }
// }
// System.out.println();
_method.invoke(target, _args);
if (_throw != null) {
throw _throw;
}
}
}
private static class Recording {
private static final Integer ZERO = new Integer(0);
private List _recordedEvents = new ArrayList(500);
public void record(Method method, Object[] args) {
convertArgs(args);
_recordedEvents.add(new RecordedEvent(method, args));
}
public void playback(Object target) throws Throwable {
try {
for (RecordedEvent event : _recordedEvents) {
event.playback(target);
}
} catch (java.lang.reflect.InvocationTargetException ite) {
throw ite.getCause();
}
}
private void convertArgs(Object[] args) {
if (args != null) {
if (args.length == 3 && args[0] instanceof char[] && args[1] instanceof Integer && args[2] instanceof Integer) {
int length = ((Integer)args[2]).intValue();
int start = ((Integer)args[1]).intValue();
char[] buf = new char[length];
System.arraycopy(args[0], start, buf, 0, length);
args[0] = buf;
args[1] = ZERO;
} else {
for (int x = 0; x < args.length; ++x) {
if (args[x] instanceof Locator2) {
args[x] = new Locator2Impl((Locator) args[x]);
} else if (args[x] instanceof Locator) {
args[x] = new LocatorImpl((Locator) args[x]);
} else if (args[x] instanceof Attributes) {
args[x] = new AttributesImpl((Attributes) args[x]);
}
}
}
}
}
}
private static class RecordingHandler implements InvocationHandler {
private AntXMLContext context;
private Recording _recording = new Recording();
private RecordingHandler(AntXMLContext context) {
this.context = context;
}
public Recording getRecording() {
return _recording;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("resolveEntity")) {
return resolveEntity((String) args[0], (String) args[1]);
} else {
_recording.record(method, args);
return null;
}
}
public InputSource resolveEntity(String publicId, String systemId) {
context.getProject().log("resolving systemId: " + systemId, Project.MSG_VERBOSE);
if (systemId.startsWith("file:")) {
String path = fileUtils.fromURI(systemId);
File file = new File(path);
if (!file.isAbsolute()) {
file = fileUtils.resolveFile(context.getBuildFileParent(), path);
}
try {
InputSource inputSource = new InputSource(new FileInputStream(file));
inputSource.setSystemId(fileUtils.toURI(file.getAbsolutePath()));
return inputSource;
} catch (FileNotFoundException fne) {
context.getProject().log(file.getAbsolutePath() + " could not be found", Project.MSG_WARN);
}
}
// use default if not file or file not found
return null;
}
}
public CachingProjectHelper() {
}
private Recording createRecording(File buildFile, Project project, AntXMLContext context) throws BuildException {
InputStream inputStream = null;
InputSource inputSource = null;
try {
/**
* SAX 2 style parser used to parse the given file.
*/
XMLReader parser = JAXPUtils.getNamespaceXMLReader();
String uri = null;
uri = fileUtils.toURI(buildFile.getAbsolutePath());
inputStream = new BufferedInputStream(new FileInputStream(buildFile));
inputSource = new InputSource(inputStream);
if (uri != null) {
inputSource.setSystemId(uri);
}
project.log("parsing buildfile " + buildFile.getName() + " with URI = " + uri, Project.MSG_VERBOSE);
RecordingHandler recordingHandler = new RecordingHandler(context);
Object handler = Proxy.newProxyInstance(recordingHandler.getClass().getClassLoader(), DEFAULT_HANDLER_INTERFACES, recordingHandler);
parser.setContentHandler((ContentHandler) handler);
parser.setEntityResolver((EntityResolver) handler);
parser.setErrorHandler((ErrorHandler) handler);
parser.setDTDHandler((DTDHandler) handler);
parser.parse(inputSource);
return recordingHandler.getRecording();
} catch (SAXParseException exc) {
Location location = new Location(exc.getSystemId(), exc.getLineNumber(), exc.getColumnNumber());
Throwable t = exc.getException();
if (t instanceof BuildException) {
BuildException be = (BuildException) t;
if (be.getLocation() == Location.UNKNOWN_LOCATION) {
be.setLocation(location);
}
throw be;
} else if (t == null) {
t = exc;
}
throw new BuildException(exc.getMessage(), t, location);
} catch (SAXException exc) {
Throwable t = exc.getException();
if (t instanceof BuildException) {
throw (BuildException) t;
} else if (t == null) {
t = exc;
}
throw new BuildException(exc.getMessage(), t);
} catch (FileNotFoundException exc) {
throw new BuildException(exc);
} catch (UnsupportedEncodingException exc) {
throw new BuildException("Encoding of project file " + buildFile.getName() + " is invalid.", exc);
} catch (IOException exc) {
throw new BuildException("Error reading project file " + buildFile.getName() + ": " + exc.getMessage(), exc);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ioe) {
// ignore this
}
}
}
}
private Recording getRecording(File file, Project project, AntXMLContext context) throws BuildException {
Recording recording = _recordings.get(file);
if (recording == null) {
recording = createRecording(file, project, context);
if (recording != null) {
_recordings.put(file, recording);
}
}
return recording;
}
public void parse(Project project, Object source, RootHandler handler) throws BuildException {
AntXMLContext context = (AntXMLContext) project.getReference("ant.parsing.context");
if (context == null) {
throw new IllegalStateException();
}
if (source instanceof File) {
File buildFile = (File) source;
buildFile = fileUtils.normalize(buildFile.getAbsolutePath());
context.setBuildFile(buildFile);
Recording recording = getRecording(buildFile, project, context);
try {
recording.playback(handler);
} catch (SAXParseException parseException) {
Location location = new Location(parseException.getSystemId(), parseException.getLineNumber(), parseException.getColumnNumber());
Throwable t = parseException.getException();
if (t instanceof BuildException) {
BuildException be = (BuildException) t;
if (be.getLocation() == Location.UNKNOWN_LOCATION) {
be.setLocation(location);
}
throw be;
} else if (t == null) {
t = parseException;
}
throw new BuildException(parseException.getMessage(), t, location);
} catch (Throwable t) {
if (t instanceof BuildException) {
throw (BuildException) t;
}
throw new BuildException(t);
}
} else if (source instanceof URL) {
super.parse(project, source, handler);
}
}
/**
* Clear the recordings cache.
*
*/
public static void clear() {
_recordings.clear();
}
}