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(); } }