1. /*
  2. * Copyright 2001-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.util.StringTokenizer;
  20. import java.util.Vector;
  21. import java.util.List;
  22. import java.util.ArrayList;
  23. import org.apache.tools.ant.BuildException;
  24. import org.apache.tools.ant.Project;
  25. import org.apache.tools.ant.Task;
  26. import org.apache.tools.ant.taskdefs.condition.Os;
  27. import org.apache.tools.ant.types.DirSet;
  28. import org.apache.tools.ant.types.EnumeratedAttribute;
  29. import org.apache.tools.ant.types.FileList;
  30. import org.apache.tools.ant.types.FileSet;
  31. import org.apache.tools.ant.types.Path;
  32. import org.apache.tools.ant.types.Reference;
  33. import org.apache.tools.ant.types.Mapper;
  34. import org.apache.tools.ant.util.FileNameMapper;
  35. /**
  36. * Converts path and classpath information to a specific target OS
  37. * format. The resulting formatted path is placed into the specified property.
  38. *
  39. * @since Ant 1.4
  40. * @ant.task category="utility"
  41. */
  42. public class PathConvert extends Task {
  43. // Members
  44. /**
  45. * Path to be converted
  46. */
  47. private Path path = null;
  48. /**
  49. * Reference to path/fileset to convert
  50. */
  51. private Reference refid = null;
  52. /**
  53. * The target OS type
  54. */
  55. private String targetOS = null;
  56. /**
  57. * Set when targetOS is set to windows
  58. */
  59. private boolean targetWindows = false;
  60. /**
  61. * Set if we're running on windows
  62. */
  63. private boolean onWindows = false;
  64. /**
  65. * Set if we should create a new property even if the result is empty
  66. */
  67. private boolean setonempty = true;
  68. /**
  69. * The property to receive the conversion
  70. */
  71. private String property = null;//
  72. /**
  73. * Path prefix map
  74. */
  75. private Vector prefixMap = new Vector();
  76. /**
  77. * User override on path sep char
  78. */
  79. private String pathSep = null;
  80. /**
  81. * User override on directory sep char
  82. */
  83. private String dirSep = null;
  84. /** Filename mapper */
  85. private Mapper mapper = null;
  86. /**
  87. * constructor
  88. */
  89. public PathConvert() {
  90. onWindows = Os.isFamily("dos");
  91. }
  92. /**
  93. * Helper class, holds the nested <map> values. Elements will look like
  94. * this: <map from="d:" to="/foo"/>
  95. *
  96. * When running on windows, the prefix comparison will be case
  97. * insensitive.
  98. */
  99. public class MapEntry {
  100. /** Set the "from" attribute of the map entry */
  101. /**
  102. * the prefix string to search for; required.
  103. * Note that this value is case-insensitive when the build is
  104. * running on a Windows platform and case-sensitive when running on
  105. * a Unix platform.
  106. * @param from
  107. */
  108. public void setFrom(String from) {
  109. this.from = from;
  110. }
  111. /**
  112. * The replacement text to use when from is matched; required.
  113. * @param to new prefix
  114. */
  115. public void setTo(String to) {
  116. this.to = to;
  117. }
  118. /**
  119. * Apply this map entry to a given path element
  120. *
  121. * @param elem Path element to process
  122. * @return String Updated path element after mapping
  123. */
  124. public String apply(String elem) {
  125. if (from == null || to == null) {
  126. throw new BuildException("Both 'from' and 'to' must be set "
  127. + "in a map entry");
  128. }
  129. // If we're on windows, then do the comparison ignoring case
  130. String cmpElem = onWindows ? elem.toLowerCase() : elem;
  131. String cmpFrom = onWindows ? from.toLowerCase() : from;
  132. // If the element starts with the configured prefix, then
  133. // convert the prefix to the configured 'to' value.
  134. if (cmpElem.startsWith(cmpFrom)) {
  135. int len = from.length();
  136. if (len >= elem.length()) {
  137. elem = to;
  138. } else {
  139. elem = to + elem.substring(len);
  140. }
  141. }
  142. return elem;
  143. }
  144. // Members
  145. private String from = null;
  146. private String to = null;
  147. }
  148. /**
  149. * an enumeration of supported targets:
  150. * windows", "unix", "netware", and "os/2".
  151. */
  152. public static class TargetOs extends EnumeratedAttribute {
  153. public String[] getValues() {
  154. return new String[]{"windows", "unix", "netware", "os/2", "tandem"};
  155. }
  156. }
  157. /** Create a nested PATH element */
  158. public Path createPath() {
  159. if (isReference()) {
  160. throw noChildrenAllowed();
  161. }
  162. if (path == null) {
  163. path = new Path(getProject());
  164. }
  165. return path.createPath();
  166. }
  167. /**
  168. * Create a nested MAP element
  169. * @return a Map to configure
  170. */
  171. public MapEntry createMap() {
  172. MapEntry entry = new MapEntry();
  173. prefixMap.addElement(entry);
  174. return entry;
  175. }
  176. /**
  177. * Set targetos to a platform to one of
  178. * "windows", "unix", "netware", or "os/2".
  179. *
  180. * Required unless unless pathsep and/or dirsep are specified.
  181. *
  182. * @deprecated use the method taking a TargetOs argument instead
  183. * @see #setTargetos(PathConvert.TargetOs)
  184. */
  185. public void setTargetos(String target) {
  186. TargetOs to = new TargetOs();
  187. to.setValue(target);
  188. setTargetos(to);
  189. }
  190. /**
  191. * Set targetos to a platform to one of
  192. * "windows", "unix", "netware", or "os/2"; required unless
  193. * unless pathsep and/or dirsep are specified.
  194. *
  195. * @since Ant 1.5
  196. */
  197. public void setTargetos(TargetOs target) {
  198. targetOS = target.getValue();
  199. // Currently, we deal with only two path formats: Unix and Windows
  200. // And Unix is everything that is not Windows
  201. // for NetWare and OS/2, piggy-back on Windows, since in the
  202. // validateSetup code, the same assumptions can be made as
  203. // with windows - that ; is the path separator
  204. targetWindows = !targetOS.equals("unix") && !targetOS.equals("tandem");
  205. }
  206. /**
  207. * Set setonempty
  208. *
  209. * If false, don't set the new property if the result is the empty string.
  210. * @param setonempty true or false
  211. *
  212. * @since Ant 1.5
  213. */
  214. public void setSetonempty(boolean setonempty) {
  215. this.setonempty = setonempty;
  216. }
  217. /**
  218. * The property into which the converted path will be placed.
  219. */
  220. public void setProperty(String p) {
  221. property = p;
  222. }
  223. /**
  224. * Adds a reference to a Path, FileSet, DirSet, or FileList defined
  225. * elsewhere.
  226. */
  227. public void setRefid(Reference r) {
  228. if (path != null) {
  229. throw noChildrenAllowed();
  230. }
  231. refid = r;
  232. }
  233. /**
  234. * Set the default path separator string;
  235. * defaults to current JVM
  236. * {@link java.io.File#pathSeparator File.pathSeparator}
  237. * @param sep path separator string
  238. */
  239. public void setPathSep(String sep) {
  240. pathSep = sep;
  241. }
  242. /**
  243. * Set the default directory separator string;
  244. * defaults to current JVM {@link java.io.File#separator File.separator}
  245. * @param sep directory separator string
  246. */
  247. public void setDirSep(String sep) {
  248. dirSep = sep;
  249. }
  250. /**
  251. * Has the refid attribute of this element been set?
  252. * @return true if refid is valid
  253. */
  254. public boolean isReference() {
  255. return refid != null;
  256. }
  257. /** Do the execution.
  258. * @throws BuildException if something is invalid
  259. */
  260. public void execute() throws BuildException {
  261. Path savedPath = path;
  262. String savedPathSep = pathSep; // may be altered in validateSetup
  263. String savedDirSep = dirSep; // may be altered in validateSetup
  264. try {
  265. // If we are a reference, create a Path from the reference
  266. if (isReference()) {
  267. path = new Path(getProject()).createPath();
  268. Object obj = refid.getReferencedObject(getProject());
  269. if (obj instanceof Path) {
  270. path.setRefid(refid);
  271. } else if (obj instanceof FileSet) {
  272. FileSet fs = (FileSet) obj;
  273. path.addFileset(fs);
  274. } else if (obj instanceof DirSet) {
  275. DirSet ds = (DirSet) obj;
  276. path.addDirset(ds);
  277. } else if (obj instanceof FileList) {
  278. FileList fl = (FileList) obj;
  279. path.addFilelist(fl);
  280. } else {
  281. throw new BuildException("'refid' does not refer to a "
  282. + "path, fileset, dirset, or "
  283. + "filelist.");
  284. }
  285. }
  286. validateSetup(); // validate our setup
  287. // Currently, we deal with only two path formats: Unix and Windows
  288. // And Unix is everything that is not Windows
  289. // (with the exception for NetWare and OS/2 below)
  290. // for NetWare and OS/2, piggy-back on Windows, since here and
  291. // in the apply code, the same assumptions can be made as with
  292. // windows - that \\ is an OK separator, and do comparisons
  293. // case-insensitive.
  294. String fromDirSep = onWindows ? "\\" : "/";
  295. StringBuffer rslt = new StringBuffer(100);
  296. // Get the list of path components in canonical form
  297. String[] elems = path.list();
  298. if (mapper != null) {
  299. FileNameMapper impl = mapper.getImplementation();
  300. List ret = new ArrayList();
  301. for (int i = 0; i < elems.length; ++i) {
  302. String[] mapped = impl.mapFileName(elems[i]);
  303. for (int m = 0; mapped != null && m < mapped.length; ++m) {
  304. ret.add(mapped[m]);
  305. }
  306. }
  307. elems = (String[]) ret.toArray(new String[] {});
  308. }
  309. for (int i = 0; i < elems.length; i++) {
  310. String elem = elems[i];
  311. elem = mapElement(elem); // Apply the path prefix map
  312. // Now convert the path and file separator characters from the
  313. // current os to the target os.
  314. if (i != 0) {
  315. rslt.append(pathSep);
  316. }
  317. StringTokenizer stDirectory =
  318. new StringTokenizer(elem, fromDirSep, true);
  319. String token = null;
  320. while (stDirectory.hasMoreTokens()) {
  321. token = stDirectory.nextToken();
  322. if (fromDirSep.equals(token)) {
  323. rslt.append(dirSep);
  324. } else {
  325. rslt.append(token);
  326. }
  327. }
  328. }
  329. // Place the result into the specified property,
  330. // unless setonempty == false
  331. String value = rslt.toString();
  332. if (setonempty) {
  333. log("Set property " + property + " = " + value,
  334. Project.MSG_VERBOSE);
  335. getProject().setNewProperty(property, value);
  336. } else {
  337. if (rslt.length() > 0) {
  338. log("Set property " + property + " = " + value,
  339. Project.MSG_VERBOSE);
  340. getProject().setNewProperty(property, value);
  341. }
  342. }
  343. } finally {
  344. path = savedPath;
  345. dirSep = savedDirSep;
  346. pathSep = savedPathSep;
  347. }
  348. }
  349. /**
  350. * Apply the configured map to a path element. The map is used to convert
  351. * between Windows drive letters and Unix paths. If no map is configured,
  352. * then the input string is returned unchanged.
  353. *
  354. * @param elem The path element to apply the map to
  355. * @return String Updated element
  356. */
  357. private String mapElement(String elem) {
  358. int size = prefixMap.size();
  359. if (size != 0) {
  360. // Iterate over the map entries and apply each one.
  361. // Stop when one of the entries actually changes the element.
  362. for (int i = 0; i < size; i++) {
  363. MapEntry entry = (MapEntry) prefixMap.elementAt(i);
  364. String newElem = entry.apply(elem);
  365. // Note I'm using "!=" to see if we got a new object back from
  366. // the apply method.
  367. if (newElem != elem) {
  368. elem = newElem;
  369. break; // We applied one, so we're done
  370. }
  371. }
  372. }
  373. return elem;
  374. }
  375. /**
  376. * Add a mapper to convert the file names.
  377. *
  378. * @param mapper a <code>Mapper</code> value
  379. */
  380. public void addMapper(Mapper mapper) {
  381. if (this.mapper != null) {
  382. throw new BuildException(
  383. "Cannot define more than one mapper");
  384. }
  385. this.mapper = mapper;
  386. }
  387. /**
  388. * Validate that all our parameters have been properly initialized.
  389. *
  390. * @throws BuildException if something is not setup properly
  391. */
  392. private void validateSetup() throws BuildException {
  393. if (path == null) {
  394. throw new BuildException("You must specify a path to convert");
  395. }
  396. if (property == null) {
  397. throw new BuildException("You must specify a property");
  398. }
  399. // Must either have a target OS or both a dirSep and pathSep
  400. if (targetOS == null && pathSep == null && dirSep == null) {
  401. throw new BuildException("You must specify at least one of "
  402. + "targetOS, dirSep, or pathSep");
  403. }
  404. // Determine the separator strings. The dirsep and pathsep attributes
  405. // override the targetOS settings.
  406. String dsep = File.separator;
  407. String psep = File.pathSeparator;
  408. if (targetOS != null) {
  409. psep = targetWindows ? ";" : ":";
  410. dsep = targetWindows ? "\\" : "/";
  411. }
  412. if (pathSep != null) {
  413. // override with pathsep=
  414. psep = pathSep;
  415. }
  416. if (dirSep != null) {
  417. // override with dirsep=
  418. dsep = dirSep;
  419. }
  420. pathSep = psep;
  421. dirSep = dsep;
  422. }
  423. /**
  424. * Creates an exception that indicates that this XML element must not have
  425. * child elements if the refid attribute is set.
  426. */
  427. private BuildException noChildrenAllowed() {
  428. return new BuildException("You must not specify nested <path> "
  429. + "elements when using the refid attribute.");
  430. }
  431. }