/*
* 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
* .
*/
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;
/**
* 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 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 /*abstract*/ class ProjectHelper {
/**
* Configures the Project with the contents of the specified XML file.
* ( should it be deprecated ? Using getProjectHelper(), parse()
* is cleaner )
*/
public static void configureProject(Project project, File buildFile)
throws BuildException
{
ProjectHelper helper=ProjectHelper.getProjectHelper();
helper.parse(project, buildFile);
}
public ProjectHelper() {
}
/**
* Constructs a new Ant parser for the specified XML file.
* @deprecated Use the plugin mechanism instead.
*/
private ProjectHelper(Project project, File buildFile) {
// this.project = project;
// this.buildFile = new File(buildFile.getAbsolutePath());
// buildFileParent = new File(this.buildFile.getParent());
}
public Project createProject(ClassLoader coreLoader) {
return new Project();
}
/**
* Process an input source for the project.
*
* All processors must support at least File sources. It is usefull to also support
* InputSource - this allows the input to come from a non-filesystem source
* (like input stream of a POST, or a soap body ).
*/
public /*abstract*/ void parse(Project project, Object source)
throws BuildException
{
throw new BuildException("You must use a real ProjectHelper implementation");
}
/* -------------------- 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.
*/
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
ClassLoader classLoader = getContextClassLoader();
ProjectHelper helper=null;
// First, try the system property
try {
String helperClass = System.getProperty(HELPER_PROPERTY);
if (helperClass != null) {
helper = newHelper(helperClass, classLoader);
}
} 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 {
InputStream is=null;
if (classLoader == null) {
is=ClassLoader.getSystemResourceAsStream( SERVICE_ID );
} else {
is=classLoader.getResourceAsStream( 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, classLoader );
}
}
} catch( Exception ex ) {
;
}
}
// Default
return new ProjectHelperImpl();
}
private static ProjectHelper newHelper(String helperClass,
ClassLoader classLoader)
throws BuildException
{
try {
Class clazz = null;
if (classLoader == null) {
clazz = Class.forName(helperClass);
} else {
clazz = classLoader.loadClass(helperClass);
}
return ((ProjectHelper) clazz.newInstance());
} catch (Exception e) {
throw new BuildException(e);
}
}
/**
*/
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);
}
/* -------------------- Common utilities and wrappers -------------------- */
/** Configure a java object using ant's rules.
*/
public static void configure(Object target, AttributeList attrs,
Project project) throws BuildException {
TaskAdapter adapter=null;
if( target instanceof TaskAdapter ) {
adapter=(TaskAdapter)target;
target=adapter.getProxy();
}
IntrospectionHelper ih =
IntrospectionHelper.getHelper(target.getClass());
if( adapter != null )
adapter.setIntrospectionHelper( ih );
// XXX What's that ?
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() );
String name=attrs.getName(i).toLowerCase(Locale.US);
try {
if (adapter!=null ) {
adapter.setAttribute( name, value );
} else {
ih.setAttribute(project, target,
name, value);
}
} catch (BuildException be) {
// id attribute must be set externally
// XXX Shuldn't it be 'name' ( i.e. lower-cased ) ?
if (!attrs.getName(i).equals("id")) {
throw be;
}
}
}
}
/**
* Adds the content of #PCDATA sections to an element.
*/
public static void addText(Project project, Object target, char[] buf, int start, int end)
throws BuildException {
addText(project, target, new String(buf, start, end));
}
/**
* Adds the content of #PCDATA sections to an element.
*/
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 into its parent object
*/
public static void storeChild(Project project, Object parent, Object child, String tag) {
IntrospectionHelper ih = IntrospectionHelper.getHelper(parent.getClass());
ih.storeElement(project, parent, child, tag);
}
/**
* Replace ${} style constructions in the given value with the string value of
* the corresponding data types.
*
* @param value the string to be scanned for property references.
* @since 1.5
*/
public static String replaceProperties(Project project, String value)
throws BuildException {
return project.replaceProperties(value);
}
/**
* Replace ${} style constructions in the given value with the string value of
* the corresponding data types.
*
* @param value the string to be scanned for property references.
*/
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();
}
/**
* This method will parse a string containing ${value} style
* property values into two lists. The first list is a collection
* of text fragments, while the other is a set of string property names
* null entries in the first list indicate a property reference from the
* second list.
*/
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));
}
}
}