/*
* Copyright (c) 2001-2004 Ant-Contrib project. All rights reserved.
*
* Licensed 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 net.sf.antcontrib.property;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.Task;
/**
* Similar to Property, but this property is mutable. In fact, much of the code
* in this class is copy and paste from Property. In general, the standard Ant
* property should be used, but occasionally it is useful to use a mutable
* property.
* <p>
* This used to be a nice little task that took advantage of what is probably
* a flaw in the Ant Project API -- setting a "user" property programatically
* causes the project to overwrite a previously set property. Now this task
* has become more violent and employs a technique known as "object rape" to
* directly access the Project's private property hashtable.
* <p>Developed for use with Antelope, migrated to ant-contrib Oct 2003.
*
* @author Dale Anson, danson@germane-software.com
* @since Ant 1.5
* @version $Revision: 1.6 $
*/
public class Variable extends Task {
// attribute storage
private String value = "";
private String name = null;
private File file = null;
private boolean remove = false;
/**
* Set the name of the property. Required unless 'file' is used.
*
* @param name the name of the property.
*/
public void setName( String name ) {
this.name = name;
}
/**
* Set the value of the property. Optional, defaults to "".
*
* @param value the value of the property.
*/
public void setValue( String value ) {
this.value = value;
}
/**
* Set the name of a file to read properties from. Optional.
*
* @param file the file to read properties from.
*/
public void setFile( File file ) {
this.file = file;
}
/**
* Determines whether the property should be removed from the project.
* Default is false. Once removed, conditions that check for property
* existence will find this property does not exist.
*
* @param b set to true to remove the property from the project.
*/
public void setUnset( boolean b ) {
remove = b;
}
/**
* Execute this task.
*
* @exception BuildException Description of the Exception
*/
public void execute() throws BuildException {
if ( remove ) {
if ( name == null || name.equals( "" ) ) {
throw new BuildException( "The 'name' attribute is required with 'unset'." );
}
removeProperty( name );
return ;
}
if ( file == null ) {
// check for the required name attribute
if ( name == null || name.equals( "" ) ) {
throw new BuildException( "The 'name' attribute is required." );
}
// check for the required value attribute
if ( value == null ) {
value = "";
}
// adjust the property value if necessary -- is this necessary?
// Doesn't Ant do this automatically?
value = getProject().replaceProperties( value );
// set the property
forceProperty( name, value );
}
else {
if ( !file.exists() ) {
throw new BuildException( file.getAbsolutePath() + " does not exists." );
}
loadFile( file );
}
}
/**
* Remove a property from the project's property table and the userProperty table.
* Note that Ant 1.6 uses a helper for this.
*/
private void removeProperty( String name ) {
Hashtable properties = null;
// Ant 1.5 stores properties in Project
try {
properties = ( Hashtable ) getValue( getProject(), "properties" );
if ( properties != null ) {
properties.remove( name );
}
}
catch ( Exception e ) {
// ignore, could be Ant 1.6
}
try {
properties = ( Hashtable ) getValue( getProject(), "userProperties" );
if ( properties != null ) {
properties.remove( name );
}
}
catch ( Exception e ) {
// ignore, could be Ant 1.6
}
// Ant 1.6 uses a PropertyHelper, can check for it by checking for a
// reference to "ant.PropertyHelper"
try {
Object property_helper = getProject().getReference( "ant.PropertyHelper" );
if ( property_helper != null ) {
try {
properties = ( Hashtable ) getValue( property_helper, "properties" );
if ( properties != null ) {
properties.remove( name );
}
}
catch ( Exception e ) {
// ignore
}
try {
properties = ( Hashtable ) getValue( property_helper, "userProperties" );
if ( properties != null ) {
properties.remove( name );
}
}
catch ( Exception e ) {
// ignore
}
}
}
catch ( Exception e ) {
// ignore, could be Ant 1.5
}
}
private void forceProperty( String name, String value ) {
try {
Hashtable properties = ( Hashtable ) getValue( getProject(), "properties" );
if ( properties == null ) {
getProject().setUserProperty( name, value );
}
else {
properties.put( name, value );
}
}
catch ( Exception e ) {
getProject().setUserProperty( name, value );
}
}
/**
* Object rape: fondle the private parts of an object without it's
* permission.
*
* @param thisClass The class to rape.
* @param fieldName The field to fondle
* @return The field value
* @exception NoSuchFieldException Darn, nothing to fondle.
*/
private Field getField( Class thisClass, String fieldName ) throws NoSuchFieldException {
if ( thisClass == null ) {
throw new NoSuchFieldException( "Invalid field : " + fieldName );
}
try {
return thisClass.getDeclaredField( fieldName );
}
catch ( NoSuchFieldException e ) {
return getField( thisClass.getSuperclass(), fieldName );
}
}
/**
* Object rape: fondle the private parts of an object without it's
* permission.
*
* @param instance the object instance
* @param fieldName the name of the field
* @return an object representing the value of the
* field
* @exception IllegalAccessException foiled by the security manager
* @exception NoSuchFieldException Darn, nothing to fondle
*/
private Object getValue( Object instance, String fieldName )
throws IllegalAccessException, NoSuchFieldException {
Field field = getField( instance.getClass(), fieldName );
field.setAccessible( true );
return field.get( instance );
}
/**
* load variables from a file
*
* @param file file to load
* @exception BuildException Description of the Exception
*/
private void loadFile( File file ) throws BuildException {
Properties props = new Properties();
try {
if ( file.exists() ) {
FileInputStream fis = new FileInputStream( file );
try {
props.load( fis );
}
finally {
if ( fis != null ) {
fis.close();
}
}
addProperties( props );
}
else {
log( "Unable to find property file: " + file.getAbsolutePath(),
Project.MSG_VERBOSE );
}
}
catch ( IOException ex ) {
throw new BuildException( ex, location );
}
}
/**
* iterate through a set of properties, resolve them, then assign them
*
* @param props The feature to be added to the Properties attribute
*/
protected void addProperties( Properties props ) {
resolveAllProperties( props );
Enumeration e = props.keys();
while ( e.hasMoreElements() ) {
String name = ( String ) e.nextElement();
String value = props.getProperty( name );
forceProperty( name, value );
}
}
/**
* resolve properties inside a properties hashtable
*
* @param props properties object to resolve
* @exception BuildException Description of the Exception
*/
private void resolveAllProperties( Properties props ) throws BuildException {
for ( Enumeration e = props.keys(); e.hasMoreElements(); ) {
String name = ( String ) e.nextElement();
String value = props.getProperty( name );
boolean resolved = false;
while ( !resolved ) {
Vector fragments = new Vector();
Vector propertyRefs = new Vector();
ProjectHelper.parsePropertyString( value, fragments,
propertyRefs );
resolved = true;
if ( propertyRefs.size() != 0 ) {
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 ( propertyName.equals( name ) ) {
throw new BuildException( "Property " + name
+ " was circularly "
+ "defined." );
}
fragment = getProject().getProperty( propertyName );
if ( fragment == null ) {
if ( props.containsKey( propertyName ) ) {
fragment = props.getProperty( propertyName );
resolved = false;
}
else {
fragment = "${" + propertyName + "}";
}
}
}
sb.append( fragment );
}
value = sb.toString();
props.put( name, value );
}
}
}
}
}