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.helper;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.io.UnsupportedEncodingException;
26 import java.util.Locale;
27 import org.apache.tools.ant.BuildException;
28 import org.apache.tools.ant.IntrospectionHelper;
29 import org.apache.tools.ant.Location;
30 import org.apache.tools.ant.Project;
31 import org.apache.tools.ant.ProjectHelper;
32 import org.apache.tools.ant.RuntimeConfigurable;
33 import org.apache.tools.ant.Target;
34 import org.apache.tools.ant.Task;
35 import org.apache.tools.ant.TypeAdapter;
36 import org.apache.tools.ant.TaskContainer;
37 import org.apache.tools.ant.UnknownElement;
38 import org.apache.tools.ant.util.FileUtils;
39 import org.apache.tools.ant.util.JAXPUtils;
40 import org.xml.sax.AttributeList;
41 import org.xml.sax.DocumentHandler;
42 import org.xml.sax.HandlerBase;
43 import org.xml.sax.InputSource;
44 import org.xml.sax.Locator;
45 import org.xml.sax.SAXException;
46 import org.xml.sax.SAXParseException;
47 import org.xml.sax.helpers.XMLReaderAdapter;
48
49 /**
50 * Original helper.
51 *
52 */
53 public class ProjectHelperImpl extends ProjectHelper {
54
55 /**
56 * helper for path -> URI and URI -> path conversions.
57 */
58 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
59
60 /**
61 * SAX 1 style parser used to parse the given file. This may
62 * in fact be a SAX 2 XMLReader wrapped in an XMLReaderAdapter.
63 */
64 private org.xml.sax.Parser parser;
65
66 /** The project to configure. */
67 private Project project;
68 /** The configuration file to parse. */
69 private File buildFile;
70 /**
71 * Parent directory of the build file. Used for resolving entities
72 * and setting the project's base directory.
73 */
74 private File buildFileParent;
75 /**
76 * Locator for the configuration file parser.
77 * Used for giving locations of errors etc.
78 */
79 private Locator locator;
80 /**
81 * Target that all other targets will depend upon implicitly.
82 *
83 * <p>This holds all tasks and data type definitions that have
84 * been placed outside of targets.</p>
85 */
86 private Target implicitTarget = new Target();
87
88 /**
89 * default constructor
90 */
91 public ProjectHelperImpl() {
92 implicitTarget.setName("");
93 }
94
95 /**
96 * Parses the project file, configuring the project as it goes.
97 *
98 * @param project project instance to be configured.
99 * @param source the source from which the project is read.
100 * @exception BuildException if the configuration is invalid or cannot
101 * be read.
102 */
103 public void parse(Project project, Object source) throws BuildException {
104 if (!(source instanceof File)) {
105 throw new BuildException("Only File source supported by "
106 + "default plugin");
107 }
108 File bFile = (File) source;
109 FileInputStream inputStream = null;
110 InputSource inputSource = null;
111
112 this.project = project;
113 this.buildFile = new File(bFile.getAbsolutePath());
114 buildFileParent = new File(this.buildFile.getParent());
115
116 try {
117 try {
118 parser = JAXPUtils.getParser();
119 } catch (BuildException e) {
120 parser = new XMLReaderAdapter(JAXPUtils.getXMLReader());
121 }
122
123
124 String uri = FILE_UTILS.toURI(bFile.getAbsolutePath());
125 inputStream = new FileInputStream(bFile);
126 inputSource = new InputSource(inputStream);
127 inputSource.setSystemId(uri);
128 project.log("parsing buildfile " + bFile + " with URI = "
129 + uri, Project.MSG_VERBOSE);
130 HandlerBase hb = new RootHandler(this);
131 parser.setDocumentHandler(hb);
132 parser.setEntityResolver(hb);
133 parser.setErrorHandler(hb);
134 parser.setDTDHandler(hb);
135 parser.parse(inputSource);
136 } catch (SAXParseException exc) {
137 Location location =
138 new Location(exc.getSystemId(), exc.getLineNumber(),
139 exc.getColumnNumber());
140
141 Throwable t = exc.getException();
142 if (t instanceof BuildException) {
143 BuildException be = (BuildException) t;
144 if (be.getLocation() == Location.UNKNOWN_LOCATION) {
145 be.setLocation(location);
146 }
147 throw be;
148 }
149
150 throw new BuildException(exc.getMessage(), t, location);
151 } catch (SAXException exc) {
152 Throwable t = exc.getException();
153 if (t instanceof BuildException) {
154 throw (BuildException) t;
155 }
156 throw new BuildException(exc.getMessage(), t);
157 } catch (FileNotFoundException exc) {
158 throw new BuildException(exc);
159 } catch (UnsupportedEncodingException exc) {
160 throw new BuildException("Encoding of project file is invalid.",
161 exc);
162 } catch (IOException exc) {
163 throw new BuildException("Error reading project file: "
164 + exc.getMessage(), exc);
165 } finally {
166 FileUtils.close(inputStream);
167 }
168 }
169
170 /**
171 * The common superclass for all SAX event handlers used to parse
172 * the configuration file. Each method just throws an exception,
173 * so subclasses should override what they can handle.
174 *
175 * Each type of XML element (task, target, etc.) in Ant has
176 * a specific subclass.
177 *
178 * In the constructor, this class takes over the handling of SAX
179 * events from the parent handler and returns
180 * control back to the parent in the endElement method.
181 */
182 static class AbstractHandler extends HandlerBase {
183 // CheckStyle:VisibilityModifier OFF - bc
184
185 /**
186 * Previous handler for the document.
187 * When the next element is finished, control returns
188 * to this handler.
189 */
190 protected DocumentHandler parentHandler;
191
192 /** Helper impl. With non-static internal classes, the compiler will generate
193 this automatically - but this will fail with some compilers ( reporting
194 "Expecting to find object/array on stack" ). If we pass it
195 explicitly it'll work with more compilers.
196 */
197 ProjectHelperImpl helperImpl;
198 // CheckStyle:VisibilityModifier ON
199
200 /**
201 * Creates a handler and sets the parser to use it
202 * for the current element.
203 *
204 * @param helperImpl the ProjectHelperImpl instance associated
205 * with this handler.
206 *
207 * @param parentHandler The handler which should be restored to the
208 * parser at the end of the element.
209 * Must not be <code>null</code>.
210 */
211 public AbstractHandler(ProjectHelperImpl helperImpl, DocumentHandler parentHandler) {
212 this.parentHandler = parentHandler;
213 this.helperImpl = helperImpl;
214
215 // Start handling SAX events
216 helperImpl.parser.setDocumentHandler(this);
217 }
218
219 /**
220 * Handles the start of an element. This base implementation just
221 * throws an exception.
222 *
223 * @param tag The name of the element being started.
224 * Will not be <code>null</code>.
225 * @param attrs Attributes of the element being started.
226 * Will not be <code>null</code>.
227 *
228 * @exception SAXParseException if this method is not overridden, or in
229 * case of error in an overridden version
230 */
231 public void startElement(String tag, AttributeList attrs) throws SAXParseException {
232 throw new SAXParseException("Unexpected element \"" + tag + "\"", helperImpl.locator);
233 }
234
235 /**
236 * Handles text within an element. This base implementation just
237 * throws an exception.
238 *
239 * @param buf A character array of the text within the element.
240 * Will not be <code>null</code>.
241 * @param start The start element in the array.
242 * @param count The number of characters to read from the array.
243 *
244 * @exception SAXParseException if this method is not overridden, or in
245 * case of error in an overridden version
246 */
247 public void characters(char[] buf, int start, int count) throws SAXParseException {
248 String s = new String(buf, start, count).trim();
249
250 if (s.length() > 0) {
251 throw new SAXParseException("Unexpected text \"" + s + "\"", helperImpl.locator);
252 }
253 }
254
255 /**
256 * Handles the end of an element. Any required clean-up is performed
257 * by the finished() method and then the original handler is restored to
258 * the parser.
259 *
260 * @param name The name of the element which is ending.
261 * Will not be <code>null</code>.
262 *
263 * @exception SAXException in case of error (not thrown in
264 * this implementation)
265 */
266 public void endElement(String name) throws SAXException {
267 // Let parent resume handling SAX events
268 helperImpl.parser.setDocumentHandler(parentHandler);
269 }
270 }
271
272 /**
273 * Handler for the root element. Its only child must be the "project" element.
274 */
275 static class RootHandler extends HandlerBase {
276 // CheckStyle:VisibilityModifier OFF - bc
277 ProjectHelperImpl helperImpl;
278 // CheckStyle:VisibilityModifier ON
279
280 public RootHandler(ProjectHelperImpl helperImpl) {
281 this.helperImpl = helperImpl;
282 }
283
284 /**
285 * Resolves file: URIs relative to the build file.
286 *
287 * @param publicId The public identifier, or <code>null</code>
288 * if none is available. Ignored in this
289 * implementation.
290 * @param systemId The system identifier provided in the XML
291 * document. Will not be <code>null</code>.
292 */
293 public InputSource resolveEntity(String publicId,
294 String systemId) {
295
296 helperImpl.project.log("resolving systemId: " + systemId, Project.MSG_VERBOSE);
297
298 if (systemId.startsWith("file:")) {
299 String path = FILE_UTILS.fromURI(systemId);
300
301 File file = new File(path);
302 if (!file.isAbsolute()) {
303 file = FILE_UTILS.resolveFile(helperImpl.buildFileParent, path);
304 helperImpl.project.log(
305 "Warning: '" + systemId + "' in " + helperImpl.buildFile
306 + " should be expressed simply as '" + path.replace('\\', '/')
307 + "' for compliance with other XML tools",
308 Project.MSG_WARN);
309 }
310 try {
311 InputSource inputSource = new InputSource(new FileInputStream(file));
312 inputSource.setSystemId(FILE_UTILS.toURI(file.getAbsolutePath()));
313 return inputSource;
314 } catch (FileNotFoundException fne) {
315 helperImpl.project.log(file.getAbsolutePath() + " could not be found",
316 Project.MSG_WARN);
317 }
318 }
319 // use default if not file or file not found
320 return null;
321 }
322
323 /**
324 * Handles the start of a project element. A project handler is created
325 * and initialised with the element name and attributes.
326 *
327 * @param tag The name of the element being started.
328 * Will not be <code>null</code>.
329 * @param attrs Attributes of the element being started.
330 * Will not be <code>null</code>.
331 *
332 * @exception SAXParseException if the tag given is not
333 * <code>"project"</code>
334 */
335 public void startElement(String tag, AttributeList attrs) throws SAXParseException {
336 if (tag.equals("project")) {
337 new ProjectHandler(helperImpl, this).init(tag, attrs);
338 } else {
339 throw new SAXParseException("Config file is not of expected "
340 + "XML type", helperImpl.locator);
341 }
342 }
343
344 /**
345 * Sets the locator in the project helper for future reference.
346 *
347 * @param locator The locator used by the parser.
348 * Will not be <code>null</code>.
349 */
350 public void setDocumentLocator(Locator locator) {
351 helperImpl.locator = locator;
352 }
353 }
354
355 /**
356 * Handler for the top level "project" element.
357 */
358 static class ProjectHandler extends AbstractHandler {
359
360 /**
361 * Constructor which just delegates to the superconstructor.
362 *
363 * @param parentHandler The handler which should be restored to the
364 * parser at the end of the element.
365 * Must not be <code>null</code>.
366 */
367 public ProjectHandler(ProjectHelperImpl helperImpl, DocumentHandler parentHandler) {
368 super(helperImpl, parentHandler);
369 }
370
371 /**
372 * Initialisation routine called after handler creation
373 * with the element name and attributes. The attributes which
374 * this handler can deal with are: <code>"default"</code>,
375 * <code>"name"</code>, <code>"id"</code> and <code>"basedir"</code>.
376 *
377 * @param tag Name of the element which caused this handler
378 * to be created. Should not be <code>null</code>.
379 * Ignored in this implementation.
380 * @param attrs Attributes of the element which caused this
381 * handler to be created. Must not be <code>null</code>.
382 *
383 * @exception SAXParseException if an unexpected attribute is
384 * encountered or if the <code>"default"</code> attribute
385 * is missing.
386 */
387 public void init(String tag, AttributeList attrs) throws SAXParseException {
388 String def = null;
389 String name = null;
390 String id = null;
391 String baseDir = null;
392
393 for (int i = 0; i < attrs.getLength(); i++) {
394 String key = attrs.getName(i);
395 String value = attrs.getValue(i);
396
397 if (key.equals("default")) {
398 def = value;
399 } else if (key.equals("name")) {
400 name = value;
401 } else if (key.equals("id")) {
402 id = value;
403 } else if (key.equals("basedir")) {
404 baseDir = value;
405 } else {
406 throw new SAXParseException("Unexpected attribute \"" + attrs.getName(i) + "\"",
407 helperImpl.locator);
408 }
409 }
410
411 if (def != null && !def.equals("")) {
412 helperImpl.project.setDefaultTarget(def);
413 } else {
414 throw new BuildException("The default attribute is required");
415 }
416
417 if (name != null) {
418 helperImpl.project.setName(name);
419 helperImpl.project.addReference(name, helperImpl.project);
420 }
421
422 if (id != null) {
423 helperImpl.project.addReference(id, helperImpl.project);
424 }
425
426 if (helperImpl.project.getProperty("basedir") != null) {
427 helperImpl.project.setBasedir(helperImpl.project.getProperty("basedir"));
428 } else {
429 if (baseDir == null) {
430 helperImpl.project.setBasedir(helperImpl.buildFileParent.getAbsolutePath());
431 } else {
432 // check whether the user has specified an absolute path
433 if ((new File(baseDir)).isAbsolute()) {
434 helperImpl.project.setBasedir(baseDir);
435 } else {
436 File resolvedBaseDir = FILE_UTILS.resolveFile(
437 helperImpl.buildFileParent, baseDir);
438 helperImpl.project.setBaseDir(resolvedBaseDir);
439 }
440 }
441 }
442
443 helperImpl.project.addTarget("", helperImpl.implicitTarget);
444 }
445
446 /**
447 * Handles the start of a top-level element within the project. An
448 * appropriate handler is created and initialised with the details
449 * of the element.
450 *
451 * @param name The name of the element being started.
452 * Will not be <code>null</code>.
453 * @param attrs Attributes of the element being started.
454 * Will not be <code>null</code>.
455 *
456 * @exception SAXParseException if the tag given is not
457 * <code>"taskdef"</code>, <code>"typedef"</code>,
458 * <code>"property"</code>, <code>"target"</code>
459 * or a data type definition
460 */
461 public void startElement(String name, AttributeList attrs) throws SAXParseException {
462 if (name.equals("target")) {
463 handleTarget(name, attrs);
464 } else {
465 handleElement(helperImpl, this, helperImpl.implicitTarget,
466 name, attrs);
467 }
468 }
469
470 /**
471 * Handles a target definition element by creating a target handler
472 * and initialising is with the details of the element.
473 *
474 * @param tag The name of the element to be handled.
475 * Will not be <code>null</code>.
476 * @param attrs Attributes of the element to be handled.
477 * Will not be <code>null</code>.
478 *
479 * @exception SAXParseException if an error occurs initialising
480 * the handler
481 */
482 private void handleTarget(String tag, AttributeList attrs) throws SAXParseException {
483 new TargetHandler(helperImpl, this).init(tag, attrs);
484 }
485
486 }
487
488 /**
489 * Handler for "target" elements.
490 */
491 static class TargetHandler extends AbstractHandler {
492 private Target target;
493
494 /**
495 * Constructor which just delegates to the superconstructor.
496 *
497 * @param parentHandler The handler which should be restored to the
498 * parser at the end of the element.
499 * Must not be <code>null</code>.
500 */
501 public TargetHandler(ProjectHelperImpl helperImpl, DocumentHandler parentHandler) {
502 super(helperImpl, parentHandler);
503 }
504
505 /**
506 * Initialisation routine called after handler creation
507 * with the element name and attributes. The attributes which
508 * this handler can deal with are: <code>"name"</code>,
509 * <code>"depends"</code>, <code>"if"</code>,
510 * <code>"unless"</code>, <code>"id"</code> and
511 * <code>"description"</code>.
512 *
513 * @param tag Name of the element which caused this handler
514 * to be created. Should not be <code>null</code>.
515 * Ignored in this implementation.
516 * @param attrs Attributes of the element which caused this
517 * handler to be created. Must not be <code>null</code>.
518 *
519 * @exception SAXParseException if an unexpected attribute is encountered
520 * or if the <code>"name"</code> attribute is missing.
521 */
522 public void init(String tag, AttributeList attrs) throws SAXParseException {
523 String name = null;
524 String depends = "";
525 String ifCond = null;
526 String unlessCond = null;
527 String id = null;
528 String description = null;
529
530 for (int i = 0; i < attrs.getLength(); i++) {
531 String key = attrs.getName(i);
532 String value = attrs.getValue(i);
533
534 if (key.equals("name")) {
535 name = value;
536 if (name.equals("")) {
537 throw new BuildException("name attribute must not"
538 + " be empty",
539 new Location(helperImpl.locator));
540 }
541 } else if (key.equals("depends")) {
542 depends = value;
543 } else if (key.equals("if")) {
544 ifCond = value;
545 } else if (key.equals("unless")) {
546 unlessCond = value;
547 } else if (key.equals("id")) {
548 id = value;
549 } else if (key.equals("description")) {
550 description = value;
551 } else {
552 throw new SAXParseException("Unexpected attribute \""
553 + key + "\"", helperImpl.locator);
554 }
555 }
556
557 if (name == null) {
558 throw new SAXParseException("target element appears without a name attribute",
559 helperImpl.locator);
560 }
561
562 target = new Target();
563
564 // implicit target must be first on dependency list
565 target.addDependency("");
566
567 target.setName(name);
568 target.setIf(ifCond);
569 target.setUnless(unlessCond);
570 target.setDescription(description);
571 helperImpl.project.addTarget(name, target);
572
573 if (id != null && !id.equals("")) {
574 helperImpl.project.addReference(id, target);
575 }
576
577 // take care of dependencies
578
579 if (depends.length() > 0) {
580 target.setDepends(depends);
581 }
582 }
583
584 /**
585 * Handles the start of an element within a target.
586 *
587 * @param name The name of the element being started.
588 * Will not be <code>null</code>.
589 * @param attrs Attributes of the element being started.
590 * Will not be <code>null</code>.
591 *
592 * @exception SAXParseException if an error occurs when initialising
593 * the appropriate child handler
594 */
595 public void startElement(String name, AttributeList attrs) throws SAXParseException {
596 handleElement(helperImpl, this, target, name, attrs);
597 }
598 }
599
600 /**
601 * Start a new DataTypeHandler if element is known to be a
602 * data-type and a TaskHandler otherwise.
603 *
604 * <p>Factored out of TargetHandler.</p>
605 *
606 * @since Ant 1.6
607 */
608 private static void handleElement(ProjectHelperImpl helperImpl,
609 DocumentHandler parent,
610 Target target, String elementName,
611 AttributeList attrs)
612 throws SAXParseException {
613 if (elementName.equals("description")) {
614 new DescriptionHandler(helperImpl, parent);
615 } else if (helperImpl.project.getDataTypeDefinitions()
616 .get(elementName) != null) {
617 new DataTypeHandler(helperImpl, parent, target)
618 .init(elementName, attrs);
619 } else {
620 new TaskHandler(helperImpl, parent, target, null, target)
621 .init(elementName, attrs);
622 }
623 }
624
625 /**
626 * Handler for "description" elements.
627 */
628 static class DescriptionHandler extends AbstractHandler {
629
630 /**
631 * Constructor which just delegates to the superconstructor.
632 *
633 * @param parentHandler The handler which should be restored to the
634 * parser at the end of the element.
635 * Must not be <code>null</code>.
636 */
637 public DescriptionHandler(ProjectHelperImpl helperImpl,
638 DocumentHandler parentHandler) {
639 super(helperImpl, parentHandler);
640 }
641
642 /**
643 * Adds the text as description to the project.
644 *
645 * @param buf A character array of the text within the element.
646 * Will not be <code>null</code>.
647 * @param start The start element in the array.
648 * @param count The number of characters to read from the array.
649 */
650 public void characters(char[] buf, int start, int count) {
651 String text = new String(buf, start, count);
652 String currentDescription = helperImpl.project.getDescription();
653 if (currentDescription == null) {
654 helperImpl.project.setDescription(text);
655 } else {
656 helperImpl.project.setDescription(currentDescription + text);
657 }
658 }
659
660 }
661
662 /**
663 * Handler for all task elements.
664 */
665 static class TaskHandler extends AbstractHandler {
666 /** Containing target, if any. */
667 private Target target;
668 /**
669 * Container for the task, if any. If target is
670 * non-<code>null</code>, this must be too.
671 */
672 private TaskContainer container;
673 /**
674 * Task created by this handler.
675 */
676 private Task task;
677 /**
678 * Wrapper for the parent element, if any. The wrapper for this
679 * element will be added to this wrapper as a child.
680 */
681 private RuntimeConfigurable parentWrapper;
682 /**
683 * Wrapper for this element which takes care of actually configuring
684 * the element, if this element is contained within a target.
685 * Otherwise the configuration is performed with the configure method.
686 * @see ProjectHelper#configure(Object,AttributeList,Project)
687 */
688 private RuntimeConfigurable wrapper = null;
689
690 /**
691 * Constructor.
692 *
693 * @param parentHandler The handler which should be restored to the
694 * parser at the end of the element.
695 * Must not be <code>null</code>.
696 *
697 * @param container Container for the element.
698 * Must not be <code>null</code>.
699 *
700 * @param parentWrapper Wrapper for the parent element, if any.
701 * May be <code>null</code>.
702 *
703 * @param target Target this element is part of.
704 * Must not be <code>null</code>.
705 */
706 public TaskHandler(ProjectHelperImpl helperImpl, DocumentHandler parentHandler,
707 TaskContainer container,
708 RuntimeConfigurable parentWrapper, Target target) {
709 super(helperImpl, parentHandler);
710 this.container = container;
711 this.parentWrapper = parentWrapper;
712 this.target = target;
713 }
714
715 /**
716 * Initialisation routine called after handler creation
717 * with the element name and attributes. This configures
718 * the element with its attributes and sets it up with
719 * its parent container (if any). Nested elements are then
720 * added later as the parser encounters them.
721 *
722 * @param tag Name of the element which caused this handler
723 * to be created. Must not be <code>null</code>.
724 *
725 * @param attrs Attributes of the element which caused this
726 * handler to be created. Must not be <code>null</code>.
727 *
728 * @exception SAXParseException in case of error (not thrown in
729 * this implementation)
730 */
731 public void init(String tag, AttributeList attrs) throws SAXParseException {
732 try {
733 task = helperImpl.project.createTask(tag);
734 } catch (BuildException e) {
735 // swallow here, will be thrown again in
736 // UnknownElement.maybeConfigure if the problem persists.
737 }
738
739 if (task == null) {
740 task = new UnknownElement(tag);
741 task.setProject(helperImpl.project);
742 //XXX task.setTaskType(tag);
743 task.setTaskName(tag);
744 }
745
746 task.setLocation(new Location(helperImpl.locator));
747 helperImpl.configureId(task, attrs);
748
749 task.setOwningTarget(target);
750 container.addTask(task);
751 task.init();
752 wrapper = task.getRuntimeConfigurableWrapper();
753 wrapper.setAttributes(attrs);
754 if (parentWrapper != null) {
755 parentWrapper.addChild(wrapper);
756 }
757 }
758
759 /**
760 * Adds text to the task, using the wrapper.
761 *
762 * @param buf A character array of the text within the element.
763 * Will not be <code>null</code>.
764 * @param start The start element in the array.
765 * @param count The number of characters to read from the array.
766 */
767 public void characters(char[] buf, int start, int count) {
768 wrapper.addText(buf, start, count);
769 }
770
771 /**
772 * Handles the start of an element within a target. Task containers
773 * will always use another task handler, and all other tasks
774 * will always use a nested element handler.
775 *
776 * @param name The name of the element being started.
777 * Will not be <code>null</code>.
778 * @param attrs Attributes of the element being started.
779 * Will not be <code>null</code>.
780 *
781 * @exception SAXParseException if an error occurs when initialising
782 * the appropriate child handler
783 */
784 public void startElement(String name, AttributeList attrs) throws SAXParseException {
785 if (task instanceof TaskContainer) {
786 // task can contain other tasks - no other nested elements possible
787 new TaskHandler(helperImpl, this, (TaskContainer) task,
788 wrapper, target).init(name, attrs);
789 } else {
790 new NestedElementHandler(helperImpl, this, task,
791 wrapper, target).init(name, attrs);
792 }
793 }
794 }
795
796 /**
797 * Handler for all nested properties.
798 */
799 static class NestedElementHandler extends AbstractHandler {
800 /** Parent object (task/data type/etc). */
801 private Object parent;
802 /** The nested element itself. */
803 private Object child;
804 /**
805 * Wrapper for the parent element, if any. The wrapper for this
806 * element will be added to this wrapper as a child.
807 */
808 private RuntimeConfigurable parentWrapper;
809 /**
810 * Wrapper for this element which takes care of actually configuring
811 * the element, if a parent wrapper is provided.
812 * Otherwise the configuration is performed with the configure method.
813 * @see ProjectHelper#configure(Object,AttributeList,Project)
814 */
815 private RuntimeConfigurable childWrapper = null;
816 /** Target this element is part of, if any. */
817 private Target target;
818
819 /**
820 * Constructor.
821 *
822 * @param parentHandler The handler which should be restored to the
823 * parser at the end of the element.
824 * Must not be <code>null</code>.
825 *
826 * @param parent Parent of this element (task/data type/etc).
827 * Must not be <code>null</code>.
828 *
829 * @param parentWrapper Wrapper for the parent element, if any.
830 * Must not be <code>null</code>.
831 *
832 * @param target Target this element is part of.
833 * Must not be <code>null</code>.
834 */
835 public NestedElementHandler(ProjectHelperImpl helperImpl,
836 DocumentHandler parentHandler,
837 Object parent,
838 RuntimeConfigurable parentWrapper,
839 Target target) {
840 super(helperImpl, parentHandler);
841
842 if (parent instanceof TypeAdapter) {
843 this.parent = ((TypeAdapter) parent).getProxy();
844 } else {
845 this.parent = parent;
846 }
847 this.parentWrapper = parentWrapper;
848 this.target = target;
849 }
850
851 /**
852 * Initialisation routine called after handler creation
853 * with the element name and attributes. This configures
854 * the element with its attributes and sets it up with
855 * its parent container (if any). Nested elements are then
856 * added later as the parser encounters them.
857 *
858 * @param propType Name of the element which caused this handler
859 * to be created. Must not be <code>null</code>.
860 *
861 * @param attrs Attributes of the element which caused this
862 * handler to be created. Must not be <code>null</code>.
863 *
864 * @exception SAXParseException in case of error, such as a
865 * BuildException being thrown during configuration.
866 */
867 public void init(String propType, AttributeList attrs) throws SAXParseException {
868 Class parentClass = parent.getClass();
869 IntrospectionHelper ih =
870 IntrospectionHelper.getHelper(helperImpl.project, parentClass);
871
872 try {
873 String elementName = propType.toLowerCase(Locale.US);
874 if (parent instanceof UnknownElement) {
875 UnknownElement uc = new UnknownElement(elementName);
876 uc.setProject(helperImpl.project);
877 ((UnknownElement) parent).addChild(uc);
878 child = uc;
879 } else {
880 child = ih.createElement(helperImpl.project, parent, elementName);
881 }
882
883 helperImpl.configureId(child, attrs);
884
885 childWrapper = new RuntimeConfigurable(child, propType);
886 childWrapper.setAttributes(attrs);
887 parentWrapper.addChild(childWrapper);
888 } catch (BuildException exc) {
889 throw new SAXParseException(exc.getMessage(), helperImpl.locator, exc);
890 }
891 }
892
893 /**
894 * Adds text to the element, using the wrapper.
895 *
896 * @param buf A character array of the text within the element.
897 * Will not be <code>null</code>.
898 * @param start The start element in the array.
899 * @param count The number of characters to read from the array.
900 */
901 public void characters(char[] buf, int start, int count) {
902 childWrapper.addText(buf, start, count);
903 }
904
905 /**
906 * Handles the start of an element within this one. Task containers
907 * will always use a task handler, and all other elements
908 * will always use another nested element handler.
909 *
910 * @param name The name of the element being started.
911 * Will not be <code>null</code>.
912 * @param attrs Attributes of the element being started.
913 * Will not be <code>null</code>.
914 *
915 * @exception SAXParseException if an error occurs when initialising
916 * the appropriate child handler
917 */
918 public void startElement(String name, AttributeList attrs) throws SAXParseException {
919 if (child instanceof TaskContainer) {
920 // taskcontainer nested element can contain other tasks - no other
921 // nested elements possible
922 new TaskHandler(helperImpl, this, (TaskContainer) child,
923 childWrapper, target).init(name, attrs);
924 } else {
925 new NestedElementHandler(helperImpl, this, child,
926 childWrapper, target).init(name, attrs);
927 }
928 }
929 }
930
931 /**
932 * Handler for all data types directly subordinate to project or target.
933 */
934 static class DataTypeHandler extends AbstractHandler {
935 /** Parent target, if any. */
936 private Target target;
937 /** The element being configured. */
938 private Object element;
939 /** Wrapper for this element, if it's part of a target. */
940 private RuntimeConfigurable wrapper = null;
941
942 /**
943 * Constructor with a target specified.
944 *
945 * @param parentHandler The handler which should be restored to the
946 * parser at the end of the element.
947 * Must not be <code>null</code>.
948 *
949 * @param target The parent target of this element.
950 * Must not be <code>null</code>.
951 */
952 public DataTypeHandler(ProjectHelperImpl helperImpl,
953 DocumentHandler parentHandler, Target target) {
954 super(helperImpl, parentHandler);
955 this.target = target;
956 }
957
958 /**
959 * Initialisation routine called after handler creation
960 * with the element name and attributes. This configures
961 * the element with its attributes and sets it up with
962 * its parent container (if any). Nested elements are then
963 * added later as the parser encounters them.
964 *
965 * @param propType Name of the element which caused this handler
966 * to be created. Must not be <code>null</code>.
967 *
968 * @param attrs Attributes of the element which caused this
969 * handler to be created. Must not be <code>null</code>.
970 *
971 * @exception SAXParseException in case of error, such as a
972 * BuildException being thrown during configuration.
973 */
974 public void init(String propType, AttributeList attrs) throws SAXParseException {
975 try {
976 element = helperImpl.project.createDataType(propType);
977 if (element == null) {
978 throw new BuildException("Unknown data type " + propType);
979 }
980
981 wrapper = new RuntimeConfigurable(element, propType);
982 wrapper.setAttributes(attrs);
983 target.addDataType(wrapper);
984 } catch (BuildException exc) {
985 throw new SAXParseException(exc.getMessage(), helperImpl.locator, exc);
986 }
987 }
988
989 /**
990 * Adds text to the using the wrapper.
991 *
992 * @param buf A character array of the text within the element.
993 * Will not be <code>null</code>.
994 * @param start The start element in the array.
995 * @param count The number of characters to read from the array.
996 *
997 * @see ProjectHelper#addText(Project,Object,char[],int,int)
998 */
999 public void characters(char[] buf, int start, int count) {
1000 wrapper.addText(buf, start, count);
1001 }
1002
1003 /**
1004 * Handles the start of an element within this one.
1005 * This will always use a nested element handler.
1006 *
1007 * @param name The name of the element being started.
1008 * Will not be <code>null</code>.
1009 * @param attrs Attributes of the element being started.
1010 * Will not be <code>null</code>.
1011 *
1012 * @exception SAXParseException if an error occurs when initialising
1013 * the child handler
1014 */
1015 public void startElement(String name, AttributeList attrs) throws SAXParseException {
1016 new NestedElementHandler(helperImpl, this, element, wrapper, target).init(name, attrs);
1017 }
1018 }
1019
1020 /**
1021 * Scans an attribute list for the <code>id</code> attribute and
1022 * stores a reference to the target object in the project if an
1023 * id is found.
1024 * <p>
1025 * This method was moved out of the configure method to allow
1026 * it to be executed at parse time.
1027 *
1028 * @see #configure(Object,AttributeList,Project)
1029 */
1030 private void configureId(Object target, AttributeList attr) {
1031 String id = attr.getValue("id");
1032 if (id != null) {
1033 project.addReference(id, target);
1034 }
1035 }
1036 }