001: /*
002: * This file is part of PFIXCORE.
003: *
004: * PFIXCORE is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU Lesser General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * PFIXCORE is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public License
015: * along with PFIXCORE; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: */
019: package de.schlund.pfixcore.util;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.net.MalformedURLException;
024: import java.util.Date;
025:
026: import org.apache.tools.ant.BuildException;
027: import org.apache.tools.ant.DirectoryScanner;
028: import org.apache.tools.ant.Project;
029: import org.apache.tools.ant.taskdefs.MatchingTask;
030:
031: /**
032: * @author adam
033: *
034: * To change the template for this generated type comment go to
035: * Window - Preferences - Java - Code Generation - Code and Comments
036: */
037: public class XsltGenericTask extends MatchingTask {
038:
039: // --- Attribute names ---
040: public static String ATTR_SRCDIR = "srcdir";
041: public static String ATTR_DESTDIR = "destdir";
042: public static String ATTR_INFILE = "infile";
043: public static String ATTR_OUTFILE = "outfile";
044: public static String ATTR_CATALOGFILE = "catalogfile";
045: public static String ATTR_VALIDATE = "validate";
046:
047: // --- Attribute values ---
048: public static String VAL_VALIDATE_TRUE = "true";
049: public static String VAL_VALIDATE_DYNAMIC = "dynamic";
050:
051: // --- Attributes ---
052:
053: /** destination directory */
054: protected File destdir = null;
055:
056: /** where to find the source XML file, default is the project's basedir */
057: protected File srcdir = null;
058:
059: /** single inputfile */
060: protected File infile = null;
061:
062: /** single outputfile */
063: protected File outfile = null;
064:
065: /** XSL stylesheet relative to project's basedir */
066: protected String style = null;
067:
068: /** extension of the files produced by XSL processing */
069: protected String targetExtension = null;
070:
071: ///** Classpath to use when trying to load the XSL processor */
072: //protected Path classpath = null;
073:
074: /** XML Schema Validation. Default: false. */
075: protected String validate = "false";
076:
077: /** Catalog file compliant to http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd - optional */
078: protected String catalogfile = null;
079:
080: // --- other members ---
081:
082: /**
083: *
084: */
085: protected XsltTransformer transformer;
086:
087: /**
088: *
089: */
090: protected PfixXmlCatalogEntityResolver pfixResolver;
091:
092: /**
093: * existent stylesheet (handled by {@link #executeSetup()})
094: */
095: protected File stylefile;
096:
097: /**
098: * resolved from project's base directory (handled by {@link #executeSetup()})
099: */
100: protected File srcdirResolved;
101:
102: /**
103: * resolved from project's base directory (handled by {@link #executeSetup()})
104: */
105: protected File destdirResolved;
106:
107: protected void executeSetup() {
108:
109: if (infile != null) {
110: if (getSrcdir() != null) {
111: log(ATTR_SRCDIR + "/" + ATTR_DESTDIR + " ignored as "
112: + ATTR_INFILE + " specified as " + infile,
113: Project.MSG_WARN);
114: }
115: if (outfile == null) {
116: throw new BuildException("no correspondig "
117: + ATTR_OUTFILE + " for " + ATTR_INFILE + "="
118: + infile);
119: }
120: } else {
121: // srcdir
122: srcdirResolved = getSrcdir();
123: if (srcdirResolved == null) {
124: throw new BuildException("neither " + ATTR_INFILE
125: + " nor " + ATTR_SRCDIR + " set");
126: }
127: // destdir
128: destdirResolved = getDestdir();
129: if (destdirResolved == null) {
130: destdirResolved = srcdirResolved;
131: log(
132: "destdir attribute not specified, using value of srcdir attribute",
133: Project.MSG_VERBOSE);
134: }
135: }
136: // style
137: String styleTmp = getStyle();
138: if (styleTmp == null) {
139: throw new BuildException("no stylesheet specified");
140: } else {
141: stylefile = getProject().resolveFile(styleTmp);
142: if (stylefile.exists() == false) {
143: throw new BuildException("stylesheet " + stylefile
144: + " does not exist.");
145: }
146: }
147: getTransformer().setStylesheet(stylefile);
148: }
149:
150: protected void executeCleanup() {
151: srcdirResolved = null;
152: destdirResolved = null;
153: stylefile = null;
154: if (isTransformerInstantiated()) {
155: getTransformer().clearParameters();
156: }
157: }
158:
159: /* (non-Javadoc)
160: * @see org.apache.tools.ant.Task#execute()
161: */
162: public void execute() throws BuildException {
163:
164: try {
165: executeSetup();
166: StringBuffer tmp = new StringBuffer(300);
167: tmp.append("style=\"");
168: tmp.append(stylefile);
169: tmp.append("\" srcdir=\"");
170: tmp.append(srcdirResolved);
171: tmp.append("\" destdir=");
172: tmp.append(destdirResolved);
173: tmp.append("\" using " + getTransformer());
174: log(tmp.toString(), Project.MSG_DEBUG);
175: // delme log("Transforming from directory \""+srcdirResolved+"\" to \""+destdirResolved+"\" with style \""+stylefile+"\" using "+transformer, Project.MSG_VERBOSE);
176: doTransformations();
177: } finally {
178: executeCleanup();
179: }
180:
181: }
182:
183: // TEMP ssed -e 's/^.* \(.*\)\( =\|;\)/\1 = null;/'
184: // Variables used in transformXXX()
185: protected DirectoryScanner scanner;
186: protected String[] infilenames; /* input filenames relative to srcdirResolved */
187: protected String inname; /* filename relative to srcdirResolved */
188: protected String infilenameNoExt; /* filename relative to srcdirResolved without extension */
189: protected String outname; /* filename relative to srcdirResolved */
190: protected File in;
191: protected File out;
192: protected long inLastModified;
193: protected long outLastModified;
194: protected long styleLastModified;
195: protected StringBuffer sb = new StringBuffer(300);
196:
197: /**
198: * checks attributes, sets {@link #in}, {@link #out} and calls {@link #doTransformationMaybe()}
199: * for each input file.
200: */
201: protected void doTransformations() {
202:
203: int transformed = 0;
204: int uptodate = 0;
205: int candidates = 0;
206: int count; // number of files transformed per doTransformationMaybe() call, 0 or 1
207: int dotPos;
208:
209: try {
210: if (infile != null) {
211: candidates = 1;
212: in = infile;
213: out = outfile;
214: transformed = doTransformationMaybe();
215: uptodate = candidates - transformed;
216: } else {
217: scanner = getDirectoryScanner(srcdirResolved);
218: infilenames = scanner.getIncludedFiles();
219: candidates = infilenames.length;
220: uptodate = 0;
221: for (int i = 0; i < infilenames.length; i++) {
222:
223: inname = infilenames[i];
224: in = new File(srcdirResolved, inname);
225:
226: dotPos = inname.lastIndexOf('.');
227: if (dotPos > 0) {
228: infilenameNoExt = inname.substring(0, inname
229: .lastIndexOf('.'));
230: } else {
231: infilenameNoExt = inname;
232: }
233: outname = infilenameNoExt + getExtension();
234: out = new File(destdirResolved, outname);
235: count = doTransformationMaybe(); // count is 0 or 1
236: transformed = transformed + count;
237: if (count <= 0) {
238: uptodate++;
239: }
240: }
241: }
242: } finally {
243:
244: // log only if not everything was uptodate
245: if (candidates != uptodate) {
246: log("Transformed " + transformed + " of " + candidates
247: + " file" + (candidates == 1 ? "" : "s") + ", "
248: + uptodate + " "
249: + (uptodate == 1 ? "has been" : "have been")
250: + " up to date", Project.MSG_INFO);
251: }
252: scanner = null;
253: infilenames = null; /* input filenames relative to srcdirResolved */
254: inname = null; /* filename relative to srcdirResolved */
255: infilenameNoExt = null; /* filename relative to srcdirResolved without extension */
256: outname = null; /* filename relative to srcdirResolved */
257: in = null;
258: out = null;
259: inLastModified = 0;
260: outLastModified = 0;
261: styleLastModified = 0;
262: }
263: }
264:
265: /**
266: * must be initialized: {@link #in}, {@link #out},
267: *
268: * @return number of files transformed,
269: * either 1 if file has been transformed or 0 if it was up to date
270: */
271: protected int doTransformationMaybe() {
272: inLastModified = in.lastModified();
273: outLastModified = out.lastModified();
274: styleLastModified = getStyleLastModified();
275:
276: // TODO_AH throw out? transformer is expensive
277: // transformer is not yet initialized, as it only initializes itself
278: // on transformation, therefore the firsttime transformer.toString()
279: // is called, the internal transformer variable of XsltTransformer
280: // is still null
281: sb.setLength(0);
282: sb.append("styleLastModified=");
283: sb.append(new Date(styleLastModified));
284: sb.append(" inLastModified=");
285: sb.append(new Date(inLastModified));
286: sb.append(" outLastModified=");
287: sb.append(new Date(outLastModified));
288: sb.append(" in=");
289: sb.append(in);
290: sb.append(" out=");
291: sb.append(out);
292: sb.append(" transformer=");
293: sb.append(getTransformer());
294: log(sb.toString(), Project.MSG_DEBUG);
295: if ((outLastModified < styleLastModified)
296: || (outLastModified < inLastModified)) {
297: sb.setLength(0);
298: sb.append("transform ");
299: sb.append(in);
300: sb.append(" ===> ");
301: sb.append(out);
302: log(sb.toString(), Project.MSG_VERBOSE);
303: doTransformation();
304: return 1;
305: } else {
306: sb.setLength(0);
307: sb.append("uptodate ");
308: sb.append(in);
309: sb.append(" <==> ");
310: sb.append(out);
311: log(sb.toString(), Project.MSG_VERBOSE);
312: return 0;
313: }
314: }
315:
316: protected void doTransformation() throws BuildException {
317: getTransformer().transform(in, out);
318: }
319:
320: /**
321: * @return lastModified time of {@link #stylefile}, 0 if {@link #stylefile} not set
322: * @see File#lastModified()
323: */
324: protected long getStyleLastModified() {
325: long lm = 0;
326: if (stylefile != null) {
327: lm = stylefile.lastModified();
328: }
329: return lm;
330: }
331:
332: /**
333: * Used to check if cleanup is necessary.
334: */
335: protected boolean isTransformerInstantiated() {
336: return transformer != null;
337: }
338:
339: protected XsltTransformer getTransformer() {
340: // TODO set dynamic val
341: if (transformer == null) {
342: transformer = new XsltTransformer(getProject());
343: transformer.setValidate(isValidate());
344: transformer.setValidateDynamic(isValidateDynamic()); // Xerces-Default: false
345: //transformer.setNamespaceAware(true); // Xerces-Default: true
346: }
347: if (pfixResolver == null) {
348: if (getCatalogfile() != null) {
349: try {
350: pfixResolver = new PfixXmlCatalogEntityResolver(
351: getCatalogfile());
352: } catch (MalformedURLException e) {
353: throw new BuildException(
354: "Unable to initialize PfixXmlCatalogEntityResolver with catalogfile \""
355: + getCatalogfile() + "\"", e);
356: } catch (IOException e) {
357: throw new BuildException(
358: "Unable to initialize PfixXmlCatalogEntityResolver with catalogfile \""
359: + getCatalogfile() + "\"", e);
360: }
361: transformer.setEntitiyResolver(pfixResolver);
362: }
363: }
364: return transformer;
365: }
366:
367: protected void invalidateTransformer() {
368: transformer = null;
369: }
370:
371: protected void invalidateXmlCatalog() {
372: pfixResolver = null;
373: }
374:
375: public File getInfile() {
376: return infile;
377: }
378:
379: public void setInfile(File infile) {
380: this .infile = infile;
381: }
382:
383: public File getOutfile() {
384: return outfile;
385: }
386:
387: public void setOutfile(File outfile) {
388: this .outfile = outfile;
389: }
390:
391: /**
392: * Set the Source directory;
393: * required.
394: *
395: * @param srcdir the base directory
396: **/
397: public void setSrcdir(File srcdir) {
398: this .srcdir = srcdir;
399: }
400:
401: public File getSrcdir() {
402: return this .srcdir;
403: }
404:
405: /**
406: * Set the destination directory into which the XSL result
407: * files should be copied to;
408: * optional, defaults to srcdir if not specified
409: *
410: * @param destdir the name of the destination directory
411: **/
412: public void setDestdir(File destdir) {
413: this .destdir = destdir;
414: }
415:
416: public File getDestdir() {
417: return destdir;
418: }
419:
420: /**
421: * Set the desired file extension to be used for the target;
422: * optional, default is "".
423: *
424: * @param name the extension to use
425: **/
426: public void setExtension(String name) {
427: targetExtension = name;
428: }
429:
430: /**
431: * @return the target extension or "" if none set
432: */
433:
434: public String getExtension() {
435: return (targetExtension == null) ? "" : targetExtension;
436: }
437:
438: /**
439: * Name of the stylesheet to use - given either relative
440: * to the project's basedir or as an absolute path; required.
441: *
442: * @param style the stylesheet to use
443: */
444: public void setStyle(String style) {
445: this .style = style;
446: }
447:
448: public String getStyle() {
449: return style;
450: }
451:
452: /**
453: * adds a configured instance of an XSLT parameter
454: *
455: * @param param a configured instance of the {@link XsltParam}
456: */
457: public void addConfiguredParam(XsltParam param) {
458: getTransformer().setParameter(param);
459: }
460:
461: public boolean isValidate() {
462: return getValidate().equals(VAL_VALIDATE_TRUE)
463: || getValidate().equals(VAL_VALIDATE_DYNAMIC);
464: }
465:
466: public boolean isValidateDynamic() {
467: return getValidate().equals(VAL_VALIDATE_DYNAMIC);
468: }
469:
470: public String getValidate() {
471: return validate;
472: }
473:
474: public void setValidate(String validate) {
475: if (validate == null) {
476: throw new IllegalArgumentException("attribute "
477: + ATTR_VALIDATE
478: + " is null; has to be specified as "
479: + VAL_VALIDATE_TRUE + "|false|"
480: + VAL_VALIDATE_DYNAMIC);
481: }
482: if (this .validate.equals(validate) == false) {
483: invalidateTransformer();
484: }
485: this .validate = validate;
486: }
487:
488: public String getCatalogfile() {
489: return catalogfile;
490: }
491:
492: public void setCatalogfile(String catalogfile) {
493: if (XsltGenericTask.equals(this .catalogfile, catalogfile) == false) {
494: invalidateXmlCatalog();
495: }
496: this .catalogfile = catalogfile;
497: }
498:
499: // --- Helper methods ---
500: protected static boolean isNothing(String s) {
501: return (s == null) || (s.trim().length() == 0);
502: }
503:
504: /**
505: * Test for equality of {@param object1} and {@param object2}.
506: * @param object1, may be null
507: * @param object2. may be null
508: * @return true if {@param object1} and {@param object2} are equal
509: */
510: protected static boolean equals(Object object1, Object object2) {
511: if (object1 != null) {
512: return object1.equals(object2);
513: } else {
514: // o1 == null
515: if (object2 != null) {
516: return object2.equals(object1);
517: } else {
518: // o1 == null && o2 == null
519: return true;
520: }
521: }
522: }
523:
524: }
|