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