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;
  18. import java.io.BufferedReader;
  19. import java.io.File;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.util.Hashtable;
  23. import java.util.Locale;
  24. import java.util.Vector;
  25. import org.apache.tools.ant.helper.ProjectHelper2;
  26. import org.apache.tools.ant.util.LoaderUtils;
  27. import org.xml.sax.AttributeList;
  28. /**
  29. * Configures a Project (complete with Targets and Tasks) based on
  30. * a XML build file. It'll rely on a plugin to do the actual processing
  31. * of the xml file.
  32. *
  33. * This class also provide static wrappers for common introspection.
  34. *
  35. * All helper plugins must provide backward compatibility with the
  36. * original ant patterns, unless a different behavior is explicitly
  37. * specified. For example, if namespace is used on the <project> tag
  38. * the helper can expect the entire build file to be namespace-enabled.
  39. * Namespaces or helper-specific tags can provide meta-information to
  40. * the helper, allowing it to use new ( or different policies ).
  41. *
  42. * However, if no namespace is used the behavior should be exactly
  43. * identical with the default helper.
  44. *
  45. */
  46. public class ProjectHelper {
  47. /** The URI for ant name space */
  48. public static final String ANT_CORE_URI = "antlib:org.apache.tools.ant";
  49. /** The URI for antlib current definitions */
  50. public static final String ANT_CURRENT_URI = "ant:current";
  51. /** The URI for defined types/tasks - the format is antlib:<package> */
  52. public static final String ANTLIB_URI = "antlib:";
  53. /** Polymorphic attribute */
  54. public static final String ANT_TYPE = "ant-type";
  55. /**
  56. * Name of JVM system property which provides the name of the
  57. * ProjectHelper class to use.
  58. */
  59. public static final String HELPER_PROPERTY =
  60. "org.apache.tools.ant.ProjectHelper";
  61. /**
  62. * The service identifier in jars which provide Project Helper
  63. * implementations.
  64. */
  65. public static final String SERVICE_ID =
  66. "META-INF/services/org.apache.tools.ant.ProjectHelper";
  67. /**
  68. * Configures the project with the contents of the specified XML file.
  69. *
  70. * @param project The project to configure. Must not be <code>null</code>.
  71. * @param buildFile An XML file giving the project's configuration.
  72. * Must not be <code>null</code>.
  73. *
  74. * @deprecated Use the non-static parse method
  75. * @exception BuildException if the configuration is invalid or cannot
  76. * be read
  77. */
  78. public static void configureProject(Project project, File buildFile)
  79. throws BuildException {
  80. ProjectHelper helper = ProjectHelper.getProjectHelper();
  81. project.addReference("ant.projectHelper", helper);
  82. helper.parse(project, buildFile);
  83. }
  84. /** Default constructor */
  85. public ProjectHelper() {
  86. }
  87. // -------------------- Common properties --------------------
  88. // The following properties are required by import ( and other tasks
  89. // that read build files using ProjectHelper ).
  90. // A project helper may process multiple files. We'll keep track
  91. // of them - to avoid loops and to allow caching. The caching will
  92. // probably accelerate things like <antCall>.
  93. // The key is the absolute file, the value is a processed tree.
  94. // Since the tree is composed of UE and RC - it can be reused !
  95. // protected Hashtable processedFiles=new Hashtable();
  96. private Vector importStack = new Vector();
  97. // Temporary - until we figure a better API
  98. /** EXPERIMENTAL WILL_CHANGE
  99. *
  100. */
  101. // public Hashtable getProcessedFiles() {
  102. // return processedFiles;
  103. // }
  104. /** EXPERIMENTAL WILL_CHANGE
  105. * Import stack.
  106. * Used to keep track of imported files. Error reporting should
  107. * display the import path.
  108. *
  109. * @return the stack of import source objects.
  110. */
  111. public Vector getImportStack() {
  112. return importStack;
  113. }
  114. // -------------------- Parse method --------------------
  115. /**
  116. * Parses the project file, configuring the project as it goes.
  117. *
  118. * @param project The project for the resulting ProjectHelper to configure.
  119. * Must not be <code>null</code>.
  120. * @param source The source for XML configuration. A helper must support
  121. * at least File, for backward compatibility. Helpers may
  122. * support URL, InputStream, etc or specialized types.
  123. *
  124. * @since Ant1.5
  125. * @exception BuildException if the configuration is invalid or cannot
  126. * be read
  127. */
  128. public void parse(Project project, Object source) throws BuildException {
  129. throw new BuildException("ProjectHelper.parse() must be implemented "
  130. + "in a helper plugin " + this.getClass().getName());
  131. }
  132. /**
  133. * Discovers a project helper instance. Uses the same patterns
  134. * as JAXP, commons-logging, etc: a system property, a JDK1.3
  135. * service discovery, default.
  136. *
  137. * @return a ProjectHelper, either a custom implementation
  138. * if one is available and configured, or the default implementation
  139. * otherwise.
  140. *
  141. * @exception BuildException if a specified helper class cannot
  142. * be loaded/instantiated.
  143. */
  144. public static ProjectHelper getProjectHelper()
  145. throws BuildException {
  146. // Identify the class loader we will be using. Ant may be
  147. // in a webapp or embedded in a different app
  148. ProjectHelper helper = null;
  149. // First, try the system property
  150. String helperClass = System.getProperty(HELPER_PROPERTY);
  151. try {
  152. if (helperClass != null) {
  153. helper = newHelper(helperClass);
  154. }
  155. } catch (SecurityException e) {
  156. System.out.println("Unable to load ProjectHelper class \""
  157. + helperClass + " specified in system property "
  158. + HELPER_PROPERTY);
  159. }
  160. // A JDK1.3 'service' ( like in JAXP ). That will plug a helper
  161. // automatically if in CLASSPATH, with the right META-INF/services.
  162. if (helper == null) {
  163. try {
  164. ClassLoader classLoader = LoaderUtils.getContextClassLoader();
  165. InputStream is = null;
  166. if (classLoader != null) {
  167. is = classLoader.getResourceAsStream(SERVICE_ID);
  168. }
  169. if (is == null) {
  170. is = ClassLoader.getSystemResourceAsStream(SERVICE_ID);
  171. }
  172. if (is != null) {
  173. // This code is needed by EBCDIC and other strange systems.
  174. // It's a fix for bugs reported in xerces
  175. InputStreamReader isr;
  176. try {
  177. isr = new InputStreamReader(is, "UTF-8");
  178. } catch (java.io.UnsupportedEncodingException e) {
  179. isr = new InputStreamReader(is);
  180. }
  181. BufferedReader rd = new BufferedReader(isr);
  182. String helperClassName = rd.readLine();
  183. rd.close();
  184. if (helperClassName != null
  185. && !"".equals(helperClassName)) {
  186. helper = newHelper(helperClassName);
  187. }
  188. }
  189. } catch (Exception ex) {
  190. System.out.println("Unable to load ProjectHelper "
  191. + "from service \"" + SERVICE_ID);
  192. }
  193. }
  194. if (helper != null) {
  195. return helper;
  196. } else {
  197. try {
  198. // Default
  199. // return new ProjectHelperImpl();
  200. return new ProjectHelper2();
  201. } catch (Throwable e) {
  202. String message = "Unable to load default ProjectHelper due to "
  203. + e.getClass().getName() + ": " + e.getMessage();
  204. throw new BuildException(message, e);
  205. }
  206. }
  207. }
  208. /**
  209. * Creates a new helper instance from the name of the class.
  210. * It'll first try the thread class loader, then Class.forName()
  211. * will load from the same loader that loaded this class.
  212. *
  213. * @param helperClass The name of the class to create an instance
  214. * of. Must not be <code>null</code>.
  215. *
  216. * @return a new instance of the specified class.
  217. *
  218. * @exception BuildException if the class cannot be found or
  219. * cannot be appropriate instantiated.
  220. */
  221. private static ProjectHelper newHelper(String helperClass)
  222. throws BuildException {
  223. ClassLoader classLoader = LoaderUtils.getContextClassLoader();
  224. try {
  225. Class clazz = null;
  226. if (classLoader != null) {
  227. try {
  228. clazz = classLoader.loadClass(helperClass);
  229. } catch (ClassNotFoundException ex) {
  230. // try next method
  231. }
  232. }
  233. if (clazz == null) {
  234. clazz = Class.forName(helperClass);
  235. }
  236. return ((ProjectHelper) clazz.newInstance());
  237. } catch (Exception e) {
  238. throw new BuildException(e);
  239. }
  240. }
  241. /**
  242. * JDK1.1 compatible access to the context class loader.
  243. * Cut&paste from JAXP.
  244. *
  245. * @deprecated Use LoaderUtils.getContextClassLoader()
  246. * @return the current context class loader, or <code>null</code>
  247. * if the context class loader is unavailable.
  248. */
  249. public static ClassLoader getContextClassLoader() {
  250. if (!LoaderUtils.isContextLoaderAvailable()) {
  251. return null;
  252. }
  253. return LoaderUtils.getContextClassLoader();
  254. }
  255. // -------------------- Static utils, used by most helpers ----------------
  256. /**
  257. * Configures an object using an introspection handler.
  258. *
  259. * @param target The target object to be configured.
  260. * Must not be <code>null</code>.
  261. * @param attrs A list of attributes to configure within the target.
  262. * Must not be <code>null</code>.
  263. * @param project The project containing the target.
  264. * Must not be <code>null</code>.
  265. *
  266. * @deprecated Use IntrospectionHelper for each property
  267. * @exception BuildException if any of the attributes can't be handled by
  268. * the target
  269. */
  270. public static void configure(Object target, AttributeList attrs,
  271. Project project) throws BuildException {
  272. if (target instanceof TypeAdapter) {
  273. target = ((TypeAdapter) target).getProxy();
  274. }
  275. IntrospectionHelper ih =
  276. IntrospectionHelper.getHelper(target.getClass());
  277. project.addBuildListener(ih);
  278. for (int i = 0; i < attrs.getLength(); i++) {
  279. // reflect these into the target
  280. String value = replaceProperties(project, attrs.getValue(i),
  281. project.getProperties());
  282. try {
  283. ih.setAttribute(project, target,
  284. attrs.getName(i).toLowerCase(Locale.US), value);
  285. } catch (BuildException be) {
  286. // id attribute must be set externally
  287. if (!attrs.getName(i).equals("id")) {
  288. throw be;
  289. }
  290. }
  291. }
  292. }
  293. /**
  294. * Adds the content of #PCDATA sections to an element.
  295. *
  296. * @param project The project containing the target.
  297. * Must not be <code>null</code>.
  298. * @param target The target object to be configured.
  299. * Must not be <code>null</code>.
  300. * @param buf A character array of the text within the element.
  301. * Will not be <code>null</code>.
  302. * @param start The start element in the array.
  303. * @param count The number of characters to read from the array.
  304. *
  305. * @exception BuildException if the target object doesn't accept text
  306. */
  307. public static void addText(Project project, Object target, char[] buf,
  308. int start, int count) throws BuildException {
  309. addText(project, target, new String(buf, start, count));
  310. }
  311. /**
  312. * Adds the content of #PCDATA sections to an element.
  313. *
  314. * @param project The project containing the target.
  315. * Must not be <code>null</code>.
  316. * @param target The target object to be configured.
  317. * Must not be <code>null</code>.
  318. * @param text Text to add to the target.
  319. * May be <code>null</code>, in which case this
  320. * method call is a no-op.
  321. *
  322. * @exception BuildException if the target object doesn't accept text
  323. */
  324. public static void addText(Project project, Object target, String text)
  325. throws BuildException {
  326. if (text == null) {
  327. return;
  328. }
  329. if (target instanceof TypeAdapter) {
  330. target = ((TypeAdapter) target).getProxy();
  331. }
  332. IntrospectionHelper.getHelper(target.getClass()).addText(project,
  333. target, text);
  334. }
  335. /**
  336. * Stores a configured child element within its parent object.
  337. *
  338. * @param project Project containing the objects.
  339. * May be <code>null</code>.
  340. * @param parent Parent object to add child to.
  341. * Must not be <code>null</code>.
  342. * @param child Child object to store in parent.
  343. * Should not be <code>null</code>.
  344. * @param tag Name of element which generated the child.
  345. * May be <code>null</code>, in which case
  346. * the child is not stored.
  347. */
  348. public static void storeChild(Project project, Object parent,
  349. Object child, String tag) {
  350. IntrospectionHelper ih
  351. = IntrospectionHelper.getHelper(parent.getClass());
  352. ih.storeElement(project, parent, child, tag);
  353. }
  354. /**
  355. * Replaces <code>${xxx}</code> style constructions in the given value with
  356. * the string value of the corresponding properties.
  357. *
  358. * @param project The project containing the properties to replace.
  359. * Must not be <code>null</code>.
  360. *
  361. * @param value The string to be scanned for property references.
  362. * May be <code>null</code>.
  363. *
  364. * @exception BuildException if the string contains an opening
  365. * <code>${</code> without a closing
  366. * <code>}</code>
  367. * @return the original string with the properties replaced, or
  368. * <code>null</code> if the original string is <code>null</code>.
  369. *
  370. * @deprecated Use project.replaceProperties()
  371. * @since 1.5
  372. */
  373. public static String replaceProperties(Project project, String value)
  374. throws BuildException {
  375. // needed since project properties are not accessible
  376. return project.replaceProperties(value);
  377. }
  378. /**
  379. * Replaces <code>${xxx}</code> style constructions in the given value
  380. * with the string value of the corresponding data types.
  381. *
  382. * @param project The container project. This is used solely for
  383. * logging purposes. Must not be <code>null</code>.
  384. * @param value The string to be scanned for property references.
  385. * May be <code>null</code>, in which case this
  386. * method returns immediately with no effect.
  387. * @param keys Mapping (String to String) of property names to their
  388. * values. Must not be <code>null</code>.
  389. *
  390. * @exception BuildException if the string contains an opening
  391. * <code>${</code> without a closing
  392. * <code>}</code>
  393. * @return the original string with the properties replaced, or
  394. * <code>null</code> if the original string is <code>null</code>.
  395. * @deprecated Use PropertyHelper
  396. */
  397. public static String replaceProperties(Project project, String value,
  398. Hashtable keys) throws BuildException {
  399. PropertyHelper ph = PropertyHelper.getPropertyHelper(project);
  400. return ph.replaceProperties(null, value, keys);
  401. }
  402. /**
  403. * Parses a string containing <code>${xxx}</code> style property
  404. * references into two lists. The first list is a collection
  405. * of text fragments, while the other is a set of string property names.
  406. * <code>null</code> entries in the first list indicate a property
  407. * reference from the second list.
  408. *
  409. * @param value Text to parse. Must not be <code>null</code>.
  410. * @param fragments List to add text fragments to.
  411. * Must not be <code>null</code>.
  412. * @param propertyRefs List to add property names to.
  413. * Must not be <code>null</code>.
  414. *
  415. * @deprecated Use PropertyHelper
  416. * @exception BuildException if the string contains an opening
  417. * <code>${</code> without a closing
  418. * <code>}</code>
  419. */
  420. public static void parsePropertyString(String value, Vector fragments,
  421. Vector propertyRefs)
  422. throws BuildException {
  423. PropertyHelper.parsePropertyStringDefault(value, fragments,
  424. propertyRefs);
  425. }
  426. /**
  427. * Map a namespaced {uri,name} to an internal string format.
  428. * For BC purposes the names from the ant core uri will be
  429. * mapped to "name", other names will be mapped to
  430. * uri + ":" + name.
  431. * @param uri The namepace URI
  432. * @param name The localname
  433. * @return The stringified form of the ns name
  434. */
  435. public static String genComponentName(String uri, String name) {
  436. if (uri == null || uri.equals("") || uri.equals(ANT_CORE_URI)) {
  437. return name;
  438. }
  439. return uri + ":" + name;
  440. }
  441. /**
  442. * extract a uri from a component name
  443. *
  444. * @param componentName The stringified form for {uri, name}
  445. * @return The uri or "" if not present
  446. */
  447. public static String extractUriFromComponentName(String componentName) {
  448. if (componentName == null) {
  449. return "";
  450. }
  451. int index = componentName.lastIndexOf(':');
  452. if (index == -1) {
  453. return "";
  454. }
  455. return componentName.substring(0, index);
  456. }
  457. /**
  458. * extract the element name from a component name
  459. *
  460. * @param componentName The stringified form for {uri, name}
  461. * @return The element name of the component
  462. */
  463. public static String extractNameFromComponentName(String componentName) {
  464. int index = componentName.lastIndexOf(':');
  465. if (index == -1) {
  466. return componentName;
  467. }
  468. return componentName.substring(index + 1);
  469. }
  470. /**
  471. * Add location to build exception.
  472. * @param ex the build exception, if the build exception
  473. * does not include
  474. * @param newLocation the location of the calling task (may be null)
  475. * @return a new build exception based in the build exception with
  476. * location set to newLocation. If the original exception
  477. * did not have a location, just return the build exception
  478. */
  479. public static BuildException addLocationToBuildException(
  480. BuildException ex, Location newLocation) {
  481. if (ex.getLocation() == null || ex.getMessage() == null) {
  482. return ex;
  483. }
  484. String errorMessage
  485. = "The following error occurred while executing this line:"
  486. + System.getProperty("line.separator")
  487. + ex.getLocation().toString()
  488. + ex.getMessage();
  489. if (newLocation == null) {
  490. return new BuildException(errorMessage, ex);
  491. } else {
  492. return new BuildException(
  493. errorMessage, ex, newLocation);
  494. }
  495. }
  496. }