blob: c3977600133f08939e28554647d52cc044ebd7ae [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tools.ant.types;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;
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.types.resources.PropertyResource;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.regexp.RegexpMatcher;
import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;
/**
* A set of properties.
*
* @since Ant 1.6
*/
public class PropertySet extends DataType implements ResourceCollection {
private boolean dynamic = true;
private boolean negate = false;
private Set cachedNames;
private Vector ptyRefs = new Vector();
private Vector setRefs = new Vector();
private Mapper mapper;
/**
* This is a nested class containing a reference to some properties
* and optionally a source of properties.
*/
public static class PropertyRef {
private int count;
private String name;
private String regex;
private String prefix;
private String builtin;
/**
* Set the name.
* @param name a <code>String</code> value.
*/
public void setName(String name) {
assertValid("name", name);
this.name = name;
}
/**
* Set the regular expression to use to filter the properties.
* @param regex a regular expression.
*/
public void setRegex(String regex) {
assertValid("regex", regex);
this.regex = regex;
}
/**
* Set the prefix to use.
* @param prefix a <code>String</code> value.
*/
public void setPrefix(String prefix) {
assertValid("prefix", prefix);
this.prefix = prefix;
}
/**
* Builtin property names - all, system or commandline.
* @param b an enumerated <code>BuildinPropertySetName</code> value.
*/
public void setBuiltin(BuiltinPropertySetName b) {
String pBuiltIn = b.getValue();
assertValid("builtin", pBuiltIn);
this.builtin = pBuiltIn;
}
private void assertValid(String attr, String value) {
if (value == null || value.length() < 1) {
throw new BuildException("Invalid attribute: " + attr);
}
if (++count != 1) {
throw new BuildException("Attributes name, regex, and "
+ "prefix are mutually exclusive");
}
}
/**
* A debug toString().
* @return a string version of this object.
*/
public String toString() {
return "name=" + name + ", regex=" + regex + ", prefix=" + prefix
+ ", builtin=" + builtin;
}
} //end nested class
/**
* Allow properties of a particular name in the set.
* @param name the property name to allow.
*/
public void appendName(String name) {
PropertyRef r = new PropertyRef();
r.setName(name);
addPropertyref(r);
}
/**
* Allow properties whose names match a regex in the set.
* @param regex the regular expression to use.
*/
public void appendRegex(String regex) {
PropertyRef r = new PropertyRef();
r.setRegex(regex);
addPropertyref(r);
}
/**
* Allow properties whose names start with a prefix in the set.
* @param prefix the prefix to use.
*/
public void appendPrefix(String prefix) {
PropertyRef r = new PropertyRef();
r.setPrefix(prefix);
addPropertyref(r);
}
/**
* Allow builtin (all, system or commandline) properties in the set.
* @param b the type of builtin properties.
*/
public void appendBuiltin(BuiltinPropertySetName b) {
PropertyRef r = new PropertyRef();
r.setBuiltin(b);
addPropertyref(r);
}
/**
* Set a mapper to change property names.
* @param type mapper type.
* @param from source pattern.
* @param to output pattern.
*/
public void setMapper(String type, String from, String to) {
Mapper m = createMapper();
Mapper.MapperType mapperType = new Mapper.MapperType();
mapperType.setValue(type);
m.setType(mapperType);
m.setFrom(from);
m.setTo(to);
}
/**
* Add a property reference (nested element) to the references to be used.
* @param ref a property reference.
*/
public void addPropertyref(PropertyRef ref) {
assertNotReference();
ptyRefs.addElement(ref);
}
/**
* Add another property set to this set.
* @param ref another property set.
*/
public void addPropertyset(PropertySet ref) {
assertNotReference();
setRefs.addElement(ref);
}
/**
* Create a mapper to map the property names.
* @return a mapper to be configured.
*/
public Mapper createMapper() {
assertNotReference();
if (mapper != null) {
throw new BuildException("Too many <mapper>s!");
}
mapper = new Mapper(getProject());
return mapper;
}
/**
* Add a nested FileNameMapper.
* @param fileNameMapper the mapper to add.
* @since Ant 1.6.3
*/
public void add(FileNameMapper fileNameMapper) {
createMapper().add(fileNameMapper);
}
/**
* Set whether to reevaluate the set everytime the set is used.
* Default is true.
*
* @param dynamic if true, reevaluate the property set each time
* the set is used. if false cache the property set
* the first time and use the cached set on subsequent
* occasions.
*/
public void setDynamic(boolean dynamic) {
assertNotReference();
this.dynamic = dynamic;
}
/**
* Set whether to negate results.
* If "true", all properties not selected by nested elements will be returned.
* Default is "false".
* @param negate if true, negate the selection criteria.
*/
public void setNegate(boolean negate) {
assertNotReference();
this.negate = negate;
}
/**
* Get the dynamic attribute.
* @return true if the property set is to be evalulated each time it is used.
*/
public boolean getDynamic() {
return isReference() ? getRef().dynamic : dynamic;
}
/**
* Get the mapper attribute.
* @return the mapper attribute.
*/
public Mapper getMapper() {
return isReference() ? getRef().mapper : mapper;
}
/**
* Convert the system properties to a hashtable.
* Use propertynames to get the list of properties (including
* default ones).
*/
private Hashtable getAllSystemProperties() {
Hashtable ret = new Hashtable();
for (Enumeration e = System.getProperties().propertyNames();
e.hasMoreElements();) {
String name = (String) e.nextElement();
ret.put(name, System.getProperties().getProperty(name));
}
return ret;
}
/**
* This is the operation to get the existing or recalculated properties.
* @return the properties for this propertyset.
*/
public Properties getProperties() {
if (isReference()) {
return getRef().getProperties();
}
Set names = null;
Project prj = getProject();
Hashtable props =
prj == null ? getAllSystemProperties() : prj.getProperties();
//quick & dirty, to make nested mapped p-sets work:
for (Enumeration e = setRefs.elements(); e.hasMoreElements();) {
PropertySet set = (PropertySet) e.nextElement();
props.putAll(set.getProperties());
}
if (getDynamic() || cachedNames == null) {
names = new HashSet();
addPropertyNames(names, props);
// Add this PropertySet's nested PropertySets' property names.
for (Enumeration e = setRefs.elements(); e.hasMoreElements();) {
PropertySet set = (PropertySet) e.nextElement();
names.addAll(set.getProperties().keySet());
}
if (negate) {
//make a copy...
HashSet complement = new HashSet(props.keySet());
complement.removeAll(names);
names = complement;
}
if (!getDynamic()) {
cachedNames = names;
}
} else {
names = cachedNames;
}
FileNameMapper m = null;
Mapper myMapper = getMapper();
if (myMapper != null) {
m = myMapper.getImplementation();
}
Properties properties = new Properties();
//iterate through the names, get the matching values
for (Iterator iter = names.iterator(); iter.hasNext();) {
String name = (String) iter.next();
String value = (String) props.get(name);
if (value != null) {
// may be null if a system property has been added
// after the project instance has been initialized
if (m != null) {
//map the names
String[] newname = m.mapFileName(name);
if (newname != null) {
name = newname[0];
}
}
properties.setProperty(name, value);
}
}
return properties;
}
/**
* @param names the output Set to fill with the property names
* matching this PropertySet selection criteria.
* @param properties the current Project properties, passed in to
* avoid needless duplication of the Hashtable during recursion.
*/
private void addPropertyNames(Set names, Hashtable properties) {
// Add this PropertySet's property names.
for (Enumeration e = ptyRefs.elements(); e.hasMoreElements();) {
PropertyRef r = (PropertyRef) e.nextElement();
if (r.name != null) {
if (properties.get(r.name) != null) {
names.add(r.name);
}
} else if (r.prefix != null) {
for (Enumeration p = properties.keys(); p.hasMoreElements();) {
String name = (String) p.nextElement();
if (name.startsWith(r.prefix)) {
names.add(name);
}
}
} else if (r.regex != null) {
RegexpMatcherFactory matchMaker = new RegexpMatcherFactory();
RegexpMatcher matcher = matchMaker.newRegexpMatcher();
matcher.setPattern(r.regex);
for (Enumeration p = properties.keys(); p.hasMoreElements();) {
String name = (String) p.nextElement();
if (matcher.matches(name)) {
names.add(name);
}
}
} else if (r.builtin != null) {
if (r.builtin.equals(BuiltinPropertySetName.ALL)) {
names.addAll(properties.keySet());
} else if (r.builtin.equals(BuiltinPropertySetName.SYSTEM)) {
names.addAll(System.getProperties().keySet());
} else if (r.builtin.equals(BuiltinPropertySetName
.COMMANDLINE)) {
names.addAll(getProject().getUserProperties().keySet());
} else {
throw new BuildException("Impossible: Invalid builtin "
+ "attribute!");
}
} else {
throw new BuildException("Impossible: Invalid PropertyRef!");
}
}
}
/**
* Performs the check for circular references and returns the
* referenced PropertySet.
* @return the referenced PropertySet.
*/
protected PropertySet getRef() {
return (PropertySet) getCheckedRef(PropertySet.class, "propertyset");
}
/**
* Sets the value of the refid attribute.
*
* @param r the reference this datatype should point to.
* @throws BuildException if another attribute was set, since
* refid and all other attributes are mutually exclusive.
*/
public final void setRefid(Reference r) {
if (!noAttributeSet) {
throw tooManyAttributes();
}
super.setRefid(r);
}
/**
* Ensures this data type is not a reference.
*
* <p>Calling this method as the first line of every bean method of
* this data type (setXyz, addXyz, createXyz) ensure proper handling
* of the refid attribute.</p>
*
* @throws BuildException if the refid attribute was already set, since
* refid and all other attributes are mutually exclusive.
*/
protected final void assertNotReference() {
if (isReference()) {
throw tooManyAttributes();
}
noAttributeSet = false;
}
/**
* Flag which tracks whether any attribute has been set; used by
* {@link #assertNotReference()} and {@link #setRefid(Reference)}.
*/
private boolean noAttributeSet = true;
/**
* Used for propertyref's builtin attribute.
*/
public static class BuiltinPropertySetName extends EnumeratedAttribute {
static final String ALL = "all";
static final String SYSTEM = "system";
static final String COMMANDLINE = "commandline";
/** {@inheritDoc}. */
public String[] getValues() {
return new String[] {ALL, SYSTEM, COMMANDLINE};
}
}
/**
* A debug toString.
* This gets a comma separated list of key=value pairs for
* the properties in the set.
* The output order is sorted according to the keys' <i>natural order</i>.
* @return a string rep of this object.
*/
public String toString() {
StringBuffer b = new StringBuffer();
TreeMap sorted = new TreeMap(getProperties());
for (Iterator i = sorted.entrySet().iterator(); i.hasNext();) {
Map.Entry e = (Map.Entry) i.next();
if (b.length() != 0) {
b.append(", ");
}
b.append(e.getKey().toString());
b.append("=");
b.append(e.getValue().toString());
}
return b.toString();
}
/**
* Fulfill the ResourceCollection interface.
* @return an Iterator of Resources.
* @since Ant 1.7
*/
public Iterator iterator() {
final Enumeration e = getProperties().propertyNames();
return new Iterator() {
public boolean hasNext() {
return e.hasMoreElements();
}
public Object next() {
return new PropertyResource(getProject(), (String) e.nextElement());
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Fulfill the ResourceCollection contract.
* @return the size of this ResourceCollection.
*/
public int size() {
return isReference() ? getRef().size() : getProperties().size();
}
/**
* Fulfill the ResourceCollection contract.
* @return whether this is a filesystem-only resource collection.
*/
public boolean isFilesystemOnly() {
return isReference() && getRef().isFilesystemOnly();
}
}