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: package org.apache.tools.ant.taskdefs;
019:
020: import java.io.File;
021: import java.util.List;
022: import java.util.Vector;
023: import java.util.ArrayList;
024: import java.util.StringTokenizer;
025: import org.apache.tools.ant.Task;
026: import org.apache.tools.ant.Project;
027: import org.apache.tools.ant.BuildException;
028: import org.apache.tools.ant.taskdefs.condition.Os;
029: import org.apache.tools.ant.types.Path;
030: import org.apache.tools.ant.types.Mapper;
031: import org.apache.tools.ant.types.Reference;
032: import org.apache.tools.ant.types.ResourceCollection;
033: import org.apache.tools.ant.types.EnumeratedAttribute;
034: import org.apache.tools.ant.types.resources.Union;
035: import org.apache.tools.ant.util.FileNameMapper;
036:
037: /**
038: * Converts path and classpath information to a specific target OS
039: * format. The resulting formatted path is placed into the specified property.
040: *
041: * @since Ant 1.4
042: * @ant.task category="utility"
043: */
044: public class PathConvert extends Task {
045:
046: /**
047: * Set if we're running on windows
048: */
049: private static boolean onWindows = Os.isFamily("dos");
050:
051: // Members
052: /**
053: * Path to be converted
054: */
055: private Union path = null;
056: /**
057: * Reference to path/fileset to convert
058: */
059: private Reference refid = null;
060: /**
061: * The target OS type
062: */
063: private String targetOS = null;
064: /**
065: * Set when targetOS is set to windows
066: */
067: private boolean targetWindows = false;
068: /**
069: * Set if we should create a new property even if the result is empty
070: */
071: private boolean setonempty = true;
072: /**
073: * The property to receive the conversion
074: */
075: private String property = null;
076: /**
077: * Path prefix map
078: */
079: private Vector prefixMap = new Vector();
080: /**
081: * User override on path sep char
082: */
083: private String pathSep = null;
084: /**
085: * User override on directory sep char
086: */
087: private String dirSep = null;
088:
089: /** Filename mapper */
090: private Mapper mapper = null;
091:
092: /**
093: * Construct a new instance of the PathConvert task.
094: */
095: public PathConvert() {
096: }
097:
098: /**
099: * Helper class, holds the nested <map> values. Elements will look like
100: * this: <map from="d:" to="/foo"/>
101: *
102: * When running on windows, the prefix comparison will be case
103: * insensitive.
104: */
105: public class MapEntry {
106:
107: // Members
108: private String from = null;
109: private String to = null;
110:
111: /**
112: * Set the "from" attribute of the map entry.
113: * @param from the prefix string to search for; required.
114: * Note that this value is case-insensitive when the build is
115: * running on a Windows platform and case-sensitive when running on
116: * a Unix platform.
117: */
118: public void setFrom(String from) {
119: this .from = from;
120: }
121:
122: /**
123: * Set the replacement text to use when from is matched; required.
124: * @param to new prefix.
125: */
126: public void setTo(String to) {
127: this .to = to;
128: }
129:
130: /**
131: * Apply this map entry to a given path element.
132: *
133: * @param elem Path element to process.
134: * @return String Updated path element after mapping.
135: */
136: public String apply(String elem) {
137: if (from == null || to == null) {
138: throw new BuildException(
139: "Both 'from' and 'to' must be set "
140: + "in a map entry");
141: }
142: // If we're on windows, then do the comparison ignoring case
143: // and treat the two directory characters the same
144: String cmpElem = onWindows ? elem.toLowerCase().replace(
145: '\\', '/') : elem;
146: String cmpFrom = onWindows ? from.toLowerCase().replace(
147: '\\', '/') : from;
148:
149: // If the element starts with the configured prefix, then
150: // convert the prefix to the configured 'to' value.
151:
152: return cmpElem.startsWith(cmpFrom) ? to
153: + elem.substring(from.length()) : elem;
154: }
155: }
156:
157: /**
158: * An enumeration of supported targets:
159: * "windows", "unix", "netware", and "os/2".
160: */
161: public static class TargetOs extends EnumeratedAttribute {
162: /**
163: * @return the list of values for this enumerated attribute.
164: */
165: public String[] getValues() {
166: return new String[] { "windows", "unix", "netware", "os/2",
167: "tandem" };
168: }
169: }
170:
171: /**
172: * Create a nested path element.
173: * @return a Path to be used by Ant reflection.
174: */
175: public Path createPath() {
176: if (isReference()) {
177: throw noChildrenAllowed();
178: }
179: Path result = new Path(getProject());
180: add(result);
181: return result;
182: }
183:
184: /**
185: * Add an arbitrary ResourceCollection.
186: * @param rc the ResourceCollection to add.
187: * @since Ant 1.7
188: */
189: public void add(ResourceCollection rc) {
190: if (isReference()) {
191: throw noChildrenAllowed();
192: }
193: getPath().add(rc);
194: }
195:
196: private synchronized Union getPath() {
197: if (path == null) {
198: path = new Union();
199: path.setProject(getProject());
200: }
201: return path;
202: }
203:
204: /**
205: * Create a nested MAP element.
206: * @return a Map to configure.
207: */
208: public MapEntry createMap() {
209: MapEntry entry = new MapEntry();
210: prefixMap.addElement(entry);
211: return entry;
212: }
213:
214: /**
215: * Set targetos to a platform to one of
216: * "windows", "unix", "netware", or "os/2";
217: * current platform settings are used by default.
218: * @param target the target os.
219: * @deprecated since 1.5.x.
220: * Use the method taking a TargetOs argument instead.
221: * @see #setTargetos(PathConvert.TargetOs)
222: */
223: public void setTargetos(String target) {
224: TargetOs to = new TargetOs();
225: to.setValue(target);
226: setTargetos(to);
227: }
228:
229: /**
230: * Set targetos to a platform to one of
231: * "windows", "unix", "netware", or "os/2";
232: * current platform settings are used by default.
233: * @param target the target os
234: *
235: * @since Ant 1.5
236: */
237: public void setTargetos(TargetOs target) {
238: targetOS = target.getValue();
239:
240: // Currently, we deal with only two path formats: Unix and Windows
241: // And Unix is everything that is not Windows
242:
243: // for NetWare and OS/2, piggy-back on Windows, since in the
244: // validateSetup code, the same assumptions can be made as
245: // with windows - that ; is the path separator
246:
247: targetWindows = !targetOS.equals("unix")
248: && !targetOS.equals("tandem");
249: }
250:
251: /**
252: * Set whether the specified property will be set if the result
253: * is the empty string.
254: * @param setonempty true or false.
255: *
256: * @since Ant 1.5
257: */
258: public void setSetonempty(boolean setonempty) {
259: this .setonempty = setonempty;
260: }
261:
262: /**
263: * Set the name of the property into which the converted path will be placed.
264: * @param p the property name.
265: */
266: public void setProperty(String p) {
267: property = p;
268: }
269:
270: /**
271: * Add a reference to a Path, FileSet, DirSet, or FileList defined elsewhere.
272: * @param r the reference to a path, fileset, dirset or filelist.
273: */
274: public void setRefid(Reference r) {
275: if (path != null) {
276: throw noChildrenAllowed();
277: }
278: refid = r;
279: }
280:
281: /**
282: * Set the default path separator string; defaults to current JVM
283: * {@link java.io.File#pathSeparator File.pathSeparator}.
284: * @param sep path separator string.
285: */
286: public void setPathSep(String sep) {
287: pathSep = sep;
288: }
289:
290: /**
291: * Set the default directory separator string;
292: * defaults to current JVM {@link java.io.File#separator File.separator}.
293: * @param sep directory separator string.
294: */
295: public void setDirSep(String sep) {
296: dirSep = sep;
297: }
298:
299: /**
300: * Learn whether the refid attribute of this element been set.
301: * @return true if refid is valid.
302: */
303: public boolean isReference() {
304: return refid != null;
305: }
306:
307: /**
308: * Do the execution.
309: * @throws BuildException if something is invalid.
310: */
311: public void execute() throws BuildException {
312: Union savedPath = path;
313: String savedPathSep = pathSep; // may be altered in validateSetup
314: String savedDirSep = dirSep; // may be altered in validateSetup
315:
316: try {
317: // If we are a reference, create a Path from the reference
318: if (isReference()) {
319: Object o = refid.getReferencedObject(getProject());
320: if (!(o instanceof ResourceCollection)) {
321: throw new BuildException(
322: "refid '"
323: + refid.getRefId()
324: + "' does not refer to a resource collection.");
325: }
326: getPath().add((ResourceCollection) o);
327: }
328: validateSetup(); // validate our setup
329:
330: // Currently, we deal with only two path formats: Unix and Windows
331: // And Unix is everything that is not Windows
332: // (with the exception for NetWare and OS/2 below)
333:
334: // for NetWare and OS/2, piggy-back on Windows, since here and
335: // in the apply code, the same assumptions can be made as with
336: // windows - that \\ is an OK separator, and do comparisons
337: // case-insensitive.
338: String fromDirSep = onWindows ? "\\" : "/";
339:
340: StringBuffer rslt = new StringBuffer();
341:
342: // Get the list of path components in canonical form
343: String[] elems = path.list();
344:
345: if (mapper != null) {
346: FileNameMapper impl = mapper.getImplementation();
347: List ret = new ArrayList();
348: for (int i = 0; i < elems.length; ++i) {
349: String[] mapped = impl.mapFileName(elems[i]);
350: for (int m = 0; mapped != null && m < mapped.length; ++m) {
351: ret.add(mapped[m]);
352: }
353: }
354: elems = (String[]) ret.toArray(new String[ret.size()]);
355: }
356: for (int i = 0; i < elems.length; i++) {
357: String elem = mapElement(elems[i]); // Apply the path prefix map
358:
359: // Now convert the path and file separator characters from the
360: // current os to the target os.
361:
362: if (i != 0) {
363: rslt.append(pathSep);
364: }
365: StringTokenizer stDirectory = new StringTokenizer(elem,
366: fromDirSep, true);
367:
368: while (stDirectory.hasMoreTokens()) {
369: String token = stDirectory.nextToken();
370: rslt.append(fromDirSep.equals(token) ? dirSep
371: : token);
372: }
373: }
374: // Place the result into the specified property,
375: // unless setonempty == false
376: if (setonempty || rslt.length() > 0) {
377: String value = rslt.toString();
378: if (property == null) {
379: log(value);
380: } else {
381: log("Set property " + property + " = " + value,
382: Project.MSG_VERBOSE);
383: getProject().setNewProperty(property, value);
384: }
385: }
386: } finally {
387: path = savedPath;
388: dirSep = savedDirSep;
389: pathSep = savedPathSep;
390: }
391: }
392:
393: /**
394: * Apply the configured map to a path element. The map is used to convert
395: * between Windows drive letters and Unix paths. If no map is configured,
396: * then the input string is returned unchanged.
397: *
398: * @param elem The path element to apply the map to.
399: * @return String Updated element.
400: */
401: private String mapElement(String elem) {
402:
403: int size = prefixMap.size();
404:
405: if (size != 0) {
406:
407: // Iterate over the map entries and apply each one.
408: // Stop when one of the entries actually changes the element.
409:
410: for (int i = 0; i < size; i++) {
411: MapEntry entry = (MapEntry) prefixMap.elementAt(i);
412: String newElem = entry.apply(elem);
413:
414: // Note I'm using "!=" to see if we got a new object back from
415: // the apply method.
416:
417: if (newElem != elem) {
418: elem = newElem;
419: break; // We applied one, so we're done
420: }
421: }
422: }
423: return elem;
424: }
425:
426: /**
427: * Add a mapper to convert the file names.
428: *
429: * @param mapper a <code>Mapper</code> value.
430: */
431: public void addMapper(Mapper mapper) {
432: if (this .mapper != null) {
433: throw new BuildException(
434: "Cannot define more than one mapper");
435: }
436: this .mapper = mapper;
437: }
438:
439: /**
440: * Add a nested filenamemapper.
441: * @param fileNameMapper the mapper to add.
442: * @since Ant 1.6.3
443: */
444: public void add(FileNameMapper fileNameMapper) {
445: Mapper m = new Mapper(getProject());
446: m.add(fileNameMapper);
447: addMapper(m);
448: }
449:
450: /**
451: * Validate that all our parameters have been properly initialized.
452: *
453: * @throws BuildException if something is not set up properly.
454: */
455: private void validateSetup() throws BuildException {
456:
457: if (path == null) {
458: throw new BuildException(
459: "You must specify a path to convert");
460: }
461: // Determine the separator strings. The dirsep and pathsep attributes
462: // override the targetOS settings.
463: String dsep = File.separator;
464: String psep = File.pathSeparator;
465:
466: if (targetOS != null) {
467: psep = targetWindows ? ";" : ":";
468: dsep = targetWindows ? "\\" : "/";
469: }
470: if (pathSep != null) {
471: // override with pathsep=
472: psep = pathSep;
473: }
474: if (dirSep != null) {
475: // override with dirsep=
476: dsep = dirSep;
477: }
478: pathSep = psep;
479: dirSep = dsep;
480: }
481:
482: /**
483: * Creates an exception that indicates that this XML element must not have
484: * child elements if the refid attribute is set.
485: * @return BuildException.
486: */
487: private BuildException noChildrenAllowed() {
488: return new BuildException("You must not specify nested "
489: + "elements when using the refid attribute.");
490: }
491:
492: }
|