/* * 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.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.TreeMap; import java.util.stream.Stream; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.resources.MappedResource; 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<String> cachedNames; private List<PropertyRef> ptyRefs = new ArrayList<>(); private List<PropertySet> setRefs = new ArrayList<>(); 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. */ @Override 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(); setChecked(false); ptyRefs.add(ref); } /** * Add another property set to this set. * @param ref another property set. */ public void addPropertyset(PropertySet ref) { assertNotReference(); setChecked(false); setRefs.add(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()); setChecked(false); 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 every time 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 evaluated each time it is used. */ public boolean getDynamic() { if (isReference()) { return getRef().dynamic; } dieOnCircularReference(); return dynamic; } /** * Get the mapper attribute. * @return the mapper attribute. */ public Mapper getMapper() { if (isReference()) { return getRef().mapper; } dieOnCircularReference(); return mapper; } /** * Convert the system properties to a Map. * Use propertynames to get the list of properties (including * default ones). */ private Map<String, Object> getAllSystemProperties() { Map<String, Object> ret = new HashMap<>(); 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() { final Properties result = new Properties(); result.putAll(getPropertyMap()); return result; } /** * * @return Map * @since 1.9.0 */ private Map<String, Object> getPropertyMap() { if (isReference()) { return getRef().getPropertyMap(); } dieOnCircularReference(); final Mapper myMapper = getMapper(); final FileNameMapper m = myMapper == null ? null : myMapper.getImplementation(); final Map<String, Object> effectiveProperties = getEffectiveProperties(); final Set<String> propertyNames = getPropertyNames(effectiveProperties); final Map<String, Object> result = new HashMap<>(); //iterate through the names, get the matching values for (String name : propertyNames) { Object value = effectiveProperties.get(name); // TODO should we include null properties? // TODO should we query the PropertyHelper for property value to grab potentially shadowed values? 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]; } } result.put(name, value); } } return result; } private Map<String, Object> getEffectiveProperties() { final Project prj = getProject(); final Map<String, Object> result = prj == null ? getAllSystemProperties() : prj.getProperties(); //quick & dirty, to make nested mapped p-sets work: for (PropertySet set : setRefs) { result.putAll(set.getPropertyMap()); } return result; } private Set<String> getPropertyNames(Map<String, Object> props) { Set<String> names; if (getDynamic() || cachedNames == null) { names = new HashSet<>(); addPropertyNames(names, props); // Add this PropertySet's nested PropertySets' property names. for (PropertySet set : setRefs) { names.addAll(set.getPropertyMap().keySet()); } if (negate) { //make a copy... HashSet<String> complement = new HashSet<>(props.keySet()); complement.removeAll(names); names = complement; } if (!getDynamic()) { cachedNames = names; } } else { names = cachedNames; } return names; } /** * @param names the output Set to fill with the property names * matching this PropertySet selection criteria. * @param props the current Project properties, passed in to * avoid needless duplication of the Hashtable during recursion. */ private void addPropertyNames(Set<String> names, Map<String, Object> props) { if (isReference()) { getRef().addPropertyNames(names, props); } dieOnCircularReference(); // Add this PropertySet's property names. for (PropertyRef r : ptyRefs) { if (r.name != null) { if (props.get(r.name) != null) { names.add(r.name); } } else if (r.prefix != null) { for (String name : props.keySet()) { 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 (String name : props.keySet()) { if (matcher.matches(name)) { names.add(name); } } } else if (r.builtin != null) { if (r.builtin.equals(BuiltinPropertySetName.ALL)) { names.addAll(props.keySet()); } else if (r.builtin.equals(BuiltinPropertySetName.SYSTEM)) { names.addAll(getAllSystemProperties().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 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. */ @Override 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}. */ @Override 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. */ @Override public String toString() { if (isReference()) { return getRef().toString(); } dieOnCircularReference(); StringBuilder b = new StringBuilder(); TreeMap<String, Object> sorted = new TreeMap<>(getPropertyMap()); for (Entry<String, Object> e : sorted.entrySet()) { if (b.length() != 0) { b.append(", "); } b.append(e.getKey()); b.append("="); b.append(e.getValue()); } return b.toString(); } /** * Fulfill the ResourceCollection interface. * @return an Iterator of Resources. * @since Ant 1.7 */ @Override public Iterator<Resource> iterator() { if (isReference()) { return getRef().iterator(); } dieOnCircularReference(); Stream<Resource> result = getPropertyNames(getEffectiveProperties()) .stream().map(name -> new PropertyResource(getProject(), name)); Optional<FileNameMapper> m = Optional.ofNullable(getMapper()).map(Mapper::getImplementation); if (m.isPresent()) { result = result.map(p -> new MappedResource(p, m.get())); } return result.iterator(); } /** * Fulfill the ResourceCollection contract. * @return the size of this ResourceCollection. */ @Override public int size() { return isReference() ? getRef().size() : getProperties().size(); } /** * Fulfill the ResourceCollection contract. * @return whether this is a filesystem-only resource collection. */ @Override public boolean isFilesystemOnly() { if (isReference()) { return getRef().isFilesystemOnly(); } dieOnCircularReference(); return false; } @Override protected synchronized void dieOnCircularReference(Stack<Object> stk, Project p) throws BuildException { if (isChecked()) { return; } if (isReference()) { super.dieOnCircularReference(stk, p); } else { if (mapper != null) { pushAndInvokeCircularReferenceCheck(mapper, stk, p); } for (PropertySet propertySet : setRefs) { pushAndInvokeCircularReferenceCheck(propertySet, stk, p); } setChecked(true); } } }