001: package org.apache.dvsl;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.Map;
023: import java.util.Properties;
024: import java.util.Vector;
025: import java.util.HashMap;
026: import java.util.Enumeration;
027:
028: import java.io.File;
029: import java.io.FileInputStream;
030: import java.io.BufferedWriter;
031: import java.io.OutputStreamWriter;
032: import java.io.FileOutputStream;
033:
034: import org.apache.tools.ant.AntClassLoader;
035: import org.apache.tools.ant.BuildException;
036: import org.apache.tools.ant.DirectoryScanner;
037: import org.apache.tools.ant.Project;
038: import org.apache.tools.ant.taskdefs.MatchingTask;
039: import org.apache.tools.ant.types.Path;
040: import org.apache.tools.ant.types.Reference;
041:
042: /**
043: * A Task to process via DVSL a set of XML documents. This is
044: * useful for building views of XML based documentation.
045: * arguments:
046: * <ul>
047: * <li>basedir
048: * <li>destdir
049: * <li>style
050: * <li>in
051: * <li>out
052: * <li>logfile
053: * <li>includes
054: * <li>excludes
055: * <li>force
056: * <li>extension
057: * <li>outputencoding
058: * <li>classpath
059: * <li>classpathref
060: * <li>toolboxfile
061: * <li>velocityconfigclass
062: * <li>
063: * </ul>
064: * <p>Of these arguments, the <b>sourcedir</b> and <b>destdir</b> are required, or,
065: * <b>in</b> and <b>out</b> are required.</p>
066: * <p>Following are the supported nested elements:</p>
067: * <ul>
068: * <li><include>
069: * <li><exclude>
070: * <li><classpath>
071: * <li><tool name="toolbox-property" value="value-or-object" />
072: * <li><velconfig name="velocity-config-name" value="config-value" />
073: * </ul>
074: * <p>This task will recursively scan the sourcedir and destdir
075: * looking for XML documents to process via DVSL. </p>
076: *
077: * <p>This task was adapted from Ant's <style> task (XSLTProcess class) from the
078: * 1.4.1 release.</p>
079: *
080: * @author <a href="mailto:kvisco@exoffice.com">Keith Visco</a>
081: * @author <a href="mailto:rubys@us.ibm.com">Sam Ruby</a>
082: * @author <a href="mailto:russgold@acm.org">Russell Gold</a>
083: * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
084: * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
085: * @author <a href="mailto:billb@progress.com">Bill Burton.</a>
086: */
087:
088: public class DVSLTask extends MatchingTask {
089: /**
090: * Supported app values
091: */
092: public static final String INFILENAME = "infilename";
093: public static final String OUTFILENAME = "outfilename";
094:
095: private DVSL dvsl;
096:
097: private File destDir = null;
098: private File baseDir = null;
099: private File stylesheet = null;
100: private String stylesheetEncoding = null;
101: private File inFile = null;
102: private File outFile = null;
103: private File logFile = null;
104:
105: private String targetExtension = ".html";
106: private String outputEncoding = "UTF-8";
107:
108: private Path classpath = null;
109: private ClassLoader classLoader = null;
110:
111: private boolean force = false;
112:
113: private Vector toolAttr = new Vector();
114: private File toolboxFile = null;
115: private Properties toolboxProps = null;
116:
117: private String velConfigClass = null;
118: private Map velConfigMap = null;
119: private Vector velConfigAttr = new Vector();
120:
121: private boolean validatingParser = false;
122:
123: //private String outputtype = null; // later when output type is supported
124:
125: /**
126: * Creates a new DVSLTask Task.
127: **/
128: public DVSLTask() {
129: classLoader = DVSLTask.class.getClassLoader();
130: }
131:
132: /**
133: * Executes the task.
134: */
135: public void execute() throws BuildException {
136: DirectoryScanner scanner;
137: String[] list;
138: String[] dirs;
139:
140: if (stylesheet == null) {
141: throw new BuildException("no stylesheet specified",
142: getLocation());
143: }
144:
145: if (baseDir == null) {
146: baseDir = getProject().resolveFile(".");
147: }
148:
149: /*
150: * make a DVSL and set validation
151: */
152: dvsl = new DVSL();
153:
154: dvsl.setValidatingParser(validatingParser);
155:
156: /*
157: * Create a new Classloader for loading the Toolbox and the Velocity
158: * properties class.
159: */
160: if (classpath != null) {
161: classLoader = new AntClassLoader(getProject(), classpath);
162: dvsl.setClassLoader(classLoader);
163: }
164:
165: /*
166: * If the user gave us a velPropClass, we create an instance
167: * of that to use to config the VelocityEngine inside te DVSL
168: */
169: Object velConfigObj = null;
170:
171: if (velConfigClass != null) {
172: try {
173: velConfigObj = Class.forName(velConfigClass, true,
174: classLoader).newInstance();
175:
176: if (velConfigObj instanceof Map) {
177: velConfigMap = (Map) velConfigObj;
178: } else {
179: throw new BuildException(
180: "VelocityPropClass is not instanceof java.util.Map");
181: }
182: } catch (Exception ex) {
183: throw new BuildException(
184: "Error instantiating VelocityPropClass : " + ex);
185: }
186: }
187:
188: /*
189: * If any nested Velocity Config elements have been specified, overlay any settings
190: * in the Velocity Config object.
191: */
192: if (!velConfigAttr.isEmpty()) {
193: if (velConfigMap == null) {
194: velConfigMap = new HashMap();
195: }
196:
197: /*
198: * Now copy velocity config attributes into the Map
199: */
200: for (Enumeration e = velConfigAttr.elements(); e
201: .hasMoreElements();) {
202: VelocityConfig p = (VelocityConfig) e.nextElement();
203: velConfigMap.put(p.getName(), p.getValue());
204: }
205: }
206:
207: /*
208: * Finally, set the Velocity Config object in DVSL if it's valid.
209: */
210: if (velConfigMap != null) {
211: dvsl.setVelocityConfig(velConfigMap);
212: }
213:
214: /*
215: * If a logfile attribute was specified, use that for the log file name,
216: * otherwise use a Velocity to Ant logging adapter.
217: */
218: if (logFile != null) {
219: dvsl.setLogFile(logFile);
220: } else {
221: dvsl.setLogChute(new AntLogChute(this ));
222: }
223:
224: /*
225: * now the stylesheet
226: */
227: try {
228: log("Loading stylesheet " + stylesheet, Project.MSG_INFO);
229: dvsl.setStylesheet(stylesheet, stylesheetEncoding);
230: } catch (Exception ex) {
231: log("Failed to read stylesheet " + stylesheet,
232: Project.MSG_INFO);
233: throw new BuildException(ex);
234: }
235:
236: /*
237: * now, if we were given a toolbox, set that up too
238: */
239: toolboxProps = new Properties();
240:
241: try {
242: if (toolboxFile != null) {
243: toolboxProps.load(new FileInputStream(toolboxFile));
244: }
245:
246: /*
247: * Overlay any parameters
248: */
249: for (Enumeration e = toolAttr.elements(); e
250: .hasMoreElements();) {
251: Tool p = (Tool) e.nextElement();
252: toolboxProps.setProperty(p.getName(), p.getValue());
253: }
254:
255: dvsl.setToolbox(toolboxProps);
256: } catch (Exception ee) {
257: throw new BuildException("Error loading the toolbox : "
258: + ee);
259: }
260:
261: /*
262: * if we have an in file and out then process them
263: */
264:
265: if (inFile != null && outFile != null) {
266: process(inFile, outFile, stylesheet);
267: return;
268: }
269:
270: /*
271: * if we get here, in and out have not been specified, we are
272: * in batch processing mode.
273: */
274:
275: /*
276: * make sure Source directory exists...
277: */
278: if (destDir == null) {
279: String msg = "destdir attributes must be set!";
280: throw new BuildException(msg);
281: }
282:
283: scanner = getDirectoryScanner(baseDir);
284: log("Transforming into " + destDir, Project.MSG_INFO);
285:
286: /*
287: * Process all the files marked for styling
288: */
289: list = scanner.getIncludedFiles();
290:
291: for (int i = 0; i < list.length; ++i) {
292: process(baseDir, list[i], destDir, stylesheet);
293: }
294:
295: /*
296: * Process all the directoried marked for styling
297: */
298: dirs = scanner.getIncludedDirectories();
299:
300: for (int j = 0; j < dirs.length; ++j) {
301: list = new File(baseDir, dirs[j]).list();
302:
303: for (int i = 0; i < list.length; ++i) {
304: process(baseDir, list[i], destDir, stylesheet);
305: }
306: }
307: } //-- execute
308:
309: /**
310: * Set whether to check dependencies, or always generate.
311: * @param force false to check dependencies, true to always generate
312: */
313: public void setForce(boolean force) {
314: this .force = force;
315: }
316:
317: /**
318: * Set the base directory.
319: * @param dir name of the base directory
320: */
321: public void setBasedir(File dir) {
322: baseDir = dir;
323: }
324:
325: /**
326: * Set the destination directory where the generated
327: * files should be directed.
328: * @param dir name of the destination directory
329: */
330: public void setDestdir(File dir) {
331: destDir = dir;
332: }
333:
334: /**
335: * Set the desired file extension to be used for the target files.
336: * If not specified, "<code>.html</code>" is used.
337: * @param name the extension to use
338: */
339: public void setExtension(String name) {
340: targetExtension = name;
341: }
342:
343: /**
344: * Sets the file to use for stylesheet.
345: * @param dvslFile stylesheet filename
346: */
347: public void setStyle(File dvslFile) {
348: this .stylesheet = dvslFile;
349: }
350:
351: /**
352: * Sets the encoding of stylesheet file.
353: * @param dvslFileEncoding encoding of stylesheet file
354: */
355: public void setStyleEncoding(String dvslFileEncoding) {
356: this .stylesheetEncoding = dvslFileEncoding;
357: }
358:
359: /**
360: * Sets the file to use for logging. If not specified, all logging
361: * is directed through Ant's logging system.
362: * @param logFile logging filename
363: */
364: public void setLogFile(File logFile) {
365: this .logFile = logFile;
366: }
367:
368: /**
369: * Sets the Toolbox properties file to use.
370: * @param toolboxFile properties file of tools
371: * @deprecated use setToolboxFile instead
372: */
373: public void setToolbox(String toolboxFile) {
374: log("DEPRECATED - use the toolboxfile attribute instead");
375: this .toolboxFile = new File(toolboxFile);
376: }
377:
378: /**
379: * Sets the Toolbox properties file to use.
380: * @param toolboxFile properties file of tools
381: */
382: public void setToolboxFile(File toolboxFile) {
383: this .toolboxFile = toolboxFile;
384: }
385:
386: /**
387: * Allows the user to specify a class that implements
388: * {@link java.util.Properties} that will have user properties
389: * to be used when setting up DVSL.
390: * @param classname Velocity configuration class to load
391: */
392: public void setVelocityConfigClass(String classname) {
393: velConfigClass = classname;
394: }
395:
396: /**
397: * Sets an output file
398: * @param outFile output file
399: */
400: public void setOut(File outFile) {
401: this .outFile = outFile;
402: }
403:
404: /**
405: * Sets an input xml file to be styled
406: * @param inFile input file
407: */
408: public void setIn(File inFile) {
409: this .inFile = inFile;
410: }
411:
412: /**
413: * Sets the character encoding for output files. If not specified,
414: * output is written with UTF-8 encodin6g.
415: * @param encoding Output encoding
416: */
417: public void setOutputEncoding(String encoding) {
418: if (encoding != null)
419: this .outputEncoding = encoding;
420: }
421:
422: /**
423: * Set the classpath to load the Processor through (attribute).
424: * @param classpath classpath to set
425: */
426: public void setClasspath(Path classpath) {
427: createClasspath().append(classpath);
428: }
429:
430: /**
431: * Set the classpath to load the Processor through (nested element).
432: */
433: public Path createClasspath() {
434: if (classpath == null) {
435: classpath = new Path(getProject());
436: }
437: return classpath.createPath();
438: }
439:
440: /**
441: * Set the classpath to load the Processor through via reference
442: * (attribute).
443: * @param r reference to classpath
444: */
445: public void setClasspathRef(Reference r) {
446: createClasspath().setRefid(r);
447: }
448:
449: /**
450: * Sets the flag to have DVSL use a validating parser for the
451: * input documents
452: */
453: public void setValidatingParser(boolean validating) {
454: if (validating == true) {
455: log("Parser is validating.");
456: }
457:
458: validatingParser = validating;
459: }
460:
461: /**
462: * Sets an application value from outside of the DVSL task
463: */
464: public void putAppValue(String name, Object o) {
465: dvsl.putAppValue(name, o);
466: }
467:
468: /**
469: * Processes the given input XML file and stores the result
470: * in the given resultFile.
471: */
472: private void process(File baseDir, String xmlFile, File destDir,
473: File stylesheet) throws BuildException {
474:
475: String fileExt = targetExtension;
476: File outFile = null;
477: File inFile = null;
478:
479: try {
480: long styleSheetLastModified = stylesheet.lastModified();
481: inFile = new File(baseDir, xmlFile);
482: int dotPos = xmlFile.lastIndexOf('.');
483:
484: dvsl.putAppValue(INFILENAME, xmlFile);
485:
486: String outfilename;
487:
488: if (dotPos > 0) {
489: outfilename = xmlFile.substring(0, xmlFile
490: .lastIndexOf('.'))
491: + fileExt;
492: outFile = new File(destDir, outfilename);
493: } else {
494: outfilename = xmlFile + fileExt;
495: outFile = new File(destDir, outfilename);
496: }
497:
498: dvsl.putAppValue(OUTFILENAME, outfilename);
499:
500: if (force || inFile.lastModified() > outFile.lastModified()
501: || styleSheetLastModified > outFile.lastModified()) {
502: ensureDirectoryFor(outFile);
503: log("Processing " + inFile + " to " + outFile,
504: Project.MSG_INFO);
505: long time = transform(inFile, outFile);
506: log("Processed " + inFile + " in " + time + " ms.",
507: Project.MSG_VERBOSE);
508: }
509: } catch (Exception ex) {
510: /*
511: * If failed to process document, must delete target document,
512: * or it will not attempt to process it the second time
513: */
514:
515: log("Failed to process " + inFile, Project.MSG_INFO);
516:
517: if (outFile != null) {
518: outFile.delete();
519: }
520:
521: throw new BuildException(ex);
522: }
523: }
524:
525: private void process(File inFile, File outFile, File stylesheet)
526: throws BuildException {
527: try {
528: long styleSheetLastModified = stylesheet.lastModified();
529:
530: log(
531: "In file " + inFile + " time: "
532: + inFile.lastModified(), Project.MSG_DEBUG);
533: log("Out file " + outFile + " time: "
534: + outFile.lastModified(), Project.MSG_DEBUG);
535: log("Style file " + stylesheet + " time: "
536: + styleSheetLastModified, Project.MSG_DEBUG);
537:
538: if (force || inFile.lastModified() > outFile.lastModified()
539: || styleSheetLastModified > outFile.lastModified()) {
540: ensureDirectoryFor(outFile);
541: log("Processing " + inFile + " to " + outFile,
542: Project.MSG_INFO);
543: long time = transform(inFile, outFile);
544: log("Processed " + inFile + " in " + time + " ms.",
545: Project.MSG_VERBOSE);
546: }
547: } catch (Exception ex) {
548: log("Failed to process " + inFile, Project.MSG_INFO);
549:
550: if (outFile != null) {
551: outFile.delete();
552: }
553: throw new BuildException(ex);
554: }
555: }
556:
557: /**
558: * <p>
559: * Does the actual transform
560: * </p>
561: *
562: * @param inFile XML document source
563: * @param outFile File for transformed input
564: * @return elapsed time in ms. for transformation
565: */
566: private long transform(File inFile, File outFile) throws Exception {
567: BufferedWriter writer = new BufferedWriter(
568: new OutputStreamWriter(new FileOutputStream(outFile),
569: outputEncoding));
570:
571: long time = dvsl.transform(inFile, writer);
572: writer.close();
573: return time;
574: }
575:
576: private void ensureDirectoryFor(File targetFile)
577: throws BuildException {
578: File directory = new File(targetFile.getParent());
579:
580: if (!directory.exists()) {
581: if (!directory.mkdirs()) {
582: throw new BuildException("Unable to create directory: "
583: + directory.getAbsolutePath());
584: }
585: }
586: }
587:
588: /**
589: * support for <tool> nested element
590: */
591: public Tool createTool() {
592: Tool p = new Tool();
593: toolAttr.addElement(p);
594: return p;
595: }
596:
597: public class Tool {
598: private String name = null;
599: private String value = null;
600:
601: public void setName(String name) {
602: this .name = name;
603: }
604:
605: public String getName() throws BuildException {
606: if (name == null) {
607: throw new BuildException("Name attribute is missing.");
608: }
609: return name;
610: }
611:
612: public void setValue(String value) {
613: this .value = value;
614: }
615:
616: public String getValue() throws BuildException {
617: if (value == null) {
618: throw new BuildException("value for attribute for "
619: + getName() + " is missing.");
620: }
621: return value;
622: }
623: }
624:
625: /**
626: * support for <velconfig> nested element
627: */
628: public VelocityConfig createVelConfig() {
629: VelocityConfig p = new VelocityConfig();
630: velConfigAttr.addElement(p);
631: return p;
632: }
633:
634: public class VelocityConfig {
635: private String name = null;
636: private String value = null;
637:
638: public void setName(String name) {
639: this .name = name;
640: }
641:
642: public String getName() throws BuildException {
643: if (name == null) {
644: throw new BuildException("Name attribute is missing.");
645: }
646: return name;
647: }
648:
649: public void setValue(String value) {
650: this .value = value;
651: }
652:
653: public String getValue() throws BuildException {
654: if (value == null) {
655: throw new BuildException("Value attribute is missing.");
656: }
657: return value;
658: }
659: }
660:
661: /**
662: * Set the output type to use for the transformation. Only "xml" (the
663: * default) is guaranteed to work for all parsers. Xalan2 also
664: * supports "html" and "text".<br>
665: * <i>Not currently implemented.</i>
666: * @param type the output method to use
667: */
668: /*
669: public void setOutputtype(String type) {
670: this.outputtype = type;
671: }
672: */
673:
674: }
|