1. /*
  2. * Copyright 2003-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Locale;
  22. import java.util.HashMap;
  23. import java.util.Iterator;
  24. import org.apache.tools.ant.AntTypeDefinition;
  25. import org.apache.tools.ant.BuildException;
  26. import org.apache.tools.ant.ComponentHelper;
  27. import org.apache.tools.ant.Project;
  28. import org.apache.tools.ant.ProjectHelper;
  29. import org.apache.tools.ant.RuntimeConfigurable;
  30. import org.apache.tools.ant.Task;
  31. import org.apache.tools.ant.TaskContainer;
  32. import org.apache.tools.ant.UnknownElement;
  33. /**
  34. * Describe class <code>MacroDef</code> here.
  35. *
  36. * @since Ant 1.6
  37. */
  38. public class MacroDef extends AntlibDefinition {
  39. private NestedSequential nestedSequential;
  40. private String name;
  41. private List attributes = new ArrayList();
  42. private Map elements = new HashMap();
  43. private String textName = null;
  44. private Text text = null;
  45. private boolean hasImplicitElement = false;
  46. /**
  47. * Name of the definition
  48. * @param name the name of the definition
  49. */
  50. public void setName(String name) {
  51. this.name = name;
  52. }
  53. /**
  54. * Add the text element.
  55. * @param text the nested text element to add
  56. * @since ant 1.6.1
  57. */
  58. public void addConfiguredText(Text text) {
  59. if (this.text != null) {
  60. throw new BuildException(
  61. "Only one nested text element allowed");
  62. }
  63. if (text.getName() == null) {
  64. throw new BuildException(
  65. "the text nested element needed a \"name\" attribute");
  66. }
  67. // Check if used by attributes
  68. for (Iterator i = attributes.iterator(); i.hasNext();) {
  69. Attribute attribute = (Attribute) i.next();
  70. if (text.getName().equals(attribute.getName())) {
  71. throw new BuildException(
  72. "the name \"" + text.getName()
  73. + "\" is already used as an attribute");
  74. }
  75. }
  76. this.text = text;
  77. this.textName = text.getName();
  78. }
  79. /**
  80. * @return the nested text element
  81. * @since ant 1.6.1
  82. */
  83. public Text getText() {
  84. return text;
  85. }
  86. /**
  87. * This is the sequential nested element of the macrodef.
  88. *
  89. * @return a sequential element to be configured.
  90. */
  91. public NestedSequential createSequential() {
  92. if (this.nestedSequential != null) {
  93. throw new BuildException("Only one sequential allowed");
  94. }
  95. this.nestedSequential = new NestedSequential();
  96. return this.nestedSequential;
  97. }
  98. /**
  99. * The class corresponding to the sequential nested element.
  100. * This is a simple task container.
  101. */
  102. public static class NestedSequential implements TaskContainer {
  103. private List nested = new ArrayList();
  104. /**
  105. * Add a task or type to the container.
  106. *
  107. * @param task an unknown element.
  108. */
  109. public void addTask(Task task) {
  110. nested.add(task);
  111. }
  112. /**
  113. * @return the list of unknown elements
  114. */
  115. public List getNested() {
  116. return nested;
  117. }
  118. /**
  119. * A compare function to compare this with another
  120. * NestedSequential.
  121. * It calls similar on the nested unknown elements.
  122. *
  123. * @param other the nested sequential to compare with.
  124. * @return true if they are similar, false otherwise
  125. */
  126. public boolean similar(NestedSequential other) {
  127. if (nested.size() != other.nested.size()) {
  128. return false;
  129. }
  130. for (int i = 0; i < nested.size(); ++i) {
  131. UnknownElement me = (UnknownElement) nested.get(i);
  132. UnknownElement o = (UnknownElement) other.nested.get(i);
  133. if (!me.similar(o)) {
  134. return false;
  135. }
  136. }
  137. return true;
  138. }
  139. }
  140. /**
  141. * Convert the nested sequential to an unknown element
  142. * @return the nested sequential as an unknown element.
  143. */
  144. public UnknownElement getNestedTask() {
  145. UnknownElement ret = new UnknownElement("sequential");
  146. ret.setTaskName("sequential");
  147. ret.setNamespace("");
  148. ret.setQName("sequential");
  149. new RuntimeConfigurable(ret, "sequential");
  150. for (int i = 0; i < nestedSequential.getNested().size(); ++i) {
  151. UnknownElement e =
  152. (UnknownElement) nestedSequential.getNested().get(i);
  153. ret.addChild(e);
  154. ret.getWrapper().addChild(e.getWrapper());
  155. }
  156. return ret;
  157. }
  158. /**
  159. * @return the nested Attributes
  160. */
  161. public List getAttributes() {
  162. return attributes;
  163. }
  164. /**
  165. * @return the nested elements
  166. */
  167. public Map getElements() {
  168. return elements;
  169. }
  170. /**
  171. * Check if a character is a valid character for an element or
  172. * attribute name
  173. * @param c the character to check
  174. * @return true if the character is a letter or digit or '.' or '-'
  175. * attribute name
  176. */
  177. public static boolean isValidNameCharacter(char c) {
  178. // ? is there an xml api for this ?
  179. return Character.isLetterOrDigit(c) || c == '.' || c == '-';
  180. }
  181. /**
  182. * Check if a string is a valid name for an element or
  183. * attribute
  184. * @param name the string to check
  185. * @return true if the name consists of valid name characters
  186. */
  187. private static boolean isValidName(String name) {
  188. if (name.length() == 0) {
  189. return false;
  190. }
  191. for (int i = 0; i < name.length(); ++i) {
  192. if (!isValidNameCharacter(name.charAt(i))) {
  193. return false;
  194. }
  195. }
  196. return true;
  197. }
  198. /**
  199. * Add an attribute element.
  200. *
  201. * @param attribute an attribute nested element.
  202. */
  203. public void addConfiguredAttribute(Attribute attribute) {
  204. if (attribute.getName() == null) {
  205. throw new BuildException(
  206. "the attribute nested element needed a \"name\" attribute");
  207. }
  208. if (attribute.getName().equals(textName)) {
  209. throw new BuildException(
  210. "the attribute name \"" + attribute.getName()
  211. + "\" has already been used by the text element");
  212. }
  213. for (int i = 0; i < attributes.size(); ++i) {
  214. if (((Attribute) attributes.get(i)).getName().equals(
  215. attribute.getName())) {
  216. throw new BuildException(
  217. "the attribute " + attribute.getName()
  218. + " has already been specified");
  219. }
  220. }
  221. attributes.add(attribute);
  222. }
  223. /**
  224. * Add an element element.
  225. *
  226. * @param element an element nested element.
  227. */
  228. public void addConfiguredElement(TemplateElement element) {
  229. if (element.getName() == null) {
  230. throw new BuildException(
  231. "the element nested element needed a \"name\" attribute");
  232. }
  233. if (elements.get(element.getName()) != null) {
  234. throw new BuildException(
  235. "the element " + element.getName()
  236. + " has already been specified");
  237. }
  238. if (hasImplicitElement
  239. || (element.isImplicit() && elements.size() != 0)) {
  240. throw new BuildException(
  241. "Only one element allowed when using implicit elements");
  242. }
  243. hasImplicitElement = element.isImplicit();
  244. elements.put(element.getName(), element);
  245. }
  246. /**
  247. * Create a new ant type based on the embedded tasks and types.
  248. *
  249. */
  250. public void execute() {
  251. if (nestedSequential == null) {
  252. throw new BuildException("Missing sequential element");
  253. }
  254. if (name == null) {
  255. throw new BuildException("Name not specified");
  256. }
  257. name = ProjectHelper.genComponentName(getURI(), name);
  258. MyAntTypeDefinition def = new MyAntTypeDefinition(this);
  259. def.setName(name);
  260. def.setClass(MacroInstance.class);
  261. ComponentHelper helper = ComponentHelper.getComponentHelper(
  262. getProject());
  263. helper.addDataTypeDefinition(def);
  264. }
  265. /**
  266. * A nested element for the MacroDef task.
  267. *
  268. */
  269. public static class Attribute {
  270. private String name;
  271. private String defaultValue;
  272. private String description;
  273. /**
  274. * The name of the attribute.
  275. *
  276. * @param name the name of the attribute
  277. */
  278. public void setName(String name) {
  279. if (!isValidName(name)) {
  280. throw new BuildException(
  281. "Illegal name [" + name + "] for attribute");
  282. }
  283. this.name = name.toLowerCase(Locale.US);
  284. }
  285. /**
  286. * @return the name of the attribute
  287. */
  288. public String getName() {
  289. return name;
  290. }
  291. /**
  292. * The default value to use if the parameter is not
  293. * used in the templated instance.
  294. *
  295. * @param defaultValue the default value
  296. */
  297. public void setDefault(String defaultValue) {
  298. this.defaultValue = defaultValue;
  299. }
  300. /**
  301. * @return the default value, null if not set
  302. */
  303. public String getDefault() {
  304. return defaultValue;
  305. }
  306. /**
  307. * @param desc Description of the element.
  308. * @since ant 1.6.1
  309. */
  310. public void setDescription(String desc) {
  311. description = desc;
  312. }
  313. /**
  314. * @return the description of the element, or <code>null</code> if
  315. * no description is available.
  316. * @since ant 1.6.1
  317. */
  318. public String getDescription() {
  319. return description;
  320. }
  321. /**
  322. * equality method
  323. *
  324. * @param obj an <code>Object</code> value
  325. * @return a <code>boolean</code> value
  326. */
  327. public boolean equals(Object obj) {
  328. if (obj == null) {
  329. return false;
  330. }
  331. if (obj.getClass() != getClass()) {
  332. return false;
  333. }
  334. Attribute other = (Attribute) obj;
  335. if (name == null) {
  336. if (other.name != null) {
  337. return false;
  338. }
  339. } else if (!name.equals(other.name)) {
  340. return false;
  341. }
  342. if (defaultValue == null) {
  343. if (other.defaultValue != null) {
  344. return false;
  345. }
  346. } else if (!defaultValue.equals(other.defaultValue)) {
  347. return false;
  348. }
  349. return true;
  350. }
  351. /**
  352. * @return a hash code value for this object.
  353. */
  354. public int hashCode() {
  355. return objectHashCode(defaultValue) + objectHashCode(name);
  356. }
  357. }
  358. /**
  359. * A nested text element for the MacroDef task.
  360. * @since ant 1.6.1
  361. */
  362. public static class Text {
  363. private String name;
  364. private boolean optional;
  365. private boolean trim;
  366. private String description;
  367. /**
  368. * The name of the attribute.
  369. *
  370. * @param name the name of the attribute
  371. */
  372. public void setName(String name) {
  373. if (!isValidName(name)) {
  374. throw new BuildException(
  375. "Illegal name [" + name + "] for attribute");
  376. }
  377. this.name = name.toLowerCase(Locale.US);
  378. }
  379. /**
  380. * @return the name of the attribute
  381. */
  382. public String getName() {
  383. return name;
  384. }
  385. /**
  386. * The optional attribute of the text element.
  387. *
  388. * @param optional if true this is optional
  389. */
  390. public void setOptional(boolean optional) {
  391. this.optional = optional;
  392. }
  393. /**
  394. * @return true if the text is optional
  395. */
  396. public boolean getOptional() {
  397. return optional;
  398. }
  399. /**
  400. * The trim attribute of the text element.
  401. *
  402. * @param trim if true this String.trim() is called on
  403. * the contents of the text element.
  404. */
  405. public void setTrim(boolean trim) {
  406. this.trim = trim;
  407. }
  408. /**
  409. * @return true if the text is trim
  410. */
  411. public boolean getTrim() {
  412. return trim;
  413. }
  414. /**
  415. * @param desc Description of the text.
  416. */
  417. public void setDescription(String desc) {
  418. description = desc;
  419. }
  420. /**
  421. * @return the description of the text, or <code>null</code> if
  422. * no description is available.
  423. */
  424. public String getDescription() {
  425. return description;
  426. }
  427. /**
  428. * equality method
  429. *
  430. * @param obj an <code>Object</code> value
  431. * @return a <code>boolean</code> value
  432. */
  433. public boolean equals(Object obj) {
  434. if (obj == null) {
  435. return false;
  436. }
  437. if (obj.getClass() != getClass()) {
  438. return false;
  439. }
  440. Text other = (Text) obj;
  441. if (name == null) {
  442. if (other.name != null) {
  443. return false;
  444. }
  445. } else if (!name.equals(other.name)) {
  446. return false;
  447. }
  448. if (optional != other.optional) {
  449. return false;
  450. }
  451. if (trim != other.trim) {
  452. return false;
  453. }
  454. return true;
  455. }
  456. /**
  457. * @return a hash code value for this object.
  458. */
  459. public int hashCode() {
  460. return objectHashCode(name);
  461. }
  462. }
  463. /**
  464. * A nested element for the MacroDef task.
  465. *
  466. */
  467. public static class TemplateElement {
  468. private String name;
  469. private boolean optional = false;
  470. private boolean implicit = false;
  471. private String description;
  472. /**
  473. * The name of the element.
  474. *
  475. * @param name the name of the element.
  476. */
  477. public void setName(String name) {
  478. if (!isValidName(name)) {
  479. throw new BuildException(
  480. "Illegal name [" + name + "] for attribute");
  481. }
  482. this.name = name.toLowerCase(Locale.US);
  483. }
  484. /**
  485. * @return the name of the element.
  486. */
  487. public String getName() {
  488. return name;
  489. }
  490. /**
  491. * is this element optional ?
  492. *
  493. * @param optional if true this element may be left out, default
  494. * is false.
  495. */
  496. public void setOptional(boolean optional) {
  497. this.optional = optional;
  498. }
  499. /**
  500. * @return the optional attribute
  501. */
  502. public boolean isOptional() {
  503. return optional;
  504. }
  505. /**
  506. * is this element implicit ?
  507. *
  508. * @param implicit if true this element may be left out, default
  509. * is false.
  510. */
  511. public void setImplicit(boolean implicit) {
  512. this.implicit = implicit;
  513. }
  514. /**
  515. * @return the implicit attribute
  516. */
  517. public boolean isImplicit() {
  518. return implicit;
  519. }
  520. /**
  521. * @param desc Description of the element.
  522. * @since ant 1.6.1
  523. */
  524. public void setDescription(String desc) {
  525. description = desc;
  526. }
  527. /**
  528. * @return the description of the element, or <code>null</code> if
  529. * no description is available.
  530. * @since ant 1.6.1
  531. */
  532. public String getDescription() {
  533. return description;
  534. }
  535. /**
  536. * equality method
  537. *
  538. * @param obj an <code>Object</code> value
  539. * @return a <code>boolean</code> value
  540. */
  541. public boolean equals(Object obj) {
  542. if (obj == null) {
  543. return false;
  544. }
  545. if (obj.getClass() != getClass()) {
  546. return false;
  547. }
  548. TemplateElement other = (TemplateElement) obj;
  549. if (name == null) {
  550. if (other.name != null) {
  551. return false;
  552. }
  553. } else if (!name.equals(other.name)) {
  554. return false;
  555. }
  556. return optional == other.optional && implicit == other.implicit;
  557. }
  558. /**
  559. * @return a hash code value for this object.
  560. */
  561. public int hashCode() {
  562. return objectHashCode(name)
  563. + (optional ? 1 : 0) + (implicit ? 1 : 0);
  564. }
  565. }
  566. /**
  567. * similar equality method for macrodef, ignores project and
  568. * runtime info.
  569. *
  570. * @param obj an <code>Object</code> value
  571. * @return a <code>boolean</code> value
  572. */
  573. public boolean similar(Object obj) {
  574. if (obj == this) {
  575. return true;
  576. }
  577. if (obj == null) {
  578. return false;
  579. }
  580. if (!obj.getClass().equals(getClass())) {
  581. return false;
  582. }
  583. MacroDef other = (MacroDef) obj;
  584. if (name == null) {
  585. return other.name == null;
  586. }
  587. if (!name.equals(other.name)) {
  588. return false;
  589. }
  590. if (text == null) {
  591. if (other.text != null) {
  592. return false;
  593. }
  594. } else {
  595. if (!text.equals(other.text)) {
  596. return false;
  597. }
  598. }
  599. if (getURI() == null || getURI().equals("")
  600. || getURI().equals(ProjectHelper.ANT_CORE_URI)) {
  601. if (!(other.getURI() == null || other.getURI().equals("")
  602. || other.getURI().equals(ProjectHelper.ANT_CORE_URI))) {
  603. return false;
  604. }
  605. } else {
  606. if (!getURI().equals(other.getURI())) {
  607. return false;
  608. }
  609. }
  610. if (!nestedSequential.similar(other.nestedSequential)) {
  611. return false;
  612. }
  613. if (!attributes.equals(other.attributes)) {
  614. return false;
  615. }
  616. if (!elements.equals(other.elements)) {
  617. return false;
  618. }
  619. return true;
  620. }
  621. /**
  622. * extends AntTypeDefinition, on create
  623. * of the object, the template macro definition
  624. * is given.
  625. */
  626. private static class MyAntTypeDefinition extends AntTypeDefinition {
  627. private MacroDef macroDef;
  628. /**
  629. * Creates a new <code>MyAntTypeDefinition</code> instance.
  630. *
  631. * @param macroDef a <code>MacroDef</code> value
  632. */
  633. public MyAntTypeDefinition(MacroDef macroDef) {
  634. this.macroDef = macroDef;
  635. }
  636. /**
  637. * create an instance of the definition.
  638. * The instance may be wrapped in a proxy class.
  639. * @param project the current project
  640. * @return the created object
  641. */
  642. public Object create(Project project) {
  643. Object o = super.create(project);
  644. if (o == null) {
  645. return null;
  646. }
  647. ((MacroInstance) o).setMacroDef(macroDef);
  648. return o;
  649. }
  650. /**
  651. * Equality method for this definition
  652. *
  653. * @param other another definition
  654. * @param project the current project
  655. * @return true if the definitions are the same
  656. */
  657. public boolean sameDefinition(AntTypeDefinition other, Project project) {
  658. if (!super.sameDefinition(other, project)) {
  659. return false;
  660. }
  661. MyAntTypeDefinition otherDef = (MyAntTypeDefinition) other;
  662. return macroDef.similar(otherDef.macroDef);
  663. }
  664. /**
  665. * Similar method for this definition
  666. *
  667. * @param other another definition
  668. * @param project the current project
  669. * @return true if the definitions are the same
  670. */
  671. public boolean similarDefinition(
  672. AntTypeDefinition other, Project project) {
  673. if (!super.similarDefinition(other, project)) {
  674. return false;
  675. }
  676. MyAntTypeDefinition otherDef = (MyAntTypeDefinition) other;
  677. return macroDef.similar(otherDef.macroDef);
  678. }
  679. }
  680. private static int objectHashCode(Object o) {
  681. if (o == null) {
  682. return 0;
  683. } else {
  684. return o.hashCode();
  685. }
  686. }
  687. }