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.util.ArrayList;
22 import java.util.Enumeration;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26 import java.io.IOException;
27 import org.apache.tools.ant.taskdefs.PreSetDef;
28
29 /**
30 * Wrapper class that holds all the information necessary to create a task
31 * or data type that did not exist when Ant started, or one which
32 * has had its definition updated to use a different implementation class.
33 *
34 */
35 public class UnknownElement extends Task {
36
37 /**
38 * Holds the name of the task/type or nested child element of a
39 * task/type that hasn't been defined at parser time or has
40 * been redefined since original creation.
41 */
42 private final String elementName;
43
44 /**
45 * Holds the namespace of the element.
46 */
47 private String namespace = "";
48
49 /**
50 * Holds the namespace qname of the element.
51 */
52 private String qname;
53
54 /**
55 * The real object after it has been loaded.
56 */
57 private Object realThing;
58
59 /**
60 * List of child elements (UnknownElements).
61 */
62 private List/*<UnknownElement>*/ children = null;
63
64 /** Specifies if a predefined definition has been done */
65 private boolean presetDefed = false;
66
67 /**
68 * Creates an UnknownElement for the given element name.
69 *
70 * @param elementName The name of the unknown element.
71 * Must not be <code>null</code>.
72 */
73 public UnknownElement(String elementName) {
74 this.elementName = elementName;
75 }
76
77 /**
78 * @return the list of nested UnknownElements for this UnknownElement.
79 */
80 public List getChildren() {
81 return children;
82 }
83
84 /**
85 * Returns the name of the XML element which generated this unknown
86 * element.
87 *
88 * @return the name of the XML element which generated this unknown
89 * element.
90 */
91 public String getTag() {
92 return elementName;
93 }
94
95 /**
96 * Return the namespace of the XML element associated with this component.
97 *
98 * @return Namespace URI used in the xmlns declaration.
99 */
100 public String getNamespace() {
101 return namespace;
102 }
103
104 /**
105 * Set the namespace of the XML element associated with this component.
106 * This method is typically called by the XML processor.
107 * If the namespace is "ant:current", the component helper
108 * is used to get the current antlib uri.
109 *
110 * @param namespace URI used in the xmlns declaration.
111 */
112 public void setNamespace(String namespace) {
113 if (namespace.equals(ProjectHelper.ANT_CURRENT_URI)) {
114 ComponentHelper helper = ComponentHelper.getComponentHelper(
115 getProject());
116 namespace = helper.getCurrentAntlibUri();
117 }
118 this.namespace = namespace == null ? "" : namespace;
119 }
120
121 /**
122 * Return the qname of the XML element associated with this component.
123 *
124 * @return namespace Qname used in the element declaration.
125 */
126 public String getQName() {
127 return qname;
128 }
129
130 /**
131 * Set the namespace qname of the XML element.
132 * This method is typically called by the XML processor.
133 *
134 * @param qname the qualified name of the element
135 */
136 public void setQName(String qname) {
137 this.qname = qname;
138 }
139
140
141 /**
142 * Get the RuntimeConfigurable instance for this UnknownElement, containing
143 * the configuration information.
144 *
145 * @return the configuration info.
146 */
147 public RuntimeConfigurable getWrapper() {
148 return super.getWrapper();
149 }
150
151 /**
152 * Creates the real object instance and child elements, then configures
153 * the attributes and text of the real object. This unknown element
154 * is then replaced with the real object in the containing target's list
155 * of children.
156 *
157 * @exception BuildException if the configuration fails
158 */
159 public void maybeConfigure() throws BuildException {
160 if (realThing != null) {
161 return;
162 }
163 configure(makeObject(this, getWrapper()));
164 }
165
166 /**
167 * Configure the given object from this UnknownElement
168 *
169 * @param realObject the real object this UnknownElement is representing.
170 *
171 */
172 public void configure(Object realObject) {
173 realThing = realObject;
174
175 getWrapper().setProxy(realThing);
176 Task task = null;
177 if (realThing instanceof Task) {
178 task = (Task) realThing;
179
180 task.setRuntimeConfigurableWrapper(getWrapper());
181
182 // For Script example that modifies id'ed tasks in other
183 // targets to work. *very* Ugly
184 // The reference is replaced by RuntimeConfigurable
185 if (getWrapper().getId() != null) {
186 this.getOwningTarget().replaceChild(this, (Task) realThing);
187 }
188 }
189
190
191 // configure attributes of the object and it's children. If it is
192 // a task container, defer the configuration till the task container
193 // attempts to use the task
194
195 if (task != null) {
196 task.maybeConfigure();
197 } else {
198 getWrapper().maybeConfigure(getProject());
199 }
200
201 handleChildren(realThing, getWrapper());
202 }
203
204 /**
205 * Handles output sent to System.out by this task or its real task.
206 *
207 * @param output The output to log. Should not be <code>null</code>.
208 */
209 protected void handleOutput(String output) {
210 if (realThing instanceof Task) {
211 ((Task) realThing).handleOutput(output);
212 } else {
213 super.handleOutput(output);
214 }
215 }
216
217 /**
218 * Delegate to realThing if present and if it as task.
219 * @see Task#handleInput(byte[], int, int)
220 * @param buffer the buffer into which data is to be read.
221 * @param offset the offset into the buffer at which data is stored.
222 * @param length the amount of data to read.
223 *
224 * @return the number of bytes read.
225 *
226 * @exception IOException if the data cannot be read.
227 * @since Ant 1.6
228 */
229 protected int handleInput(byte[] buffer, int offset, int length)
230 throws IOException {
231 if (realThing instanceof Task) {
232 return ((Task) realThing).handleInput(buffer, offset, length);
233 } else {
234 return super.handleInput(buffer, offset, length);
235 }
236
237 }
238
239 /**
240 * Handles output sent to System.out by this task or its real task.
241 *
242 * @param output The output to log. Should not be <code>null</code>.
243 */
244 protected void handleFlush(String output) {
245 if (realThing instanceof Task) {
246 ((Task) realThing).handleFlush(output);
247 } else {
248 super.handleFlush(output);
249 }
250 }
251
252 /**
253 * Handles error output sent to System.err by this task or its real task.
254 *
255 * @param output The error output to log. Should not be <code>null</code>.
256 */
257 protected void handleErrorOutput(String output) {
258 if (realThing instanceof Task) {
259 ((Task) realThing).handleErrorOutput(output);
260 } else {
261 super.handleErrorOutput(output);
262 }
263 }
264
265 /**
266 * Handles error output sent to System.err by this task or its real task.
267 *
268 * @param output The error output to log. Should not be <code>null</code>.
269 */
270 protected void handleErrorFlush(String output) {
271 if (realThing instanceof Task) {
272 ((Task) realThing).handleErrorOutput(output);
273 } else {
274 super.handleErrorOutput(output);
275 }
276 }
277
278 /**
279 * Executes the real object if it's a task. If it's not a task
280 * (e.g. a data type) then this method does nothing.
281 */
282 public void execute() {
283 if (realThing == null) {
284 // plain impossible to get here, maybeConfigure should
285 // have thrown an exception.
286 throw new BuildException("Could not create task of type: "
287 + elementName, getLocation());
288 }
289 try {
290 if (realThing instanceof Task) {
291 ((Task) realThing).execute();
292 }
293 } finally {
294 // Finished executing the task
295 // null it (unless it has an ID) to allow
296 // GC do its job
297 // If this UE is used again, a new "realthing" will be made
298 if (getWrapper().getId() == null) {
299 realThing = null;
300 getWrapper().setProxy(null);
301 }
302 }
303 }
304
305 /**
306 * Adds a child element to this element.
307 *
308 * @param child The child element to add. Must not be <code>null</code>.
309 */
310 public void addChild(UnknownElement child) {
311 if (children == null) {
312 children = new ArrayList();
313 }
314 children.add(child);
315 }
316
317 /**
318 * Creates child elements, creates children of the children
319 * (recursively), and sets attributes of the child elements.
320 *
321 * @param parent The configured object for the parent.
322 * Must not be <code>null</code>.
323 *
324 * @param parentWrapper The wrapper containing child wrappers
325 * to be configured. Must not be <code>null</code>
326 * if there are any children.
327 *
328 * @exception BuildException if the children cannot be configured.
329 */
330 protected void handleChildren(
331 Object parent,
332 RuntimeConfigurable parentWrapper)
333 throws BuildException {
334 if (parent instanceof TypeAdapter) {
335 parent = ((TypeAdapter) parent).getProxy();
336 }
337
338 String parentUri = getNamespace();
339 Class parentClass = parent.getClass();
340 IntrospectionHelper ih = IntrospectionHelper.getHelper(getProject(), parentClass);
341
342
343 if (children != null) {
344 Iterator it = children.iterator();
345 for (int i = 0; it.hasNext(); i++) {
346 RuntimeConfigurable childWrapper = parentWrapper.getChild(i);
347 UnknownElement child = (UnknownElement) it.next();
348 try {
349 if (!handleChild(
350 parentUri, ih, parent, child, childWrapper)) {
351 if (!(parent instanceof TaskContainer)) {
352 ih.throwNotSupported(getProject(), parent,
353 child.getTag());
354 } else {
355 // a task container - anything could happen - just add the
356 // child to the container
357 TaskContainer container = (TaskContainer) parent;
358 container.addTask(child);
359 }
360 }
361 } catch (UnsupportedElementException ex) {
362 throw new BuildException(
363 parentWrapper.getElementTag()
364 + " doesn't support the nested \"" + ex.getElement()
365 + "\" element.", ex);
366 }
367 }
368 }
369 }
370
371 /**
372 * @return the component name - uses ProjectHelper#genComponentName()
373 */
374 protected String getComponentName() {
375 return ProjectHelper.genComponentName(getNamespace(), getTag());
376 }
377
378 /**
379 * This is used then the realobject of the UE is a PreSetDefinition.
380 * This is also used when a presetdef is used on a presetdef
381 * The attributes, elements and text are applied to this
382 * UE.
383 *
384 * @param u an UnknownElement containing the attributes, elements and text
385 */
386 public void applyPreSet(UnknownElement u) {
387 if (presetDefed) {
388 return;
389 }
390 // Do the runtime
391 getWrapper().applyPreSet(u.getWrapper());
392 if (u.children != null) {
393 List newChildren = new ArrayList();
394 newChildren.addAll(u.children);
395 if (children != null) {
396 newChildren.addAll(children);
397 }
398 children = newChildren;
399 }
400 presetDefed = true;
401 }
402
403 /**
404 * Creates a named task or data type. If the real object is a task,
405 * it is configured up to the init() stage.
406 *
407 * @param ue The unknown element to create the real object for.
408 * Must not be <code>null</code>.
409 * @param w Ignored in this implementation.
410 *
411 * @return the task or data type represented by the given unknown element.
412 */
413 protected Object makeObject(UnknownElement ue, RuntimeConfigurable w) {
414 ComponentHelper helper = ComponentHelper.getComponentHelper(
415 getProject());
416 String name = ue.getComponentName();
417 Object o = helper.createComponent(ue, ue.getNamespace(), name);
418 if (o == null) {
419 throw getNotFoundException("task or type", name);
420 }
421 if (o instanceof PreSetDef.PreSetDefinition) {
422 PreSetDef.PreSetDefinition def = (PreSetDef.PreSetDefinition) o;
423 o = def.createObject(ue.getProject());
424 if (o == null) {
425 throw getNotFoundException(
426 "preset " + name,
427 def.getPreSets().getComponentName());
428 }
429 ue.applyPreSet(def.getPreSets());
430 if (o instanceof Task) {
431 Task task = (Task) o;
432 task.setTaskType(ue.getTaskType());
433 task.setTaskName(ue.getTaskName());
434 task.init();
435 }
436 }
437 if (o instanceof UnknownElement) {
438 o = ((UnknownElement) o).makeObject((UnknownElement) o, w);
439 }
440 if (o instanceof Task) {
441 ((Task) o).setOwningTarget(getOwningTarget());
442 }
443 if (o instanceof ProjectComponent) {
444 ((ProjectComponent) o).setLocation(getLocation());
445 }
446 return o;
447 }
448
449 /**
450 * Creates a named task and configures it up to the init() stage.
451 *
452 * @param ue The UnknownElement to create the real task for.
453 * Must not be <code>null</code>.
454 * @param w Ignored.
455 *
456 * @return the task specified by the given unknown element, or
457 * <code>null</code> if the task name is not recognised.
458 */
459 protected Task makeTask(UnknownElement ue, RuntimeConfigurable w) {
460 Task task = getProject().createTask(ue.getTag());
461
462 if (task != null) {
463 task.setLocation(getLocation());
464 // UnknownElement always has an associated target
465 task.setOwningTarget(getOwningTarget());
466 task.init();
467 }
468 return task;
469 }
470
471 /**
472 * Returns a very verbose exception for when a task/data type cannot
473 * be found.
474 *
475 * @param what The kind of thing being created. For example, when
476 * a task name could not be found, this would be
477 * <code>"task"</code>. Should not be <code>null</code>.
478 * @param name The name of the element which could not be found.
479 * Should not be <code>null</code>.
480 *
481 * @return a detailed description of what might have caused the problem.
482 */
483 protected BuildException getNotFoundException(String what,
484 String name) {
485 ComponentHelper helper = ComponentHelper.getComponentHelper(getProject());
486 String msg = helper.diagnoseCreationFailure(name, what);
487 return new BuildException(msg, getLocation());
488 }
489
490 /**
491 * Returns the name to use in logging messages.
492 *
493 * @return the name to use in logging messages.
494 */
495 public String getTaskName() {
496 //return elementName;
497 return realThing == null
498 || !(realThing instanceof Task) ? super.getTaskName()
499 : ((Task) realThing).getTaskName();
500 }
501
502 /**
503 * Returns the task instance after it has been created and if it is a task.
504 *
505 * @return a task instance or <code>null</code> if the real object is not
506 * a task.
507 */
508 public Task getTask() {
509 if (realThing instanceof Task) {
510 return (Task) realThing;
511 }
512 return null;
513 }
514
515 /**
516 * Return the configured object
517 *
518 * @return the real thing whatever it is
519 *
520 * @since ant 1.6
521 */
522 public Object getRealThing() {
523 return realThing;
524 }
525
526 /**
527 * Set the configured object
528 * @param realThing the configured object
529 * @since ant 1.7
530 */
531 public void setRealThing(Object realThing) {
532 this.realThing = realThing;
533 }
534
535 /**
536 * Try to create a nested element of <code>parent</code> for the
537 * given tag.
538 *
539 * @return whether the creation has been successful
540 */
541 private boolean handleChild(
542 String parentUri,
543 IntrospectionHelper ih,
544 Object parent, UnknownElement child,
545 RuntimeConfigurable childWrapper) {
546 String childName = ProjectHelper.genComponentName(
547 child.getNamespace(), child.getTag());
548 if (ih.supportsNestedElement(parentUri, childName, getProject(),
549 parent)) {
550 IntrospectionHelper.Creator creator = null;
551 try {
552 creator = ih.getElementCreator(getProject(), parentUri,
553 parent, childName, child);
554 } catch (UnsupportedElementException use) {
555 if (!ih.isDynamic()) {
556 throw use;
557 }
558 // can't trust supportsNestedElement for dynamic elements
559 return false;
560 }
561 creator.setPolyType(childWrapper.getPolyType());
562 Object realChild = creator.create();
563 if (realChild instanceof PreSetDef.PreSetDefinition) {
564 PreSetDef.PreSetDefinition def =
565 (PreSetDef.PreSetDefinition) realChild;
566 realChild = creator.getRealObject();
567 child.applyPreSet(def.getPreSets());
568 }
569 childWrapper.setCreator(creator);
570 childWrapper.setProxy(realChild);
571 if (realChild instanceof Task) {
572 Task childTask = (Task) realChild;
573 childTask.setRuntimeConfigurableWrapper(childWrapper);
574 childTask.setTaskName(childName);
575 childTask.setTaskType(childName);
576 }
577 if (realChild instanceof ProjectComponent) {
578 ((ProjectComponent) realChild).setLocation(child.getLocation());
579 }
580 childWrapper.maybeConfigure(getProject());
581 child.handleChildren(realChild, childWrapper);
582 creator.store();
583 return true;
584 }
585 return false;
586 }
587
588 /**
589 * like contents equals, but ignores project
590 * @param obj the object to check against
591 * @return true if this unknownelement has the same contents the other
592 */
593 public boolean similar(Object obj) {
594 if (obj == null) {
595 return false;
596 }
597 if (!getClass().getName().equals(obj.getClass().getName())) {
598 return false;
599 }
600 UnknownElement other = (UnknownElement) obj;
601 // Are the names the same ?
602 if (!equalsString(elementName, other.elementName)) {
603 return false;
604 }
605 if (!namespace.equals(other.namespace)) {
606 return false;
607 }
608 if (!qname.equals(other.qname)) {
609 return false;
610 }
611 // Are attributes the same ?
612 if (!getWrapper().getAttributeMap().equals(
613 other.getWrapper().getAttributeMap())) {
614 return false;
615 }
616 // Is the text the same?
617 // Need to use equals on the string and not
618 // on the stringbuffer as equals on the string buffer
619 // does not compare the contents.
620 if (!getWrapper().getText().toString().equals(
621 other.getWrapper().getText().toString())) {
622 return false;
623 }
624 // Are the sub elements the same ?
625 if (children == null || children.size() == 0) {
626 return other.children == null || other.children.size() == 0;
627 }
628 if (other.children == null) {
629 return false;
630 }
631 if (children.size() != other.children.size()) {
632 return false;
633 }
634 for (int i = 0; i < children.size(); ++i) {
635 UnknownElement child = (UnknownElement) children.get(i);
636 if (!child.similar(other.children.get(i))) {
637 return false;
638 }
639 }
640 return true;
641 }
642
643 private static boolean equalsString(String a, String b) {
644 return (a == null) ? (b == null) : a.equals(b);
645 }
646
647 /**
648 * Make a copy of the unknown element and set it in the new project.
649 * @param newProject the project to create the UE in.
650 * @return the copied UE.
651 */
652 public UnknownElement copy(Project newProject) {
653 UnknownElement ret = new UnknownElement(getTag());
654 ret.setNamespace(getNamespace());
655 ret.setProject(newProject);
656 ret.setQName(getQName());
657 ret.setTaskType(getTaskType());
658 ret.setTaskName(getTaskName());
659 ret.setLocation(getLocation());
660 if (getOwningTarget() == null) {
661 Target t = new Target();
662 t.setProject(getProject());
663 ret.setOwningTarget(t);
664 } else {
665 ret.setOwningTarget(getOwningTarget());
666 }
667 RuntimeConfigurable copyRC = new RuntimeConfigurable(
668 ret, getTaskName());
669 copyRC.setPolyType(getWrapper().getPolyType());
670 Map m = getWrapper().getAttributeMap();
671 for (Iterator i = m.entrySet().iterator(); i.hasNext();) {
672 Map.Entry entry = (Map.Entry) i.next();
673 copyRC.setAttribute(
674 (String) entry.getKey(), (String) entry.getValue());
675 }
676 copyRC.addText(getWrapper().getText().toString());
677
678 for (Enumeration e = getWrapper().getChildren(); e.hasMoreElements();) {
679 RuntimeConfigurable r = (RuntimeConfigurable) e.nextElement();
680 UnknownElement ueChild = (UnknownElement) r.getProxy();
681 UnknownElement copyChild = ueChild.copy(newProject);
682 copyRC.addChild(copyChild.getWrapper());
683 ret.addChild(copyChild);
684 }
685 return ret;
686 }
687 }