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.io.File;
022: import java.io.FileOutputStream;
023: import java.io.FileWriter;
024: import java.io.IOException;
025: import java.io.OutputStreamWriter;
026: import java.io.PrintWriter;
027: import java.io.UnsupportedEncodingException;
028: import java.util.Enumeration;
029: import java.util.Hashtable;
030: import java.util.Vector;
031: import org.apache.tools.ant.BuildException;
032: import org.apache.tools.ant.IntrospectionHelper;
033: import org.apache.tools.ant.Project;
034: import org.apache.tools.ant.Task;
035: import org.apache.tools.ant.TaskContainer;
036: import org.apache.tools.ant.types.EnumeratedAttribute;
037: import org.apache.tools.ant.types.Reference;
038:
039: /**
040: * Creates a partial DTD for Ant from the currently known tasks.
041: *
042: *
043: * @since Ant 1.1
044: *
045: * @ant.task category="xml"
046: */
047: public class AntStructure extends Task {
048:
049: private static final String LINE_SEP = System
050: .getProperty("line.separator");
051:
052: private File output;
053: private StructurePrinter printer = new DTDPrinter();
054:
055: /**
056: * The output file.
057: * @param output the output file
058: */
059: public void setOutput(File output) {
060: this .output = output;
061: }
062:
063: /**
064: * The StructurePrinter to use.
065: * @param p the printer to use.
066: * @since Ant 1.7
067: */
068: public void add(StructurePrinter p) {
069: printer = p;
070: }
071:
072: /**
073: * Build the antstructure DTD.
074: *
075: * @exception BuildException if the DTD cannot be written.
076: */
077: public void execute() throws BuildException {
078:
079: if (output == null) {
080: throw new BuildException("output attribute is required",
081: getLocation());
082: }
083:
084: PrintWriter out = null;
085: try {
086: try {
087: out = new PrintWriter(new OutputStreamWriter(
088: new FileOutputStream(output), "UTF8"));
089: } catch (UnsupportedEncodingException ue) {
090: /*
091: * Plain impossible with UTF8, see
092: * http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html
093: *
094: * fallback to platform specific anyway.
095: */
096: out = new PrintWriter(new FileWriter(output));
097: }
098:
099: printer.printHead(out, getProject(), getProject()
100: .getTaskDefinitions(), getProject()
101: .getDataTypeDefinitions());
102:
103: printer.printTargetDecl(out);
104:
105: Enumeration dataTypes = getProject()
106: .getDataTypeDefinitions().keys();
107: while (dataTypes.hasMoreElements()) {
108: String typeName = (String) dataTypes.nextElement();
109: printer.printElementDecl(out, getProject(), typeName,
110: (Class) getProject().getDataTypeDefinitions()
111: .get(typeName));
112: }
113:
114: Enumeration tasks = getProject().getTaskDefinitions()
115: .keys();
116: while (tasks.hasMoreElements()) {
117: String tName = (String) tasks.nextElement();
118: printer.printElementDecl(out, getProject(), tName,
119: (Class) getProject().getTaskDefinitions().get(
120: tName));
121: }
122:
123: printer.printTail(out);
124:
125: } catch (IOException ioe) {
126: throw new BuildException("Error writing "
127: + output.getAbsolutePath(), ioe, getLocation());
128: } finally {
129: if (out != null) {
130: out.close();
131: }
132: }
133: }
134:
135: /**
136: * Writes the actual structure information.
137: *
138: * <p>{@link #printHead}, {@link #printTargetDecl} and {@link #printTail}
139: * are called exactly once, {@link #printElementDecl} once for
140: * each declared task and type.</p>
141: */
142: public static interface StructurePrinter {
143: /**
144: * Prints the header of the generated output.
145: *
146: * @param out PrintWriter to write to.
147: * @param p Project instance for the current task
148: * @param tasks map (name to implementing class)
149: * @param types map (name to implementing class)
150: * data types.
151: */
152: void printHead(PrintWriter out, Project p, Hashtable tasks,
153: Hashtable types);
154:
155: /**
156: * Prints the definition for the target element.
157: * @param out PrintWriter to write to.
158: */
159: void printTargetDecl(PrintWriter out);
160:
161: /**
162: * Print the definition for a given element.
163: *
164: * @param out PrintWriter to write to.
165: * @param p Project instance for the current task
166: * @param name element name.
167: * @param element class of the defined element.
168: */
169: void printElementDecl(PrintWriter out, Project p, String name,
170: Class element);
171:
172: /**
173: * Prints the trailer.
174: * @param out PrintWriter to write to.
175: */
176: void printTail(PrintWriter out);
177: }
178:
179: private static class DTDPrinter implements StructurePrinter {
180:
181: private static final String BOOLEAN = "%boolean;";
182: private static final String TASKS = "%tasks;";
183: private static final String TYPES = "%types;";
184:
185: private Hashtable visited = new Hashtable();
186:
187: public void printTail(PrintWriter out) {
188: visited.clear();
189: }
190:
191: public void printHead(PrintWriter out, Project p,
192: Hashtable tasks, Hashtable types) {
193: printHead(out, tasks.keys(), types.keys());
194: }
195:
196: /**
197: * Prints the header of the generated output.
198: *
199: * <p>Basically this prints the XML declaration, defines some
200: * entities and the project element.</p>
201: */
202: private void printHead(PrintWriter out, Enumeration tasks,
203: Enumeration types) {
204: out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
205: out
206: .println("<!ENTITY % boolean \"(true|false|on|off|yes|no)\">");
207: out.print("<!ENTITY % tasks \"");
208: boolean first = true;
209: while (tasks.hasMoreElements()) {
210: String tName = (String) tasks.nextElement();
211: if (!first) {
212: out.print(" | ");
213: } else {
214: first = false;
215: }
216: out.print(tName);
217: }
218: out.println("\">");
219: out.print("<!ENTITY % types \"");
220: first = true;
221: while (types.hasMoreElements()) {
222: String typeName = (String) types.nextElement();
223: if (!first) {
224: out.print(" | ");
225: } else {
226: first = false;
227: }
228: out.print(typeName);
229: }
230: out.println("\">");
231:
232: out.println("");
233:
234: out.print("<!ELEMENT project (target | ");
235: out.print(TASKS);
236: out.print(" | ");
237: out.print(TYPES);
238: out.println(")*>");
239: out.println("<!ATTLIST project");
240: out.println(" name CDATA #IMPLIED");
241: out.println(" default CDATA #IMPLIED");
242: out.println(" basedir CDATA #IMPLIED>");
243: out.println("");
244: }
245:
246: /**
247: * Prints the definition for the target element.
248: */
249: public void printTargetDecl(PrintWriter out) {
250: out.print("<!ELEMENT target (");
251: out.print(TASKS);
252: out.print(" | ");
253: out.print(TYPES);
254: out.println(")*>");
255: out.println("");
256:
257: out.println("<!ATTLIST target");
258: out.println(" id ID #IMPLIED");
259: out.println(" name CDATA #REQUIRED");
260: out.println(" if CDATA #IMPLIED");
261: out.println(" unless CDATA #IMPLIED");
262: out.println(" depends CDATA #IMPLIED");
263: out.println(" description CDATA #IMPLIED>");
264: out.println("");
265: }
266:
267: /**
268: * Print the definition for a given element.
269: */
270: public void printElementDecl(PrintWriter out, Project p,
271: String name, Class element) {
272:
273: if (visited.containsKey(name)) {
274: return;
275: }
276: visited.put(name, "");
277:
278: IntrospectionHelper ih = null;
279: try {
280: ih = IntrospectionHelper.getHelper(p, element);
281: } catch (Throwable t) {
282: /*
283: * XXX - failed to load the class properly.
284: *
285: * should we print a warning here?
286: */
287: return;
288: }
289:
290: StringBuffer sb = new StringBuffer("<!ELEMENT ");
291: sb.append(name).append(" ");
292:
293: if (org.apache.tools.ant.types.Reference.class
294: .equals(element)) {
295: sb.append("EMPTY>").append(LINE_SEP);
296: sb.append("<!ATTLIST ").append(name);
297: sb.append(LINE_SEP).append(" id ID #IMPLIED");
298: sb.append(LINE_SEP).append(
299: " refid IDREF #IMPLIED");
300: sb.append(">").append(LINE_SEP);
301: out.println(sb);
302: return;
303: }
304:
305: Vector v = new Vector();
306: if (ih.supportsCharacters()) {
307: v.addElement("#PCDATA");
308: }
309:
310: if (TaskContainer.class.isAssignableFrom(element)) {
311: v.addElement(TASKS);
312: }
313:
314: Enumeration e = ih.getNestedElements();
315: while (e.hasMoreElements()) {
316: v.addElement(e.nextElement());
317: }
318:
319: if (v.isEmpty()) {
320: sb.append("EMPTY");
321: } else {
322: sb.append("(");
323: final int count = v.size();
324: for (int i = 0; i < count; i++) {
325: if (i != 0) {
326: sb.append(" | ");
327: }
328: sb.append(v.elementAt(i));
329: }
330: sb.append(")");
331: if (count > 1 || !v.elementAt(0).equals("#PCDATA")) {
332: sb.append("*");
333: }
334: }
335: sb.append(">");
336: out.println(sb);
337:
338: sb = new StringBuffer("<!ATTLIST ");
339: sb.append(name);
340: sb.append(LINE_SEP).append(" id ID #IMPLIED");
341:
342: e = ih.getAttributes();
343: while (e.hasMoreElements()) {
344: String attrName = (String) e.nextElement();
345: if ("id".equals(attrName)) {
346: continue;
347: }
348:
349: sb.append(LINE_SEP).append(" ").append(
350: attrName).append(" ");
351: Class type = ih.getAttributeType(attrName);
352: if (type.equals(java.lang.Boolean.class)
353: || type.equals(java.lang.Boolean.TYPE)) {
354: sb.append(BOOLEAN).append(" ");
355: } else if (Reference.class.isAssignableFrom(type)) {
356: sb.append("IDREF ");
357: } else if (EnumeratedAttribute.class
358: .isAssignableFrom(type)) {
359: try {
360: EnumeratedAttribute ea = (EnumeratedAttribute) type
361: .newInstance();
362: String[] values = ea.getValues();
363: if (values == null || values.length == 0
364: || !areNmtokens(values)) {
365: sb.append("CDATA ");
366: } else {
367: sb.append("(");
368: for (int i = 0; i < values.length; i++) {
369: if (i != 0) {
370: sb.append(" | ");
371: }
372: sb.append(values[i]);
373: }
374: sb.append(") ");
375: }
376: } catch (InstantiationException ie) {
377: sb.append("CDATA ");
378: } catch (IllegalAccessException ie) {
379: sb.append("CDATA ");
380: }
381: } else if (type.getSuperclass() != null
382: && type.getSuperclass().getName().equals(
383: "java.lang.Enum")) {
384: try {
385: Object[] values = (Object[]) type.getMethod(
386: "values", (Class[]) null).invoke(null,
387: (Object[]) null);
388: if (values.length == 0) {
389: sb.append("CDATA ");
390: } else {
391: sb.append('(');
392: for (int i = 0; i < values.length; i++) {
393: if (i != 0) {
394: sb.append(" | ");
395: }
396: sb.append(type.getMethod("name",
397: (Class[]) null).invoke(
398: values[i], (Object[]) null));
399: }
400: sb.append(") ");
401: }
402: } catch (Exception x) {
403: sb.append("CDATA ");
404: }
405: } else {
406: sb.append("CDATA ");
407: }
408: sb.append("#IMPLIED");
409: }
410: sb.append(">").append(LINE_SEP);
411: out.println(sb);
412:
413: final int count = v.size();
414: for (int i = 0; i < count; i++) {
415: String nestedName = (String) v.elementAt(i);
416: if (!"#PCDATA".equals(nestedName)
417: && !TASKS.equals(nestedName)
418: && !TYPES.equals(nestedName)) {
419: printElementDecl(out, p, nestedName, ih
420: .getElementType(nestedName));
421: }
422: }
423: }
424:
425: /**
426: * Does this String match the XML-NMTOKEN production?
427: * @param s the string to test
428: * @return true if the string matches the XML-NMTOKEN
429: */
430: public static final boolean isNmtoken(String s) {
431: final int length = s.length();
432: for (int i = 0; i < length; i++) {
433: char c = s.charAt(i);
434: // XXX - we are committing CombiningChar and Extender here
435: if (!Character.isLetterOrDigit(c) && c != '.'
436: && c != '-' && c != '_' && c != ':') {
437: return false;
438: }
439: }
440: return true;
441: }
442:
443: /**
444: * Do the Strings all match the XML-NMTOKEN production?
445: *
446: * <p>Otherwise they are not suitable as an enumerated attribute,
447: * for example.</p>
448: * @param s the array of string to test
449: * @return true if all the strings in the array math XML-NMTOKEN
450: */
451: public static final boolean areNmtokens(String[] s) {
452: for (int i = 0; i < s.length; i++) {
453: if (!isNmtoken(s[i])) {
454: return false;
455: }
456: }
457: return true;
458: }
459: }
460:
461: /**
462: * Does this String match the XML-NMTOKEN production?
463: * @param s the string to test
464: * @return true if the string matches the XML-NMTOKEN
465: */
466: protected boolean isNmtoken(String s) {
467: return DTDPrinter.isNmtoken(s);
468: }
469:
470: /**
471: * Do the Strings all match the XML-NMTOKEN production?
472: *
473: * <p>Otherwise they are not suitable as an enumerated attribute,
474: * for example.</p>
475: * @param s the array of string to test
476: * @return true if all the strings in the array math XML-NMTOKEN
477: */
478: protected boolean areNmtokens(String[] s) {
479: return DTDPrinter.areNmtokens(s);
480: }
481: }
|