/* * 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.Stack; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.ComponentHelper; import org.apache.tools.ant.Project; import org.apache.tools.ant.ProjectComponent; import org.apache.tools.ant.util.IdentityStack; /** * Base class for those classes that can appear inside the build file * as stand alone data types. * * <p>This class handles the common description attribute and provides * a default implementation for reference handling and checking for * circular references that is appropriate for types that can not be * nested inside elements of the same type (i.e. <patternset> * but not <path>).</p> * */ public abstract class DataType extends ProjectComponent implements Cloneable { // CheckStyle:VisibilityModifier OFF /** * Value to the refid attribute. * * @deprecated since 1.7. * The user should not be directly referencing * variable. Please use {@link #getRefid} instead. */ protected Reference ref; /** * Are we sure we don't hold circular references? * * <p>Subclasses are responsible for setting this value to false * if we'd need to investigate this condition (usually because a * child element has been added that is a subclass of * DataType).</p> * * @deprecated since 1.7. * The user should not be directly referencing * variable. Please use {@link #setChecked} or * {@link #isChecked} instead. */ protected boolean checked = true; // CheckStyle:VisibilityModifier ON /** * Has the refid attribute of this element been set? * @return true if the refid attribute has been set */ public boolean isReference() { return ref != null; } /** * Set the value of the refid attribute. * * <p>Subclasses may need to check whether any other attributes * have been set as well or child elements have been created and * thus override this method. if they do the must call * <code>super.setRefid</code>.</p> * @param ref the reference to use */ public void setRefid(final Reference ref) { this.ref = ref; checked = false; } /** * Gets as descriptive as possible a name used for this datatype instance. * @return <code>String</code> name. */ protected String getDataTypeName() { return ComponentHelper.getElementName(getProject(), this, true); } /** * Convenience method. * @since Ant 1.7 */ protected void dieOnCircularReference() { dieOnCircularReference(getProject()); } /** * Convenience method. * @param p the Ant Project instance against which to resolve references. * @since Ant 1.7 */ protected void dieOnCircularReference(Project p) { if (checked || !isReference()) { return; } dieOnCircularReference(new IdentityStack<Object>(this), p); } /** * Check to see whether any DataType we hold references to is * included in the Stack (which holds all DataType instances that * directly or indirectly reference this instance, including this * instance itself). * * <p>If one is included, throw a BuildException created by {@link * #circularReference circularReference}.</p> * * <p>This implementation is appropriate only for a DataType that * cannot hold other DataTypes as children.</p> * * <p>The general contract of this method is that it shouldn't do * anything if {@link #checked <code>checked</code>} is true and * set it to true on exit.</p> * @param stack the stack of references to check. * @param project the project to use to dereference the references. * @throws BuildException on error. */ protected void dieOnCircularReference(final Stack<Object> stack, final Project project) throws BuildException { if (checked || !isReference()) { return; } Object o = ref.getReferencedObject(project); if (o instanceof DataType) { IdentityStack<Object> id = IdentityStack.getInstance(stack); if (id.contains(o)) { throw circularReference(); } else { id.push(o); ((DataType) o).dieOnCircularReference(id, project); id.pop(); } } checked = true; } /** * Allow DataTypes outside org.apache.tools.ant.types to indirectly call * dieOnCircularReference on nested DataTypes. * @param dt the DataType to check. * @param stk the stack of references to check. * @param p the project to use to dereference the references. * @throws BuildException on error. * @since Ant 1.7 */ public static void invokeCircularReferenceCheck(DataType dt, Stack<Object> stk, Project p) { dt.dieOnCircularReference(stk, p); } /** * Allow DataTypes outside org.apache.tools.ant.types to indirectly call * dieOnCircularReference on nested DataTypes. * * <p>Pushes dt on the stack, runs dieOnCircularReference and pops * it again.</p> * @param dt the DataType to check. * @param stk the stack of references to check. * @param p the project to use to dereference the references. * @throws BuildException on error. * @since Ant 1.8.0 */ public static void pushAndInvokeCircularReferenceCheck(DataType dt, Stack<Object> stk, Project p) { stk.push(dt); dt.dieOnCircularReference(stk, p); stk.pop(); } /** * Performs the check for circular references and returns the * referenced object. * @return the dereferenced object. * @throws BuildException if the reference is invalid (circular ref, wrong class, etc). * @since Ant 1.7 */ protected Object getCheckedRef() { return getCheckedRef(getProject()); } /** * Performs the check for circular references and returns the * referenced object. * @param p the Ant Project instance against which to resolve references. * @return the dereferenced object. * @throws BuildException if the reference is invalid (circular ref, wrong class, etc). * @since Ant 1.7 */ protected Object getCheckedRef(Project p) { return getCheckedRef(getClass(), getDataTypeName(), p); } /** * Performs the check for circular references and returns the * referenced object. * @param requiredClass the class that this reference should be a subclass of. * @param dataTypeName the name of the datatype that the reference should be * (error message use only). * @return the dereferenced object. * @throws BuildException if the reference is invalid (circular ref, wrong class, etc). */ protected <T> T getCheckedRef(final Class<T> requiredClass, final String dataTypeName) { return getCheckedRef(requiredClass, dataTypeName, getProject()); } /** * Performs the check for circular references and returns the * referenced object. This version allows the fallback Project instance to be specified. * @param requiredClass the class that this reference should be a subclass of. * @param dataTypeName the name of the datatype that the reference should be * (error message use only). * @param project the fallback Project instance for dereferencing. * @return the dereferenced object. * @throws BuildException if the reference is invalid (circular ref, wrong class, etc), * or if <code>project</code> is <code>null</code>. * @since Ant 1.7 */ protected <T> T getCheckedRef(final Class<T> requiredClass, final String dataTypeName, final Project project) { if (project == null) { throw new BuildException("No Project specified"); } dieOnCircularReference(project); Object o = ref.getReferencedObject(project); if (!(requiredClass.isAssignableFrom(o.getClass()))) { log("Class " + displayName(o.getClass()) + " is not a subclass of " + displayName(requiredClass), Project.MSG_VERBOSE); String msg = ref.getRefId() + " doesn\'t denote a " + dataTypeName; throw new BuildException(msg); } @SuppressWarnings("unchecked") final T result = (T) o; return result; } /** * Creates an exception that indicates that refid has to be the * only attribute if it is set. * @return the exception to throw */ protected BuildException tooManyAttributes() { return new BuildException("You must not specify more than one " + "attribute when using refid"); } /** * Creates an exception that indicates that this XML element must * not have child elements if the refid attribute is set. * @return the exception to throw */ protected BuildException noChildrenAllowed() { return new BuildException("You must not specify nested elements " + "when using refid"); } /** * Creates an exception that indicates the user has generated a * loop of data types referencing each other. * @return the exception to throw */ protected BuildException circularReference() { return new BuildException("This data type contains a circular " + "reference."); } /** * The flag that is used to indicate that circular references have been checked. * @return true if circular references have been checked */ protected boolean isChecked() { return checked; } /** * Set the flag that is used to indicate that circular references have been checked. * @param checked if true, if circular references have been checked */ protected void setChecked(final boolean checked) { this.checked = checked; } /** * get the reference set on this object * @return the reference or null */ public Reference getRefid() { return ref; } /** * check that it is ok to set attributes, i.e that no reference is defined * @since Ant 1.6 * @throws BuildException if not allowed */ protected void checkAttributesAllowed() { if (isReference()) { throw tooManyAttributes(); } } /** * check that it is ok to add children, i.e that no reference is defined * @since Ant 1.6 * @throws BuildException if not allowed */ protected void checkChildrenAllowed() { if (isReference()) { throw noChildrenAllowed(); } } /** * Basic DataType toString(). * @return this DataType formatted as a String. */ @Override public String toString() { String d = getDescription(); return d == null ? getDataTypeName() : getDataTypeName() + " " + d; } /** * @since Ant 1.7 * @return a shallow copy of this DataType. * @throws CloneNotSupportedException if there is a problem. */ @Override public Object clone() throws CloneNotSupportedException { DataType dt = (DataType) super.clone(); dt.setDescription(getDescription()); if (getRefid() != null) { dt.setRefid(getRefid()); } dt.setChecked(isChecked()); return dt; } private String displayName(Class<?> clazz) { return clazz.getName() + " (loaded via " + clazz.getClassLoader() +")"; } }