1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 package org.apache.tools.ant;
20
21 import java.io.Serializable;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Enumeration;
25 import java.util.Hashtable;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Iterator;
30
31 import org.apache.tools.ant.util.CollectionUtils;
32 import org.xml.sax.AttributeList;
33 import org.xml.sax.helpers.AttributeListImpl;
34
35 /**
36 * Wrapper class that holds the attributes of an element, its children, and
37 * any text within it. It then takes care of configuring that element at
38 * runtime.
39 */
40 public class RuntimeConfigurable implements Serializable {
41
42 /** Serialization version */
43 private static final long serialVersionUID = 1L;
44
45 /** Empty Hashtable. */
46 private static final Hashtable EMPTY_HASHTABLE = new Hashtable(0);
47
48 /** Name of the element to configure. */
49 private String elementTag = null;
50
51 /** List of child element wrappers. */
52 private List/*<RuntimeConfigurable>*/ children = null;
53
54 /** The element to configure. It is only used during
55 * maybeConfigure.
56 */
57 private transient Object wrappedObject = null;
58
59 /** the creator used to make the wrapped object */
60 private transient IntrospectionHelper.Creator creator;
61
62 /**
63 * XML attributes for the element.
64 * @deprecated since 1.6.x
65 */
66 private transient AttributeList attributes;
67
68 /** Attribute names and values. While the XML spec doesn't require
69 * preserving the order ( AFAIK ), some ant tests do rely on the
70 * exact order.
71 * The only exception to this order is the treatment of
72 * refid. A number of datatypes check if refid is set
73 * when other attributes are set. This check will not
74 * work if the build script has the other attribute before
75 * the "refid" attribute, so now (ANT 1.7) the refid
76 * attribute will be processed first.
77 */
78 private LinkedHashMap/*<String, String>*/ attributeMap = null;
79
80 /** Text appearing within the element. */
81 private StringBuffer characters = null;
82
83 /** Indicates if the wrapped object has been configured */
84 private boolean proxyConfigured = false;
85
86 /** the polymorphic type */
87 private String polyType = null;
88
89 /** the "id" of this Element if it has one */
90 private String id = null;
91
92 /**
93 * Sole constructor creating a wrapper for the specified object.
94 *
95 * @param proxy The element to configure. Must not be <code>null</code>.
96 * @param elementTag The tag name generating this element.
97 */
98 public RuntimeConfigurable(Object proxy, String elementTag) {
99 setProxy(proxy);
100 setElementTag(elementTag);
101 // Most likely an UnknownElement
102 if (proxy instanceof Task) {
103 ((Task) proxy).setRuntimeConfigurableWrapper(this);
104 }
105 }
106
107 /**
108 * Sets the element to configure.
109 *
110 * @param proxy The element to configure. Must not be <code>null</code>.
111 */
112 public synchronized void setProxy(Object proxy) {
113 wrappedObject = proxy;
114 proxyConfigured = false;
115 }
116
117 /**
118 * Sets the creator of the element to be configured
119 * used to store the element in the parent.
120 *
121 * @param creator the creator object.
122 */
123 synchronized void setCreator(IntrospectionHelper.Creator creator) {
124 this.creator = creator;
125 }
126
127 /**
128 * Get the object for which this RuntimeConfigurable holds the configuration
129 * information.
130 *
131 * @return the object whose configure is held by this instance.
132 */
133 public synchronized Object getProxy() {
134 return wrappedObject;
135 }
136
137 /**
138 * Returns the id for this element.
139 * @return the id.
140 */
141 public synchronized String getId() {
142 return id;
143 }
144
145 /**
146 * Get the polymorphic type for this element.
147 * @return the ant component type name, null if not set.
148 */
149 public synchronized String getPolyType() {
150 return polyType;
151 }
152
153 /**
154 * Set the polymorphic type for this element.
155 * @param polyType the ant component type name, null if not set.
156 */
157 public synchronized void setPolyType(String polyType) {
158 this.polyType = polyType;
159 }
160
161 /**
162 * Sets the attributes for the wrapped element.
163 *
164 * @deprecated since 1.6.x.
165 * @param attributes List of attributes defined in the XML for this
166 * element. May be <code>null</code>.
167 */
168 public synchronized void setAttributes(AttributeList attributes) {
169 this.attributes = new AttributeListImpl(attributes);
170 for (int i = 0; i < attributes.getLength(); i++) {
171 setAttribute(attributes.getName(i), attributes.getValue(i));
172 }
173 }
174
175 /**
176 * Set an attribute to a given value.
177 *
178 * @param name the name of the attribute.
179 * @param value the attribute's value.
180 */
181 public synchronized void setAttribute(String name, String value) {
182 if (name.equalsIgnoreCase(ProjectHelper.ANT_TYPE)) {
183 this.polyType = value;
184 } else {
185 if (attributeMap == null) {
186 attributeMap = new LinkedHashMap();
187 }
188 if (name.equalsIgnoreCase("refid") && !attributeMap.isEmpty()) {
189 LinkedHashMap newAttributeMap = new LinkedHashMap();
190 newAttributeMap.put(name, value);
191 newAttributeMap.putAll(attributeMap);
192 attributeMap = newAttributeMap;
193 } else {
194 attributeMap.put(name, value);
195 }
196 if (name.equals("id")) {
197 this.id = value;
198 }
199 }
200 }
201
202 /**
203 * Delete an attribute. Not for the faint of heart.
204 * @param name the name of the attribute to be removed.
205 */
206 public synchronized void removeAttribute(String name) {
207 attributeMap.remove(name);
208 }
209
210 /**
211 * Return the attribute map.
212 *
213 * @return Attribute name to attribute value map.
214 * @since Ant 1.6
215 */
216 public synchronized Hashtable getAttributeMap() {
217 return (attributeMap == null)
218 ? EMPTY_HASHTABLE : new Hashtable(attributeMap);
219 }
220
221 /**
222 * Returns the list of attributes for the wrapped element.
223 *
224 * @deprecated Deprecated since Ant 1.6 in favor of {@link #getAttributeMap}.
225 * @return An AttributeList representing the attributes defined in the
226 * XML for this element. May be <code>null</code>.
227 */
228 public synchronized AttributeList getAttributes() {
229 return attributes;
230 }
231
232 /**
233 * Adds a child element to the wrapped element.
234 *
235 * @param child The child element wrapper to add to this one.
236 * Must not be <code>null</code>.
237 */
238 public synchronized void addChild(RuntimeConfigurable child) {
239 children = (children == null) ? new ArrayList() : children;
240 children.add(child);
241 }
242
243 /**
244 * Returns the child wrapper at the specified position within the list.
245 *
246 * @param index The index of the child to return.
247 *
248 * @return The child wrapper at position <code>index</code> within the
249 * list.
250 */
251 synchronized RuntimeConfigurable getChild(int index) {
252 return (RuntimeConfigurable) children.get(index);
253 }
254
255 /**
256 * Returns an enumeration of all child wrappers.
257 * @return an enumeration of the child wrappers.
258 * @since Ant 1.6
259 */
260 public synchronized Enumeration getChildren() {
261 return (children == null) ? new CollectionUtils.EmptyEnumeration()
262 : Collections.enumeration(children);
263 }
264
265 /**
266 * Adds characters from #PCDATA areas to the wrapped element.
267 *
268 * @param data Text to add to the wrapped element.
269 * Should not be <code>null</code>.
270 */
271 public synchronized void addText(String data) {
272 if (data.length() == 0) {
273 return;
274 }
275 characters = (characters == null)
276 ? new StringBuffer(data) : characters.append(data);
277 }
278
279 /**
280 * Adds characters from #PCDATA areas to the wrapped element.
281 *
282 * @param buf A character array of the text within the element.
283 * Must not be <code>null</code>.
284 * @param start The start element in the array.
285 * @param count The number of characters to read from the array.
286 *
287 */
288 public synchronized void addText(char[] buf, int start, int count) {
289 if (count == 0) {
290 return;
291 }
292 characters = ((characters == null)
293 ? new StringBuffer(count) : characters).append(buf, start, count);
294 }
295
296 /**
297 * Get the text content of this element. Various text chunks are
298 * concatenated, there is no way ( currently ) of keeping track of
299 * multiple fragments.
300 *
301 * @return the text content of this element.
302 * @since Ant 1.6
303 */
304 public synchronized StringBuffer getText() {
305 return (characters == null) ? new StringBuffer(0) : characters;
306 }
307
308 /**
309 * Set the element tag.
310 * @param elementTag The tag name generating this element.
311 */
312 public synchronized void setElementTag(String elementTag) {
313 this.elementTag = elementTag;
314 }
315
316 /**
317 * Returns the tag name of the wrapped element.
318 *
319 * @return The tag name of the wrapped element. This is unlikely
320 * to be <code>null</code>, but may be.
321 */
322 public synchronized String getElementTag() {
323 return elementTag;
324 }
325
326 /**
327 * Configures the wrapped element and all its children.
328 * The attributes and text for the wrapped element are configured,
329 * and then each child is configured and added. Each time the
330 * wrapper is configured, the attributes and text for it are
331 * reset.
332 *
333 * If the element has an <code>id</code> attribute, a reference
334 * is added to the project as well.
335 *
336 * @param p The project containing the wrapped element.
337 * Must not be <code>null</code>.
338 *
339 * @exception BuildException if the configuration fails, for instance due
340 * to invalid attributes or children, or text being added to
341 * an element which doesn't accept it.
342 */
343 public void maybeConfigure(Project p) throws BuildException {
344 maybeConfigure(p, true);
345 }
346
347 /**
348 * Configures the wrapped element. The attributes and text for
349 * the wrapped element are configured. Each time the wrapper is
350 * configured, the attributes and text for it are reset.
351 *
352 * If the element has an <code>id</code> attribute, a reference
353 * is added to the project as well.
354 *
355 * @param p The project containing the wrapped element.
356 * Must not be <code>null</code>.
357 *
358 * @param configureChildren ignored.
359
360 *
361 * @exception BuildException if the configuration fails, for instance due
362 * to invalid attributes , or text being added to
363 * an element which doesn't accept it.
364 */
365 public synchronized void maybeConfigure(Project p, boolean configureChildren)
366 throws BuildException {
367
368 if (proxyConfigured) {
369 return;
370 }
371
372 // Configure the object
373 Object target = (wrappedObject instanceof TypeAdapter)
374 ? ((TypeAdapter) wrappedObject).getProxy() : wrappedObject;
375
376 IntrospectionHelper ih =
377 IntrospectionHelper.getHelper(p, target.getClass());
378
379 if (attributeMap != null) {
380 for (Iterator iter = attributeMap.entrySet().iterator(); iter.hasNext();) {
381 Map.Entry entry = (Map.Entry) iter.next();
382 String name = (String) entry.getKey();
383 String value = (String) entry.getValue();
384
385 // reflect these into the target
386 Object attrValue = PropertyHelper.getPropertyHelper(p).parseProperties(value);
387 try {
388 ih.setAttribute(p, target, name, attrValue);
389 } catch (UnsupportedAttributeException be) {
390 // id attribute must be set externally
391 if (name.equals("id")) {
392 // Do nothing
393 } else if (getElementTag() == null) {
394 throw be;
395 } else {
396 throw new BuildException(
397 getElementTag() + " doesn't support the \""
398 + be.getAttribute() + "\" attribute", be);
399 }
400 } catch (BuildException be) {
401 if (name.equals("id")) {
402 // Assume that this is an not supported attribute type
403 // thrown for example by a dymanic attribute task
404 // Do nothing
405 } else {
406 throw be;
407 }
408 }
409 }
410 }
411
412 if (characters != null) {
413 ProjectHelper.addText(p, wrappedObject, characters.substring(0));
414 }
415
416 if (id != null) {
417 p.addReference(id, wrappedObject);
418 }
419 proxyConfigured = true;
420 }
421
422 /**
423 * Reconfigure the element, even if it has already been configured.
424 *
425 * @param p the project instance for this configuration.
426 */
427 public void reconfigure(Project p) {
428 proxyConfigured = false;
429 maybeConfigure(p);
430 }
431
432 /**
433 * Apply presets, attributes and text are set if not currently set.
434 * Nested elements are prepended.
435 *
436 * @param r a <code>RuntimeConfigurable</code> value.
437 */
438 public void applyPreSet(RuntimeConfigurable r) {
439 // Attributes
440 if (r.attributeMap != null) {
441 for (Iterator i = r.attributeMap.keySet().iterator(); i.hasNext();) {
442 String name = (String) i.next();
443 if (attributeMap == null || attributeMap.get(name) == null) {
444 setAttribute(name, (String) r.attributeMap.get(name));
445 }
446 }
447 }
448 // poly type
449
450 polyType = (polyType == null) ? r.polyType : polyType;
451
452 // Children (this is a shadow of UnknownElement#children)
453 if (r.children != null) {
454 List newChildren = new ArrayList();
455 newChildren.addAll(r.children);
456 if (children != null) {
457 newChildren.addAll(children);
458 }
459 children = newChildren;
460 }
461
462 // Text
463 if (r.characters != null) {
464 if (characters == null
465 || characters.toString().trim().length() == 0) {
466 characters = new StringBuffer(r.characters.toString());
467 }
468 }
469 }
470 }