1. /*
  2. * Copyright 2000-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.io.File;
  19. import java.io.FileOutputStream;
  20. import java.io.FileWriter;
  21. import java.io.IOException;
  22. import java.io.OutputStreamWriter;
  23. import java.io.PrintWriter;
  24. import java.io.UnsupportedEncodingException;
  25. import java.util.Enumeration;
  26. import java.util.Hashtable;
  27. import java.util.Vector;
  28. import org.apache.tools.ant.BuildException;
  29. import org.apache.tools.ant.IntrospectionHelper;
  30. import org.apache.tools.ant.Task;
  31. import org.apache.tools.ant.TaskContainer;
  32. import org.apache.tools.ant.types.EnumeratedAttribute;
  33. import org.apache.tools.ant.types.Reference;
  34. /**
  35. * Creates a partial DTD for Ant from the currently known tasks.
  36. *
  37. *
  38. * @version $Revision: 1.37.2.4 $
  39. *
  40. * @since Ant 1.1
  41. *
  42. * @ant.task category="xml"
  43. */
  44. public class AntStructure extends Task {
  45. private final String lSep = System.getProperty("line.separator");
  46. private static final String BOOLEAN = "%boolean;";
  47. private static final String TASKS = "%tasks;";
  48. private static final String TYPES = "%types;";
  49. private Hashtable visited = new Hashtable();
  50. private File output;
  51. /**
  52. * The output file.
  53. * @param output the output file
  54. */
  55. public void setOutput(File output) {
  56. this.output = output;
  57. }
  58. /**
  59. * Build the antstructure DTD.
  60. *
  61. * @exception BuildException if the DTD cannot be written.
  62. */
  63. public void execute() throws BuildException {
  64. if (output == null) {
  65. throw new BuildException("output attribute is required", getLocation());
  66. }
  67. PrintWriter out = null;
  68. try {
  69. try {
  70. out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output), "UTF8"));
  71. } catch (UnsupportedEncodingException ue) {
  72. /*
  73. * Plain impossible with UTF8, see
  74. * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html
  75. *
  76. * fallback to platform specific anyway.
  77. */
  78. out = new PrintWriter(new FileWriter(output));
  79. }
  80. printHead(out, getProject().getTaskDefinitions().keys(),
  81. getProject().getDataTypeDefinitions().keys());
  82. printTargetDecl(out);
  83. Enumeration dataTypes = getProject().getDataTypeDefinitions().keys();
  84. while (dataTypes.hasMoreElements()) {
  85. String typeName = (String) dataTypes.nextElement();
  86. printElementDecl(out, typeName,
  87. (Class) getProject().getDataTypeDefinitions().get(typeName));
  88. }
  89. Enumeration tasks = getProject().getTaskDefinitions().keys();
  90. while (tasks.hasMoreElements()) {
  91. String taskName = (String) tasks.nextElement();
  92. printElementDecl(out, taskName,
  93. (Class) getProject().getTaskDefinitions().get(taskName));
  94. }
  95. } catch (IOException ioe) {
  96. throw new BuildException("Error writing "
  97. + output.getAbsolutePath(), ioe, getLocation());
  98. } finally {
  99. if (out != null) {
  100. out.close();
  101. }
  102. visited.clear();
  103. }
  104. }
  105. /**
  106. * Prints the header of the generated output.
  107. *
  108. * <p>Basically this prints the XML declaration, defines some
  109. * entities and the project element.</p>
  110. */
  111. private void printHead(PrintWriter out, Enumeration tasks,
  112. Enumeration types) {
  113. out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
  114. out.println("<!ENTITY % boolean \"(true|false|on|off|yes|no)\">");
  115. out.print("<!ENTITY % tasks \"");
  116. boolean first = true;
  117. while (tasks.hasMoreElements()) {
  118. String taskName = (String) tasks.nextElement();
  119. if (!first) {
  120. out.print(" | ");
  121. } else {
  122. first = false;
  123. }
  124. out.print(taskName);
  125. }
  126. out.println("\">");
  127. out.print("<!ENTITY % types \"");
  128. first = true;
  129. while (types.hasMoreElements()) {
  130. String typeName = (String) types.nextElement();
  131. if (!first) {
  132. out.print(" | ");
  133. } else {
  134. first = false;
  135. }
  136. out.print(typeName);
  137. }
  138. out.println("\">");
  139. out.println("");
  140. out.print("<!ELEMENT project (target | ");
  141. out.print(TASKS);
  142. out.print(" | ");
  143. out.print(TYPES);
  144. out.println(")*>");
  145. out.println("<!ATTLIST project");
  146. out.println(" name CDATA #IMPLIED");
  147. out.println(" default CDATA #IMPLIED");
  148. out.println(" basedir CDATA #IMPLIED>");
  149. out.println("");
  150. }
  151. /**
  152. * Prints the definition for the target element.
  153. */
  154. private void printTargetDecl(PrintWriter out) {
  155. out.print("<!ELEMENT target (");
  156. out.print(TASKS);
  157. out.print(" | ");
  158. out.print(TYPES);
  159. out.println(")*>");
  160. out.println("");
  161. out.println("<!ATTLIST target");
  162. out.println(" id ID #IMPLIED");
  163. out.println(" name CDATA #REQUIRED");
  164. out.println(" if CDATA #IMPLIED");
  165. out.println(" unless CDATA #IMPLIED");
  166. out.println(" depends CDATA #IMPLIED");
  167. out.println(" description CDATA #IMPLIED>");
  168. out.println("");
  169. }
  170. /**
  171. * Print the definition for a given element.
  172. */
  173. private void printElementDecl(PrintWriter out, String name, Class element)
  174. throws BuildException {
  175. if (visited.containsKey(name)) {
  176. return;
  177. }
  178. visited.put(name, "");
  179. IntrospectionHelper ih = null;
  180. try {
  181. ih = IntrospectionHelper.getHelper(element);
  182. } catch (Throwable t) {
  183. /*
  184. * XXX - failed to load the class properly.
  185. *
  186. * should we print a warning here?
  187. */
  188. return;
  189. }
  190. StringBuffer sb = new StringBuffer("<!ELEMENT ");
  191. sb.append(name).append(" ");
  192. if (org.apache.tools.ant.types.Reference.class.equals(element)) {
  193. sb.append("EMPTY>").append(lSep);
  194. sb.append("<!ATTLIST ").append(name);
  195. sb.append(lSep).append(" id ID #IMPLIED");
  196. sb.append(lSep).append(" refid IDREF #IMPLIED");
  197. sb.append(">").append(lSep);
  198. out.println(sb);
  199. return;
  200. }
  201. Vector v = new Vector();
  202. if (ih.supportsCharacters()) {
  203. v.addElement("#PCDATA");
  204. }
  205. if (TaskContainer.class.isAssignableFrom(element)) {
  206. v.addElement(TASKS);
  207. }
  208. Enumeration e = ih.getNestedElements();
  209. while (e.hasMoreElements()) {
  210. v.addElement(e.nextElement());
  211. }
  212. if (v.isEmpty()) {
  213. sb.append("EMPTY");
  214. } else {
  215. sb.append("(");
  216. final int count = v.size();
  217. for (int i = 0; i < count; i++) {
  218. if (i != 0) {
  219. sb.append(" | ");
  220. }
  221. sb.append(v.elementAt(i));
  222. }
  223. sb.append(")");
  224. if (count > 1 || !v.elementAt(0).equals("#PCDATA")) {
  225. sb.append("*");
  226. }
  227. }
  228. sb.append(">");
  229. out.println(sb);
  230. sb = new StringBuffer("<!ATTLIST ");
  231. sb.append(name);
  232. sb.append(lSep).append(" id ID #IMPLIED");
  233. e = ih.getAttributes();
  234. while (e.hasMoreElements()) {
  235. String attrName = (String) e.nextElement();
  236. if ("id".equals(attrName)) {
  237. continue;
  238. }
  239. sb.append(lSep).append(" ").append(attrName).append(" ");
  240. Class type = ih.getAttributeType(attrName);
  241. if (type.equals(java.lang.Boolean.class)
  242. || type.equals(java.lang.Boolean.TYPE)) {
  243. sb.append(BOOLEAN).append(" ");
  244. } else if (Reference.class.isAssignableFrom(type)) {
  245. sb.append("IDREF ");
  246. } else if (EnumeratedAttribute.class.isAssignableFrom(type)) {
  247. try {
  248. EnumeratedAttribute ea =
  249. (EnumeratedAttribute) type.newInstance();
  250. String[] values = ea.getValues();
  251. if (values == null
  252. || values.length == 0
  253. || !areNmtokens(values)) {
  254. sb.append("CDATA ");
  255. } else {
  256. sb.append("(");
  257. for (int i = 0; i < values.length; i++) {
  258. if (i != 0) {
  259. sb.append(" | ");
  260. }
  261. sb.append(values[i]);
  262. }
  263. sb.append(") ");
  264. }
  265. } catch (InstantiationException ie) {
  266. sb.append("CDATA ");
  267. } catch (IllegalAccessException ie) {
  268. sb.append("CDATA ");
  269. }
  270. } else {
  271. sb.append("CDATA ");
  272. }
  273. sb.append("#IMPLIED");
  274. }
  275. sb.append(">").append(lSep);
  276. out.println(sb);
  277. final int count = v.size();
  278. for (int i = 0; i < count; i++) {
  279. String nestedName = (String) v.elementAt(i);
  280. if (!"#PCDATA".equals(nestedName)
  281. && !TASKS.equals(nestedName)
  282. && !TYPES.equals(nestedName)) {
  283. printElementDecl(out, nestedName, ih.getElementType(nestedName));
  284. }
  285. }
  286. }
  287. /**
  288. * Does this String match the XML-NMTOKEN production?
  289. * @param s the string to test
  290. * @return true if the string matches the XML-NMTOKEN
  291. */
  292. protected boolean isNmtoken(String s) {
  293. final int length = s.length();
  294. for (int i = 0; i < length; i++) {
  295. char c = s.charAt(i);
  296. // XXX - we are committing CombiningChar and Extender here
  297. if (!Character.isLetterOrDigit(c)
  298. && c != '.' && c != '-' && c != '_' && c != ':') {
  299. return false;
  300. }
  301. }
  302. return true;
  303. }
  304. /**
  305. * Do the Strings all match the XML-NMTOKEN production?
  306. *
  307. * <p>Otherwise they are not suitable as an enumerated attribute,
  308. * for example.</p>
  309. * @param s the array of string to test
  310. * @return true if all the strings in the array math XML-NMTOKEN
  311. */
  312. protected boolean areNmtokens(String[] s) {
  313. for (int i = 0; i < s.length; i++) {
  314. if (!isNmtoken(s[i])) {
  315. return false;
  316. }
  317. }
  318. return true;
  319. }
  320. }