1. /*
  2. * Copyright 2000-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 org.apache.tools.ant.AntClassLoader;
  20. import org.apache.tools.ant.BuildException;
  21. import org.apache.tools.ant.Project;
  22. import org.apache.tools.ant.Task;
  23. import org.apache.tools.ant.taskdefs.condition.Condition;
  24. import org.apache.tools.ant.types.EnumeratedAttribute;
  25. import org.apache.tools.ant.types.Path;
  26. import org.apache.tools.ant.types.Reference;
  27. import org.apache.tools.ant.util.FileUtils;
  28. import org.apache.tools.ant.util.StringUtils;
  29. /**
  30. * Will set the given property if the requested resource is available at
  31. * runtime. This task may also be used as a condition by the condition task.
  32. *
  33. * @since Ant 1.1
  34. *
  35. * @ant.task category="control"
  36. */
  37. public class Available extends Task implements Condition {
  38. private String property;
  39. private String classname;
  40. private String file;
  41. private Path filepath;
  42. private String resource;
  43. private FileDir type;
  44. private Path classpath;
  45. private AntClassLoader loader;
  46. private String value = "true";
  47. private boolean isTask = false;
  48. private boolean ignoreSystemclasses = false;
  49. /**
  50. * Set the classpath to be used when searching for classes and resources.
  51. *
  52. * @param classpath an Ant Path object containing the search path.
  53. */
  54. public void setClasspath(Path classpath) {
  55. createClasspath().append(classpath);
  56. }
  57. /**
  58. * Classpath to be used when searching for classes and resources.
  59. *
  60. * @return an empty Path instance to be configured by Ant.
  61. */
  62. public Path createClasspath() {
  63. if (this.classpath == null) {
  64. this.classpath = new Path(getProject());
  65. }
  66. return this.classpath.createPath();
  67. }
  68. /**
  69. * Set the classpath by reference.
  70. *
  71. * @param r a Reference to a Path instance to be used as the classpath
  72. * value.
  73. */
  74. public void setClasspathRef(Reference r) {
  75. createClasspath().setRefid(r);
  76. }
  77. /**
  78. * Set the path to use when looking for a file.
  79. *
  80. * @param filepath a Path instance containing the search path for files.
  81. */
  82. public void setFilepath(Path filepath) {
  83. createFilepath().append(filepath);
  84. }
  85. /**
  86. * Path to search for file resources.
  87. *
  88. * @return a new Path instance which Ant will configure with a file search
  89. * path.
  90. */
  91. public Path createFilepath() {
  92. if (this.filepath == null) {
  93. this.filepath = new Path(getProject());
  94. }
  95. return this.filepath.createPath();
  96. }
  97. /**
  98. * Set the name of the property which will be set if the particular resource
  99. * is available.
  100. *
  101. * @param property the name of the property to set.
  102. */
  103. public void setProperty(String property) {
  104. this.property = property;
  105. }
  106. /**
  107. * Set the value to be given to the property if the desired resource is
  108. * available.
  109. *
  110. * @param value the value to be given.
  111. */
  112. public void setValue(String value) {
  113. this.value = value;
  114. }
  115. /**
  116. * Set a classname of a class which must be available to set the given
  117. * property.
  118. *
  119. * @param classname the name of the class required.
  120. */
  121. public void setClassname(String classname) {
  122. if (!"".equals(classname)) {
  123. this.classname = classname;
  124. }
  125. }
  126. /**
  127. * Set the file which must be present in the file system to set the given
  128. * property.
  129. *
  130. * @param file the name of the file which is required.
  131. */
  132. public void setFile(File file) {
  133. this.file = FileUtils.newFileUtils()
  134. .removeLeadingPath(getProject().getBaseDir(), file);
  135. }
  136. /**
  137. * Set the name of a Java resource which is required to set the property.
  138. *
  139. * @param resource the name of a resource which is required to be available.
  140. */
  141. public void setResource(String resource) {
  142. this.resource = resource;
  143. }
  144. /**
  145. * @deprecated setType(String) is deprecated and is replaced with
  146. * setType(Available.FileDir) to make Ant's Introspection
  147. * mechanism do the work and also to encapsulate operations on
  148. * the type in its own class.
  149. * @param type the type of resource
  150. */
  151. public void setType(String type) {
  152. log("DEPRECATED - The setType(String) method has been deprecated."
  153. + " Use setType(Available.FileDir) instead.");
  154. this.type = new FileDir();
  155. this.type.setValue(type);
  156. }
  157. /**
  158. * Set what type of file is required - either directory or file.
  159. *
  160. * @param type an instance of the FileDir enumeratedAttribute indicating
  161. * whether the file required is to be a directory or a plain
  162. * file.
  163. */
  164. public void setType(FileDir type) {
  165. this.type = type;
  166. }
  167. /**
  168. * Set whether the search for classes should ignore the runtime classes and
  169. * just use the given classpath.
  170. *
  171. * @param ignore true if system classes are to be ignored.
  172. */
  173. public void setIgnoresystemclasses(boolean ignore) {
  174. this.ignoreSystemclasses = ignore;
  175. }
  176. /**
  177. * Entry point when operating as a task.
  178. *
  179. * @exception BuildException if the task is not configured correctly.
  180. */
  181. public void execute() throws BuildException {
  182. if (property == null) {
  183. throw new BuildException("property attribute is required",
  184. getLocation());
  185. }
  186. isTask = true;
  187. try {
  188. if (eval()) {
  189. String oldvalue = getProject().getProperty(property);
  190. if (null != oldvalue && !oldvalue.equals(value)) {
  191. log("DEPRECATED - <available> used to override an existing"
  192. + " property."
  193. + StringUtils.LINE_SEP
  194. + " Build file should not reuse the same property"
  195. + " name for different values.");
  196. }
  197. getProject().setProperty(property, value);
  198. }
  199. } finally {
  200. isTask = false;
  201. }
  202. }
  203. /**
  204. * Evaluate the availability of a resource.
  205. *
  206. * @return boolean is the resource is available.
  207. * @exception BuildException if the condition is not configured correctly
  208. */
  209. public boolean eval() throws BuildException {
  210. if (classname == null && file == null && resource == null) {
  211. throw new BuildException("At least one of (classname|file|"
  212. + "resource) is required", getLocation());
  213. }
  214. if (type != null) {
  215. if (file == null) {
  216. throw new BuildException("The type attribute is only valid "
  217. + "when specifying the file "
  218. + "attribute.", getLocation());
  219. }
  220. }
  221. if (classpath != null) {
  222. classpath.setProject(getProject());
  223. this.loader = getProject().createClassLoader(classpath);
  224. }
  225. String appendix = "";
  226. if (isTask) {
  227. appendix = " to set property " + property;
  228. } else {
  229. setTaskName("available");
  230. }
  231. if ((classname != null) && !checkClass(classname)) {
  232. log("Unable to load class " + classname + appendix,
  233. Project.MSG_VERBOSE);
  234. return false;
  235. }
  236. if ((file != null) && !checkFile()) {
  237. if (type != null) {
  238. log("Unable to find " + type + " " + file + appendix,
  239. Project.MSG_VERBOSE);
  240. } else {
  241. log("Unable to find " + file + appendix, Project.MSG_VERBOSE);
  242. }
  243. return false;
  244. }
  245. if ((resource != null) && !checkResource(resource)) {
  246. log("Unable to load resource " + resource + appendix,
  247. Project.MSG_VERBOSE);
  248. return false;
  249. }
  250. if (loader != null) {
  251. loader.cleanup();
  252. loader = null;
  253. }
  254. if (!isTask) {
  255. setTaskName(null);
  256. }
  257. return true;
  258. }
  259. /**
  260. * Search for file/directory either relative to project's
  261. * basedir or in the path given as filepath.
  262. *
  263. * <p>filepath can be a list of directory and/or file names (gen'd
  264. * via <fileset>)</p>
  265. *
  266. * <p>look for:</p><ul>
  267. * <li>full-pathname specified == path in list</li>
  268. * <li>full-pathname specified == parent dir of path in list</li>
  269. * <li>simple name specified == path in list</li>
  270. * <li>simple name specified == path in list + name</li>
  271. * <li>simple name specified == parent dir + name</li>
  272. * <li>simple name specified == parent of parent dir + name</li>
  273. * </ul>
  274. */
  275. private boolean checkFile() {
  276. if (filepath == null) {
  277. return checkFile(getProject().resolveFile(file), file);
  278. } else {
  279. String[] paths = filepath.list();
  280. for (int i = 0; i < paths.length; ++i) {
  281. log("Searching " + paths[i], Project.MSG_DEBUG);
  282. File path = new File(paths[i]);
  283. // ** full-pathname specified == path in list
  284. // ** simple name specified == path in list
  285. if (path.exists() && file.equals(paths[i])) {
  286. if (type == null) {
  287. log("Found: " + path, Project.MSG_VERBOSE);
  288. return true;
  289. } else if (type.isDir()
  290. && path.isDirectory()) {
  291. log("Found directory: " + path, Project.MSG_VERBOSE);
  292. return true;
  293. } else if (type.isFile()
  294. && path.isFile()) {
  295. log("Found file: " + path, Project.MSG_VERBOSE);
  296. return true;
  297. }
  298. // not the requested type
  299. return false;
  300. }
  301. FileUtils fileUtils = FileUtils.newFileUtils();
  302. File parent = fileUtils.getParentFile(path);
  303. // ** full-pathname specified == parent dir of path in list
  304. if (parent != null && parent.exists()
  305. && file.equals(parent.getAbsolutePath())) {
  306. if (type == null) {
  307. log("Found: " + parent, Project.MSG_VERBOSE);
  308. return true;
  309. } else if (type.isDir()) {
  310. log("Found directory: " + parent, Project.MSG_VERBOSE);
  311. return true;
  312. }
  313. // not the requested type
  314. return false;
  315. }
  316. // ** simple name specified == path in list + name
  317. if (path.exists() && path.isDirectory()) {
  318. if (checkFile(new File(path, file),
  319. file + " in " + path)) {
  320. return true;
  321. }
  322. }
  323. // ** simple name specified == parent dir + name
  324. if (parent != null && parent.exists()) {
  325. if (checkFile(new File(parent, file),
  326. file + " in " + parent)) {
  327. return true;
  328. }
  329. }
  330. // ** simple name specified == parent of parent dir + name
  331. if (parent != null) {
  332. File grandParent = fileUtils.getParentFile(parent);
  333. if (grandParent != null && grandParent.exists()) {
  334. if (checkFile(new File(grandParent, file),
  335. file + " in " + grandParent)) {
  336. return true;
  337. }
  338. }
  339. }
  340. }
  341. }
  342. return false;
  343. }
  344. /**
  345. * Check if a given file exists and matches the required type.
  346. */
  347. private boolean checkFile(File f, String text) {
  348. if (type != null) {
  349. if (type.isDir()) {
  350. if (f.isDirectory()) {
  351. log("Found directory: " + text, Project.MSG_VERBOSE);
  352. }
  353. return f.isDirectory();
  354. } else if (type.isFile()) {
  355. if (f.isFile()) {
  356. log("Found file: " + text, Project.MSG_VERBOSE);
  357. }
  358. return f.isFile();
  359. }
  360. }
  361. if (f.exists()) {
  362. log("Found: " + text, Project.MSG_VERBOSE);
  363. }
  364. return f.exists();
  365. }
  366. /**
  367. * Check if a given resource can be loaded.
  368. */
  369. private boolean checkResource(String resource) {
  370. if (loader != null) {
  371. return (loader.getResourceAsStream(resource) != null);
  372. } else {
  373. ClassLoader cL = this.getClass().getClassLoader();
  374. if (cL != null) {
  375. return (cL.getResourceAsStream(resource) != null);
  376. } else {
  377. return
  378. (ClassLoader.getSystemResourceAsStream(resource) != null);
  379. }
  380. }
  381. }
  382. /**
  383. * Check if a given class can be loaded.
  384. */
  385. private boolean checkClass(String classname) {
  386. try {
  387. Class requiredClass = null;
  388. if (ignoreSystemclasses) {
  389. loader = getProject().createClassLoader(classpath);
  390. loader.setParentFirst(false);
  391. loader.addJavaLibraries();
  392. if (loader != null) {
  393. try {
  394. requiredClass = loader.findClass(classname);
  395. } catch (SecurityException se) {
  396. // class found but restricted name; this is
  397. // actually the case we're looking for in JDK 1.3+,
  398. // so catch the exception and return
  399. return true;
  400. }
  401. } else {
  402. return false;
  403. }
  404. } else if (loader != null) {
  405. requiredClass = loader.loadClass(classname);
  406. } else {
  407. ClassLoader l = this.getClass().getClassLoader();
  408. // Can return null to represent the bootstrap class loader.
  409. // see API docs of Class.getClassLoader.
  410. if (l != null) {
  411. requiredClass = Class.forName(classname, true, l);
  412. } else {
  413. requiredClass = Class.forName(classname);
  414. }
  415. }
  416. return true;
  417. } catch (ClassNotFoundException e) {
  418. log("class \"" + classname + "\" was not found",
  419. Project.MSG_DEBUG);
  420. return false;
  421. } catch (NoClassDefFoundError e) {
  422. log("Could not load dependent class \"" + e.getMessage()
  423. + "\" for class \"" + classname + "\"",
  424. Project.MSG_DEBUG);
  425. return false;
  426. }
  427. }
  428. /**
  429. * EnumeratedAttribute covering the file types to be checked for, either
  430. * file or dir.
  431. */
  432. public static class FileDir extends EnumeratedAttribute {
  433. private static final String[] VALUES = {"file", "dir"};
  434. /**
  435. * @see EnumeratedAttribute#getValues
  436. */
  437. public String[] getValues() {
  438. return VALUES;
  439. }
  440. /**
  441. * Indicate if the value specifies a directory.
  442. *
  443. * @return true if the value specifies a directory.
  444. */
  445. public boolean isDir() {
  446. return "dir".equalsIgnoreCase(getValue());
  447. }
  448. /**
  449. * Indicate if the value specifies a file.
  450. *
  451. * @return true if the value specifies a file.
  452. */
  453. public boolean isFile() {
  454. return "file".equalsIgnoreCase(getValue());
  455. }
  456. }
  457. }