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