001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.types;
020:
021: import java.util.Stack;
022:
023: import org.apache.tools.ant.Project;
024: import org.apache.tools.ant.BuildException;
025: import org.apache.tools.ant.ComponentHelper;
026: import org.apache.tools.ant.ProjectComponent;
027: import org.apache.tools.ant.util.IdentityStack;
028:
029: /**
030: * Base class for those classes that can appear inside the build file
031: * as stand alone data types.
032: *
033: * <p>This class handles the common description attribute and provides
034: * a default implementation for reference handling and checking for
035: * circular references that is appropriate for types that can not be
036: * nested inside elements of the same type (i.e. <patternset>
037: * but not <path>).</p>
038: *
039: */
040: public abstract class DataType extends ProjectComponent implements
041: Cloneable {
042: // CheckStyle:VisibilityModifier OFF
043:
044: /**
045: * Value to the refid attribute.
046: *
047: * @deprecated since 1.7.
048: * The user should not be directly referencing
049: * variable. Please use {@link #getRefid} instead.
050: */
051: protected Reference ref;
052:
053: /**
054: * Are we sure we don't hold circular references?
055: *
056: * <p>Subclasses are responsible for setting this value to false
057: * if we'd need to investigate this condition (usually because a
058: * child element has been added that is a subclass of
059: * DataType).</p>
060: *
061: * @deprecated since 1.7.
062: * The user should not be directly referencing
063: * variable. Please use {@link #setChecked} or
064: * {@link #isChecked} instead.
065: */
066: protected boolean checked = true;
067:
068: // CheckStyle:VisibilityModifier ON
069:
070: /**
071: * Has the refid attribute of this element been set?
072: * @return true if the refid attribute has been set
073: */
074: public boolean isReference() {
075: return ref != null;
076: }
077:
078: /**
079: * Set the value of the refid attribute.
080: *
081: * <p>Subclasses may need to check whether any other attributes
082: * have been set as well or child elements have been created and
083: * thus override this method. if they do the must call
084: * <code>super.setRefid</code>.</p>
085: * @param ref the reference to use
086: */
087: public void setRefid(final Reference ref) {
088: this .ref = ref;
089: checked = false;
090: }
091:
092: /**
093: * Gets as descriptive as possible a name used for this datatype instance.
094: * @return <code>String</code> name.
095: */
096: protected String getDataTypeName() {
097: return ComponentHelper.getElementName(getProject(), this , true);
098: }
099:
100: /**
101: * Convenience method.
102: * @since Ant 1.7
103: */
104: protected void dieOnCircularReference() {
105: dieOnCircularReference(getProject());
106: }
107:
108: /**
109: * Convenience method.
110: * @param p the Ant Project instance against which to resolve references.
111: * @since Ant 1.7
112: */
113: protected void dieOnCircularReference(Project p) {
114: if (checked || !isReference()) {
115: return;
116: }
117: dieOnCircularReference(new IdentityStack(this ), p);
118: }
119:
120: /**
121: * Check to see whether any DataType we hold references to is
122: * included in the Stack (which holds all DataType instances that
123: * directly or indirectly reference this instance, including this
124: * instance itself).
125: *
126: * <p>If one is included, throw a BuildException created by {@link
127: * #circularReference circularReference}.</p>
128: *
129: * <p>This implementation is appropriate only for a DataType that
130: * cannot hold other DataTypes as children.</p>
131: *
132: * <p>The general contract of this method is that it shouldn't do
133: * anything if {@link #checked <code>checked</code>} is true and
134: * set it to true on exit.</p>
135: * @param stack the stack of references to check.
136: * @param project the project to use to dereference the references.
137: * @throws BuildException on error.
138: */
139: protected void dieOnCircularReference(final Stack stack,
140: final Project project) throws BuildException {
141:
142: if (checked || !isReference()) {
143: return;
144: }
145: Object o = ref.getReferencedObject(project);
146:
147: if (o instanceof DataType) {
148: IdentityStack id = IdentityStack.getInstance(stack);
149:
150: if (id.contains(o)) {
151: throw circularReference();
152: } else {
153: id.push(o);
154: ((DataType) o).dieOnCircularReference(id, project);
155: id.pop();
156: }
157: }
158: checked = true;
159: }
160:
161: /**
162: * Allow DataTypes outside org.apache.tools.ant.types to indirectly call
163: * dieOnCircularReference on nested DataTypes.
164: * @param dt the DataType to check.
165: * @param stk the stack of references to check.
166: * @param p the project to use to dereference the references.
167: * @throws BuildException on error.
168: * @since Ant 1.7
169: */
170: public static void invokeCircularReferenceCheck(DataType dt,
171: Stack stk, Project p) {
172: dt.dieOnCircularReference(stk, p);
173: }
174:
175: /**
176: * Performs the check for circular references and returns the
177: * referenced object.
178: * @return the dereferenced object.
179: * @throws BuildException if the reference is invalid (circular ref, wrong class, etc).
180: * @since Ant 1.7
181: */
182: protected Object getCheckedRef() {
183: return getCheckedRef(getProject());
184: }
185:
186: /**
187: * Performs the check for circular references and returns the
188: * referenced object.
189: * @param p the Ant Project instance against which to resolve references.
190: * @return the dereferenced object.
191: * @throws BuildException if the reference is invalid (circular ref, wrong class, etc).
192: * @since Ant 1.7
193: */
194: protected Object getCheckedRef(Project p) {
195: return getCheckedRef(getClass(), getDataTypeName(), p);
196: }
197:
198: /**
199: * Performs the check for circular references and returns the
200: * referenced object.
201: * @param requiredClass the class that this reference should be a subclass of.
202: * @param dataTypeName the name of the datatype that the reference should be
203: * (error message use only).
204: * @return the dereferenced object.
205: * @throws BuildException if the reference is invalid (circular ref, wrong class, etc).
206: */
207: protected Object getCheckedRef(final Class requiredClass,
208: final String dataTypeName) {
209: return getCheckedRef(requiredClass, dataTypeName, getProject());
210: }
211:
212: /**
213: * Performs the check for circular references and returns the
214: * referenced object. This version allows the fallback Project instance to be specified.
215: * @param requiredClass the class that this reference should be a subclass of.
216: * @param dataTypeName the name of the datatype that the reference should be
217: * (error message use only).
218: * @param project the fallback Project instance for dereferencing.
219: * @return the dereferenced object.
220: * @throws BuildException if the reference is invalid (circular ref, wrong class, etc),
221: * or if <code>project</code> is <code>null</code>.
222: * @since Ant 1.7
223: */
224: protected Object getCheckedRef(final Class requiredClass,
225: final String dataTypeName, final Project project) {
226: if (project == null) {
227: throw new BuildException("No Project specified");
228: }
229: dieOnCircularReference(project);
230: Object o = ref.getReferencedObject(project);
231: if (!(requiredClass.isAssignableFrom(o.getClass()))) {
232: log("Class " + o.getClass() + " is not a subclass of "
233: + requiredClass, Project.MSG_VERBOSE);
234: String msg = ref.getRefId() + " doesn\'t denote a "
235: + dataTypeName;
236: throw new BuildException(msg);
237: }
238: return o;
239: }
240:
241: /**
242: * Creates an exception that indicates that refid has to be the
243: * only attribute if it is set.
244: * @return the exception to throw
245: */
246: protected BuildException tooManyAttributes() {
247: return new BuildException("You must not specify more than one "
248: + "attribute when using refid");
249: }
250:
251: /**
252: * Creates an exception that indicates that this XML element must
253: * not have child elements if the refid attribute is set.
254: * @return the exception to throw
255: */
256: protected BuildException noChildrenAllowed() {
257: return new BuildException(
258: "You must not specify nested elements "
259: + "when using refid");
260: }
261:
262: /**
263: * Creates an exception that indicates the user has generated a
264: * loop of data types referencing each other.
265: * @return the exception to throw
266: */
267: protected BuildException circularReference() {
268: return new BuildException("This data type contains a circular "
269: + "reference.");
270: }
271:
272: /**
273: * The flag that is used to indicate that circular references have been checked.
274: * @return true if circular references have been checked
275: */
276: protected boolean isChecked() {
277: return checked;
278: }
279:
280: /**
281: * Set the flag that is used to indicate that circular references have been checked.
282: * @param checked if true, if circular references have been checked
283: */
284: protected void setChecked(final boolean checked) {
285: this .checked = checked;
286: }
287:
288: /**
289: * get the reference set on this object
290: * @return the reference or null
291: */
292: public Reference getRefid() {
293: return ref;
294: }
295:
296: /**
297: * check that it is ok to set attributes, i.e that no reference is defined
298: * @since Ant 1.6
299: * @throws BuildException if not allowed
300: */
301: protected void checkAttributesAllowed() {
302: if (isReference()) {
303: throw tooManyAttributes();
304: }
305: }
306:
307: /**
308: * check that it is ok to add children, i.e that no reference is defined
309: * @since Ant 1.6
310: * @throws BuildException if not allowed
311: */
312: protected void checkChildrenAllowed() {
313: if (isReference()) {
314: throw noChildrenAllowed();
315: }
316: }
317:
318: /**
319: * Basic DataType toString().
320: * @return this DataType formatted as a String.
321: */
322: public String toString() {
323: String d = getDescription();
324: return d == null ? getDataTypeName() : getDataTypeName() + " "
325: + d;
326: }
327:
328: /**
329: * @since Ant 1.7
330: * @return a shallow copy of this DataType.
331: * @throws CloneNotSupportedException if there is a problem.
332: */
333: public Object clone() throws CloneNotSupportedException {
334: DataType dt = (DataType) super.clone();
335: dt.setDescription(getDescription());
336: if (getRefid() != null) {
337: dt.setRefid(getRefid());
338: }
339: dt.setChecked(isChecked());
340: return dt;
341: }
342: }
|