/* * The Apache Software License, Version 1.1 * * Copyright (c) 2000-2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Ant", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.tools.ant; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Hashtable; import java.util.Vector; import java.util.Enumeration; import java.util.Locale; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.xml.sax.AttributeList; import org.apache.tools.ant.helper.ProjectHelperImpl; /** * Configures a Project (complete with Targets and Tasks) based on * a XML build file. It'll rely on a plugin to do the actual processing * of the xml file. * * This class also provide static wrappers for common introspection. * * All helper plugins must provide backward compatiblity with the * original ant patterns, unless a different behavior is explicitely * specified. For example, if namespace is used on the <project> tag * the helper can expect the entire build file to be namespace-enabled. * Namespaces or helper-specific tags can provide meta-information to * the helper, allowing it to use new ( or different policies ). * * However, if no namespace is used the behavior should be exactly * identical with the default helper. * * @author duncan@x180.com */ public class ProjectHelper { /** * Configures the project with the contents of the specified XML file. * * @param project The project to configure. Must not be <code>null</code>. * @param buildFile An XML file giving the project's configuration. * Must not be <code>null</code>. * * @exception BuildException if the configuration is invalid or cannot * be read */ public static void configureProject(Project project, File buildFile) throws BuildException { ProjectHelper helper=ProjectHelper.getProjectHelper(); helper.parse(project, buildFile); } public ProjectHelper() { } /** * Parses the project file, configuring the project as it goes. * * @param project The project for the resulting ProjectHelper to configure. * Must not be <code>null</code>. * @param source The source for XML configuration. A helper must support * at least File, for backward compatibility. Helpers may * support URL, InputStream, etc or specialized types. * * @since Ant1.5 * @exception BuildException if the configuration is invalid or cannot * be read */ public void parse(Project project, Object source) throws BuildException { throw new BuildException("ProjectHelper.parse() must be implemented in a helper plugin " + this.getClass().getName()); } /* -------------------- Helper discovery -------------------- */ public static final String HELPER_PROPERTY = "org.apache.tools.ant.ProjectHelper"; public static final String SERVICE_ID = "/META-INF/services/org.apache.tools.ant.ProjectHelper"; /** Discover a project helper instance. Uses the same patterns * as JAXP, commons-logging, etc: a system property, a JDK1.3 * service discovery, default. */ public static ProjectHelper getProjectHelper() throws BuildException { // Identify the class loader we will be using. Ant may be // in a webapp or embeded in a different app ProjectHelper helper=null; // First, try the system property try { String helperClass = System.getProperty(HELPER_PROPERTY); if (helperClass != null) { helper = newHelper(helperClass); } } catch (SecurityException e) { // It's ok, we'll try next option ; } // A JDK1.3 'service' ( like in JAXP ). That will plug a helper // automatically if in CLASSPATH, with the right META-INF/services. if( helper==null ) { try { ClassLoader classLoader=getContextClassLoader(); InputStream is=null; if (classLoader != null) { is=classLoader.getResourceAsStream( SERVICE_ID ); } if( is==null ) { is=ClassLoader.getSystemResourceAsStream( SERVICE_ID ); } if( is != null ) { // This code is needed by EBCDIC and other strange systems. // It's a fix for bugs reported in xerces BufferedReader rd; try { rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); } catch (java.io.UnsupportedEncodingException e) { rd = new BufferedReader(new InputStreamReader(is)); } String helperClassName = rd.readLine(); rd.close(); if (helperClassName != null && ! "".equals(helperClassName)) { helper= newHelper( helperClassName ); } } } catch( Exception ex ) { ; } } // Default return new ProjectHelperImpl(); } /** Create a new helper. It'll first try the thread class loader, * then Class.forName() will load from the same loader that * loaded this class. */ private static ProjectHelper newHelper(String helperClass) throws BuildException { ClassLoader classLoader = getContextClassLoader(); try { Class clazz = null; if (classLoader != null) { try { clazz = classLoader.loadClass(helperClass); } catch( ClassNotFoundException ex ) { // try next method } } if( clazz==null ) { clazz = Class.forName(helperClass); } return ((ProjectHelper) clazz.newInstance()); } catch (Exception e) { throw new BuildException(e); } } /** * JDK1.1 compatible access to the context class loader. * Cut&paste from Jaxp. */ public static ClassLoader getContextClassLoader() throws BuildException { // Are we running on a JDK 1.2 or later system? Method method = null; try { method = Thread.class.getMethod("getContextClassLoader", null); } catch (NoSuchMethodException e) { // we are running on JDK 1.1 return null; } // Get the thread context class loader (if there is one) ClassLoader classLoader = null; try { classLoader = (ClassLoader) method.invoke(Thread.currentThread(), null); } catch (IllegalAccessException e) { throw new BuildException ("Unexpected IllegalAccessException", e); } catch (InvocationTargetException e) { throw new BuildException ("Unexpected InvocationTargetException", e); } // Return the selected class loader return (classLoader); } // -------------------- Static utils, used by most helpers -------------------- /** * Configures an object using an introspection handler. * * @param target The target object to be configured. * Must not be <code>null</code>. * @param attrs A list of attributes to configure within the target. * Must not be <code>null</code>. * @param project The project containing the target. * Must not be <code>null</code>. * * @exception BuildException if any of the attributes can't be handled by * the target */ public static void configure(Object target, AttributeList attrs, Project project) throws BuildException { if( target instanceof TaskAdapter ) { target=((TaskAdapter)target).getProxy(); } IntrospectionHelper ih = IntrospectionHelper.getHelper(target.getClass()); project.addBuildListener(ih); for (int i = 0; i < attrs.getLength(); i++) { // reflect these into the target String value=replaceProperties(project, attrs.getValue(i), project.getProperties() ); try { ih.setAttribute(project, target, attrs.getName(i).toLowerCase(Locale.US), value); } catch (BuildException be) { // id attribute must be set externally if (!attrs.getName(i).equals("id")) { throw be; } } } } /** * Adds the content of #PCDATA sections to an element. * * @param project The project containing the target. * Must not be <code>null</code>. * @param target The target object to be configured. * Must not be <code>null</code>. * @param buf A character array of the text within the element. * Will not be <code>null</code>. * @param start The start element in the array. * @param count The number of characters to read from the array. * * @exception BuildException if the target object doesn't accept text */ public static void addText(Project project, Object target, char[] buf, int start, int count) throws BuildException { addText(project, target, new String(buf, start, count)); } /** * Adds the content of #PCDATA sections to an element. * * @param project The project containing the target. * Must not be <code>null</code>. * @param target The target object to be configured. * Must not be <code>null</code>. * @param text Text to add to the target. * May be <code>null</code>, in which case this * method call is a no-op. * * @exception BuildException if the target object doesn't accept text */ public static void addText(Project project, Object target, String text) throws BuildException { if (text == null ) { return; } if(target instanceof TaskAdapter) { target = ((TaskAdapter) target).getProxy(); } IntrospectionHelper.getHelper(target.getClass()).addText(project, target, text); } /** * Stores a configured child element within its parent object. * * @param project Project containing the objects. * May be <code>null</code>. * @param parent Parent object to add child to. * Must not be <code>null</code>. * @param child Child object to store in parent. * Should not be <code>null</code>. * @param tag Name of element which generated the child. * May be <code>null</code>, in which case * the child is not stored. */ public static void storeChild(Project project, Object parent, Object child, String tag) { IntrospectionHelper ih = IntrospectionHelper.getHelper(parent.getClass()); ih.storeElement(project, parent, child, tag); } /** * Replaces <code>${xxx}</code> style constructions in the given value with * the string value of the corresponding properties. * * @param value The string to be scanned for property references. * May be <code>null</code>. * * @exception BuildException if the string contains an opening * <code>${</code> without a closing * <code>}</code> * @return the original string with the properties replaced, or * <code>null</code> if the original string is <code>null</code>. * * @since 1.5 */ public static String replaceProperties(Project project, String value) throws BuildException { return project.replaceProperties(value); } /** * Replaces <code>${xxx}</code> style constructions in the given value * with the string value of the corresponding data types. * * @param project The container project. This is used solely for * logging purposes. Must not be <code>null</code>. * @param value The string to be scanned for property references. * May be <code>null</code>, in which case this * method returns immediately with no effect. * @param keys Mapping (String to String) of property names to their * values. Must not be <code>null</code>. * * @exception BuildException if the string contains an opening * <code>${</code> without a closing * <code>}</code> * @return the original string with the properties replaced, or * <code>null</code> if the original string is <code>null</code>. */ public static String replaceProperties(Project project, String value, Hashtable keys) throws BuildException { if (value == null) { return null; } Vector fragments = new Vector(); Vector propertyRefs = new Vector(); parsePropertyString(value, fragments, propertyRefs); StringBuffer sb = new StringBuffer(); Enumeration i = fragments.elements(); Enumeration j = propertyRefs.elements(); while (i.hasMoreElements()) { String fragment = (String)i.nextElement(); if (fragment == null) { String propertyName = (String)j.nextElement(); if (!keys.containsKey(propertyName)) { project.log("Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE); } fragment = (keys.containsKey(propertyName)) ? (String) keys.get(propertyName) : "${" + propertyName + "}"; } sb.append(fragment); } return sb.toString(); } /** * Parses a string containing <code>${xxx}</code> style property * references into two lists. The first list is a collection * of text fragments, while the other is a set of string property names. * <code>null</code> entries in the first list indicate a property * reference from the second list. * * @param value Text to parse. Must not be <code>null</code>. * @param fragments List to add text fragments to. * Must not be <code>null</code>. * @param propertyRefs List to add property names to. * Must not be <code>null</code>. * * @exception BuildException if the string contains an opening * <code>${</code> without a closing * <code>}</code> */ public static void parsePropertyString(String value, Vector fragments, Vector propertyRefs) throws BuildException { int prev = 0; int pos; while ((pos = value.indexOf("$", prev)) >= 0) { if (pos > 0) { fragments.addElement(value.substring(prev, pos)); } if( pos == (value.length() - 1)) { fragments.addElement("$"); prev = pos + 1; } else if (value.charAt(pos + 1) != '{' ) { fragments.addElement(value.substring(pos + 1, pos + 2)); prev = pos + 2; } else { int endName = value.indexOf('}', pos); if (endName < 0) { throw new BuildException("Syntax error in property: " + value ); } String propertyName = value.substring(pos + 2, endName); fragments.addElement(null); propertyRefs.addElement(propertyName); prev = endName + 1; } } if (prev < value.length()) { fragments.addElement(value.substring(prev)); } } }