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.taskdefs;
020:
021: import java.util.ArrayList;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.Locale;
025: import java.util.HashMap;
026: import java.util.Iterator;
027:
028: import org.apache.tools.ant.AntTypeDefinition;
029: import org.apache.tools.ant.BuildException;
030: import org.apache.tools.ant.ComponentHelper;
031: import org.apache.tools.ant.Project;
032: import org.apache.tools.ant.ProjectHelper;
033: import org.apache.tools.ant.RuntimeConfigurable;
034: import org.apache.tools.ant.Task;
035: import org.apache.tools.ant.TaskContainer;
036: import org.apache.tools.ant.UnknownElement;
037:
038: /**
039: * Describe class <code>MacroDef</code> here.
040: *
041: * @since Ant 1.6
042: */
043: public class MacroDef extends AntlibDefinition {
044:
045: private NestedSequential nestedSequential;
046: private String name;
047: private boolean backTrace = true;
048: private List attributes = new ArrayList();
049: private Map elements = new HashMap();
050: private String textName = null;
051: private Text text = null;
052: private boolean hasImplicitElement = false;
053:
054: /**
055: * Name of the definition
056: * @param name the name of the definition
057: */
058: public void setName(String name) {
059: this .name = name;
060: }
061:
062: /**
063: * Add the text element.
064: * @param text the nested text element to add
065: * @since ant 1.6.1
066: */
067: public void addConfiguredText(Text text) {
068: if (this .text != null) {
069: throw new BuildException(
070: "Only one nested text element allowed");
071: }
072: if (text.getName() == null) {
073: throw new BuildException(
074: "the text nested element needed a \"name\" attribute");
075: }
076: // Check if used by attributes
077: for (Iterator i = attributes.iterator(); i.hasNext();) {
078: Attribute attribute = (Attribute) i.next();
079: if (text.getName().equals(attribute.getName())) {
080: throw new BuildException("the name \"" + text.getName()
081: + "\" is already used as an attribute");
082: }
083: }
084: this .text = text;
085: this .textName = text.getName();
086: }
087:
088: /**
089: * @return the nested text element
090: * @since ant 1.6.1
091: */
092: public Text getText() {
093: return text;
094: }
095:
096: /**
097: * Set the backTrace attribute.
098: *
099: * @param backTrace if true and the macro instance generates
100: * an error, a backtrace of the location within
101: * the macro and call to the macro will be output.
102: * if false, only the location of the call to the
103: * macro will be shown. Default is true.
104: * @since ant 1.7
105: */
106: public void setBackTrace(boolean backTrace) {
107: this .backTrace = backTrace;
108: }
109:
110: /**
111: * @return the backTrace attribute.
112: * @since ant 1.7
113: */
114: public boolean getBackTrace() {
115: return backTrace;
116: }
117:
118: /**
119: * This is the sequential nested element of the macrodef.
120: *
121: * @return a sequential element to be configured.
122: */
123: public NestedSequential createSequential() {
124: if (this .nestedSequential != null) {
125: throw new BuildException("Only one sequential allowed");
126: }
127: this .nestedSequential = new NestedSequential();
128: return this .nestedSequential;
129: }
130:
131: /**
132: * The class corresponding to the sequential nested element.
133: * This is a simple task container.
134: */
135: public static class NestedSequential implements TaskContainer {
136: private List nested = new ArrayList();
137:
138: /**
139: * Add a task or type to the container.
140: *
141: * @param task an unknown element.
142: */
143: public void addTask(Task task) {
144: nested.add(task);
145: }
146:
147: /**
148: * @return the list of unknown elements
149: */
150: public List getNested() {
151: return nested;
152: }
153:
154: /**
155: * A compare function to compare this with another
156: * NestedSequential.
157: * It calls similar on the nested unknown elements.
158: *
159: * @param other the nested sequential to compare with.
160: * @return true if they are similar, false otherwise
161: */
162: public boolean similar(NestedSequential other) {
163: if (nested.size() != other.nested.size()) {
164: return false;
165: }
166: for (int i = 0; i < nested.size(); ++i) {
167: UnknownElement me = (UnknownElement) nested.get(i);
168: UnknownElement o = (UnknownElement) other.nested.get(i);
169: if (!me.similar(o)) {
170: return false;
171: }
172: }
173: return true;
174: }
175: }
176:
177: /**
178: * Convert the nested sequential to an unknown element
179: * @return the nested sequential as an unknown element.
180: */
181: public UnknownElement getNestedTask() {
182: UnknownElement ret = new UnknownElement("sequential");
183: ret.setTaskName("sequential");
184: ret.setNamespace("");
185: ret.setQName("sequential");
186: new RuntimeConfigurable(ret, "sequential");
187: for (int i = 0; i < nestedSequential.getNested().size(); ++i) {
188: UnknownElement e = (UnknownElement) nestedSequential
189: .getNested().get(i);
190: ret.addChild(e);
191: ret.getWrapper().addChild(e.getWrapper());
192: }
193: return ret;
194: }
195:
196: /**
197: * Gets this macro's attribute (and define?) list.
198: *
199: * @return the nested Attributes
200: */
201: public List getAttributes() {
202: return attributes;
203: }
204:
205: /**
206: * Gets this macro's elements.
207: *
208: * @return the map nested elements, keyed by element name, with
209: * {@link TemplateElement} values.
210: */
211: public Map getElements() {
212: return elements;
213: }
214:
215: /**
216: * Check if a character is a valid character for an element or
217: * attribute name.
218: *
219: * @param c the character to check
220: * @return true if the character is a letter or digit or '.' or '-'
221: * attribute name
222: */
223: public static boolean isValidNameCharacter(char c) {
224: // ? is there an xml api for this ?
225: return Character.isLetterOrDigit(c) || c == '.' || c == '-';
226: }
227:
228: /**
229: * Check if a string is a valid name for an element or attribute.
230: *
231: * @param name the string to check
232: * @return true if the name consists of valid name characters
233: */
234: private static boolean isValidName(String name) {
235: if (name.length() == 0) {
236: return false;
237: }
238: for (int i = 0; i < name.length(); ++i) {
239: if (!isValidNameCharacter(name.charAt(i))) {
240: return false;
241: }
242: }
243: return true;
244: }
245:
246: /**
247: * Add an attribute element.
248: *
249: * @param attribute an attribute nested element.
250: */
251: public void addConfiguredAttribute(Attribute attribute) {
252: if (attribute.getName() == null) {
253: throw new BuildException(
254: "the attribute nested element needed a \"name\" attribute");
255: }
256: if (attribute.getName().equals(textName)) {
257: throw new BuildException("the name \""
258: + attribute.getName()
259: + "\" has already been used by the text element");
260: }
261: for (int i = 0; i < attributes.size(); ++i) {
262: Attribute att = (Attribute) attributes.get(i);
263: if (att.getName().equals(attribute.getName())) {
264: throw new BuildException("the name \""
265: + attribute.getName()
266: + "\" has already been used in "
267: + "another attribute element");
268: }
269: }
270: attributes.add(attribute);
271: }
272:
273: /**
274: * Add an element element.
275: *
276: * @param element an element nested element.
277: */
278: public void addConfiguredElement(TemplateElement element) {
279: if (element.getName() == null) {
280: throw new BuildException(
281: "the element nested element needed a \"name\" attribute");
282: }
283: if (elements.get(element.getName()) != null) {
284: throw new BuildException("the element " + element.getName()
285: + " has already been specified");
286: }
287: if (hasImplicitElement
288: || (element.isImplicit() && elements.size() != 0)) {
289: throw new BuildException(
290: "Only one element allowed when using implicit elements");
291: }
292: hasImplicitElement = element.isImplicit();
293: elements.put(element.getName(), element);
294: }
295:
296: /**
297: * Create a new ant type based on the embedded tasks and types.
298: */
299: public void execute() {
300: if (nestedSequential == null) {
301: throw new BuildException("Missing sequential element");
302: }
303: if (name == null) {
304: throw new BuildException("Name not specified");
305: }
306:
307: name = ProjectHelper.genComponentName(getURI(), name);
308:
309: MyAntTypeDefinition def = new MyAntTypeDefinition(this );
310: def.setName(name);
311: def.setClass(MacroInstance.class);
312:
313: ComponentHelper helper = ComponentHelper
314: .getComponentHelper(getProject());
315:
316: helper.addDataTypeDefinition(def);
317: log("creating macro " + name, Project.MSG_VERBOSE);
318: }
319:
320: /**
321: * An attribute for the MacroDef task.
322: *
323: */
324: public static class Attribute {
325: private String name;
326: private String defaultValue;
327: private String description;
328:
329: /**
330: * The name of the attribute.
331: *
332: * @param name the name of the attribute
333: */
334: public void setName(String name) {
335: if (!isValidName(name)) {
336: throw new BuildException("Illegal name [" + name
337: + "] for attribute");
338: }
339: this .name = name.toLowerCase(Locale.US);
340: }
341:
342: /**
343: * @return the name of the attribute
344: */
345: public String getName() {
346: return name;
347: }
348:
349: /**
350: * The default value to use if the parameter is not
351: * used in the templated instance.
352: *
353: * @param defaultValue the default value
354: */
355: public void setDefault(String defaultValue) {
356: this .defaultValue = defaultValue;
357: }
358:
359: /**
360: * @return the default value, null if not set
361: */
362: public String getDefault() {
363: return defaultValue;
364: }
365:
366: /**
367: * @param desc Description of the element.
368: * @since ant 1.6.1
369: */
370: public void setDescription(String desc) {
371: description = desc;
372: }
373:
374: /**
375: * @return the description of the element, or <code>null</code> if
376: * no description is available.
377: * @since ant 1.6.1
378: */
379: public String getDescription() {
380: return description;
381: }
382:
383: /**
384: * equality method
385: *
386: * @param obj an <code>Object</code> value
387: * @return a <code>boolean</code> value
388: */
389: public boolean equals(Object obj) {
390: if (obj == null) {
391: return false;
392: }
393: if (obj.getClass() != getClass()) {
394: return false;
395: }
396: Attribute other = (Attribute) obj;
397: if (name == null) {
398: if (other.name != null) {
399: return false;
400: }
401: } else if (!name.equals(other.name)) {
402: return false;
403: }
404: if (defaultValue == null) {
405: if (other.defaultValue != null) {
406: return false;
407: }
408: } else if (!defaultValue.equals(other.defaultValue)) {
409: return false;
410: }
411: return true;
412: }
413:
414: /**
415: * @return a hash code value for this object.
416: */
417: public int hashCode() {
418: return objectHashCode(defaultValue) + objectHashCode(name);
419: }
420: }
421:
422: /**
423: * A nested text element for the MacroDef task.
424: * @since ant 1.6.1
425: */
426: public static class Text {
427: private String name;
428: private boolean optional;
429: private boolean trim;
430: private String description;
431:
432: /**
433: * The name of the attribute.
434: *
435: * @param name the name of the attribute
436: */
437: public void setName(String name) {
438: if (!isValidName(name)) {
439: throw new BuildException("Illegal name [" + name
440: + "] for attribute");
441: }
442: this .name = name.toLowerCase(Locale.US);
443: }
444:
445: /**
446: * @return the name of the attribute
447: */
448: public String getName() {
449: return name;
450: }
451:
452: /**
453: * The optional attribute of the text element.
454: *
455: * @param optional if true this is optional
456: */
457: public void setOptional(boolean optional) {
458: this .optional = optional;
459: }
460:
461: /**
462: * @return true if the text is optional
463: */
464: public boolean getOptional() {
465: return optional;
466: }
467:
468: /**
469: * The trim attribute of the text element.
470: *
471: * @param trim if true this String.trim() is called on
472: * the contents of the text element.
473: */
474: public void setTrim(boolean trim) {
475: this .trim = trim;
476: }
477:
478: /**
479: * @return true if the text is trim
480: */
481: public boolean getTrim() {
482: return trim;
483: }
484:
485: /**
486: * @param desc Description of the text.
487: */
488: public void setDescription(String desc) {
489: description = desc;
490: }
491:
492: /**
493: * @return the description of the text, or <code>null</code> if
494: * no description is available.
495: */
496: public String getDescription() {
497: return description;
498: }
499:
500: /**
501: * equality method
502: *
503: * @param obj an <code>Object</code> value
504: * @return a <code>boolean</code> value
505: */
506: public boolean equals(Object obj) {
507: if (obj == null) {
508: return false;
509: }
510: if (obj.getClass() != getClass()) {
511: return false;
512: }
513: Text other = (Text) obj;
514: if (name == null) {
515: if (other.name != null) {
516: return false;
517: }
518: } else if (!name.equals(other.name)) {
519: return false;
520: }
521: if (optional != other.optional) {
522: return false;
523: }
524: if (trim != other.trim) {
525: return false;
526: }
527: return true;
528: }
529:
530: /**
531: * @return a hash code value for this object.
532: */
533: public int hashCode() {
534: return objectHashCode(name);
535: }
536: }
537:
538: /**
539: * A nested element for the MacroDef task.
540: */
541: public static class TemplateElement {
542:
543: private String name;
544: private String description;
545: private boolean optional = false;
546: private boolean implicit = false;
547:
548: /**
549: * Sets the name of this element.
550: *
551: * @param name the name of the element
552: */
553: public void setName(String name) {
554: if (!isValidName(name)) {
555: throw new BuildException("Illegal name [" + name
556: + "] for macro element");
557: }
558: this .name = name.toLowerCase(Locale.US);
559: }
560:
561: /**
562: * Gets the name of this element.
563: *
564: * @return the name of the element.
565: */
566: public String getName() {
567: return name;
568: }
569:
570: /**
571: * Sets a textual description of this element,
572: * for build documentation purposes only.
573: *
574: * @param desc Description of the element.
575: * @since ant 1.6.1
576: */
577: public void setDescription(String desc) {
578: description = desc;
579: }
580:
581: /**
582: * Gets the description of this element.
583: *
584: * @return the description of the element, or <code>null</code> if
585: * no description is available.
586: * @since ant 1.6.1
587: */
588: public String getDescription() {
589: return description;
590: }
591:
592: /**
593: * Sets whether this element is optional.
594: *
595: * @param optional if true this element may be left out, default
596: * is false.
597: */
598: public void setOptional(boolean optional) {
599: this .optional = optional;
600: }
601:
602: /**
603: * Gets whether this element is optional.
604: *
605: * @return the optional attribute
606: */
607: public boolean isOptional() {
608: return optional;
609: }
610:
611: /**
612: * Sets whether this element is implicit.
613: *
614: * @param implicit if true this element may be left out, default
615: * is false.
616: */
617: public void setImplicit(boolean implicit) {
618: this .implicit = implicit;
619: }
620:
621: /**
622: * Gets whether this element is implicit.
623: *
624: * @return the implicit attribute
625: */
626: public boolean isImplicit() {
627: return implicit;
628: }
629:
630: /**
631: * equality method.
632: *
633: * @param obj an <code>Object</code> value
634: * @return a <code>boolean</code> value
635: */
636: public boolean equals(Object obj) {
637: if (obj == this ) {
638: return true;
639: }
640: if (obj == null || !obj.getClass().equals(getClass())) {
641: return false;
642: }
643: TemplateElement t = (TemplateElement) obj;
644: return (name == null ? t.name == null : name.equals(t.name))
645: && optional == t.optional && implicit == t.implicit;
646: }
647:
648: /**
649: * @return a hash code value for this object.
650: */
651: public int hashCode() {
652: return objectHashCode(name) + (optional ? 1 : 0)
653: + (implicit ? 1 : 0);
654: }
655:
656: } // END static class TemplateElement
657:
658: /**
659: * same or similar equality method for macrodef, ignores project and
660: * runtime info.
661: *
662: * @param obj an <code>Object</code> value
663: * @param same if true test for sameness, otherwise just similiar
664: * @return a <code>boolean</code> value
665: */
666: private boolean sameOrSimilar(Object obj, boolean same) {
667: if (obj == this ) {
668: return true;
669: }
670:
671: if (obj == null) {
672: return false;
673: }
674: if (!obj.getClass().equals(getClass())) {
675: return false;
676: }
677: MacroDef other = (MacroDef) obj;
678: if (name == null) {
679: return other.name == null;
680: }
681: if (!name.equals(other.name)) {
682: return false;
683: }
684: // Allow two macro definitions with the same location
685: // to be treated as similar - bugzilla 31215
686: if (other.getLocation() != null
687: && other.getLocation().equals(getLocation()) && !same) {
688: return true;
689: }
690: if (text == null) {
691: if (other.text != null) {
692: return false;
693: }
694: } else {
695: if (!text.equals(other.text)) {
696: return false;
697: }
698: }
699: if (getURI() == null || getURI().equals("")
700: || getURI().equals(ProjectHelper.ANT_CORE_URI)) {
701: if (!(other.getURI() == null || other.getURI().equals("") || other
702: .getURI().equals(ProjectHelper.ANT_CORE_URI))) {
703: return false;
704: }
705: } else {
706: if (!getURI().equals(other.getURI())) {
707: return false;
708: }
709: }
710:
711: if (!nestedSequential.similar(other.nestedSequential)) {
712: return false;
713: }
714: if (!attributes.equals(other.attributes)) {
715: return false;
716: }
717: if (!elements.equals(other.elements)) {
718: return false;
719: }
720: return true;
721: }
722:
723: /**
724: * Similar method for this definition
725: *
726: * @param obj another definition
727: * @return true if the definitions are similar
728: */
729: public boolean similar(Object obj) {
730: return sameOrSimilar(obj, false);
731: }
732:
733: /**
734: * Equality method for this definition
735: *
736: * @param obj another definition
737: * @return true if the definitions are the same
738: */
739: public boolean sameDefinition(Object obj) {
740: return sameOrSimilar(obj, true);
741: }
742:
743: /**
744: * extends AntTypeDefinition, on create
745: * of the object, the template macro definition
746: * is given.
747: */
748: private static class MyAntTypeDefinition extends AntTypeDefinition {
749: private MacroDef macroDef;
750:
751: /**
752: * Creates a new <code>MyAntTypeDefinition</code> instance.
753: *
754: * @param macroDef a <code>MacroDef</code> value
755: */
756: public MyAntTypeDefinition(MacroDef macroDef) {
757: this .macroDef = macroDef;
758: }
759:
760: /**
761: * Create an instance of the definition.
762: * The instance may be wrapped in a proxy class.
763: * @param project the current project
764: * @return the created object
765: */
766: public Object create(Project project) {
767: Object o = super .create(project);
768: if (o == null) {
769: return null;
770: }
771: ((MacroInstance) o).setMacroDef(macroDef);
772: return o;
773: }
774:
775: /**
776: * Equality method for this definition
777: *
778: * @param other another definition
779: * @param project the current project
780: * @return true if the definitions are the same
781: */
782: public boolean sameDefinition(AntTypeDefinition other,
783: Project project) {
784: if (!super .sameDefinition(other, project)) {
785: return false;
786: }
787: MyAntTypeDefinition otherDef = (MyAntTypeDefinition) other;
788: return macroDef.sameDefinition(otherDef.macroDef);
789: }
790:
791: /**
792: * Similar method for this definition
793: *
794: * @param other another definition
795: * @param project the current project
796: * @return true if the definitions are the same
797: */
798: public boolean similarDefinition(AntTypeDefinition other,
799: Project project) {
800: if (!super .similarDefinition(other, project)) {
801: return false;
802: }
803: MyAntTypeDefinition otherDef = (MyAntTypeDefinition) other;
804: return macroDef.similar(otherDef.macroDef);
805: }
806: }
807:
808: private static int objectHashCode(Object o) {
809: if (o == null) {
810: return 0;
811: } else {
812: return o.hashCode();
813: }
814: }
815:
816: }
|