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:
019: package org.apache.tools.ant;
020:
021: import java.io.BufferedReader;
022: import java.io.File;
023: import java.io.InputStream;
024: import java.io.InputStreamReader;
025: import java.util.Hashtable;
026: import java.util.Locale;
027: import java.util.Vector;
028: import org.apache.tools.ant.helper.ProjectHelper2;
029: import org.apache.tools.ant.util.LoaderUtils;
030: import org.xml.sax.AttributeList;
031:
032: /**
033: * Configures a Project (complete with Targets and Tasks) based on
034: * a XML build file. It'll rely on a plugin to do the actual processing
035: * of the xml file.
036: *
037: * This class also provide static wrappers for common introspection.
038: *
039: * All helper plugins must provide backward compatibility with the
040: * original ant patterns, unless a different behavior is explicitly
041: * specified. For example, if namespace is used on the <project> tag
042: * the helper can expect the entire build file to be namespace-enabled.
043: * Namespaces or helper-specific tags can provide meta-information to
044: * the helper, allowing it to use new ( or different policies ).
045: *
046: * However, if no namespace is used the behavior should be exactly
047: * identical with the default helper.
048: *
049: */
050: public class ProjectHelper {
051: /** The URI for ant name space */
052: public static final String ANT_CORE_URI = "antlib:org.apache.tools.ant";
053:
054: /** The URI for antlib current definitions */
055: public static final String ANT_CURRENT_URI = "ant:current";
056:
057: /** The URI for defined types/tasks - the format is antlib:<package> */
058: public static final String ANTLIB_URI = "antlib:";
059:
060: /** Polymorphic attribute */
061: public static final String ANT_TYPE = "ant-type";
062:
063: /**
064: * Name of JVM system property which provides the name of the
065: * ProjectHelper class to use.
066: */
067: public static final String HELPER_PROPERTY = "org.apache.tools.ant.ProjectHelper";
068:
069: /**
070: * The service identifier in jars which provide Project Helper
071: * implementations.
072: */
073: public static final String SERVICE_ID = "META-INF/services/org.apache.tools.ant.ProjectHelper";
074:
075: /**
076: * name of project helper reference that we add to a project
077: */
078: public static final String PROJECTHELPER_REFERENCE = "ant.projectHelper";
079:
080: /**
081: * Configures the project with the contents of the specified XML file.
082: *
083: * @param project The project to configure. Must not be <code>null</code>.
084: * @param buildFile An XML file giving the project's configuration.
085: * Must not be <code>null</code>.
086: *
087: * @exception BuildException if the configuration is invalid or cannot
088: * be read
089: */
090: public static void configureProject(Project project, File buildFile)
091: throws BuildException {
092: ProjectHelper helper = ProjectHelper.getProjectHelper();
093: project.addReference(PROJECTHELPER_REFERENCE, helper);
094: helper.parse(project, buildFile);
095: }
096:
097: /** Default constructor */
098: public ProjectHelper() {
099: }
100:
101: // -------------------- Common properties --------------------
102: // The following properties are required by import ( and other tasks
103: // that read build files using ProjectHelper ).
104:
105: // A project helper may process multiple files. We'll keep track
106: // of them - to avoid loops and to allow caching. The caching will
107: // probably accelerate things like <antCall>.
108: // The key is the absolute file, the value is a processed tree.
109: // Since the tree is composed of UE and RC - it can be reused !
110: // protected Hashtable processedFiles=new Hashtable();
111:
112: private Vector importStack = new Vector();
113:
114: // Temporary - until we figure a better API
115: /** EXPERIMENTAL WILL_CHANGE
116: *
117: */
118: // public Hashtable getProcessedFiles() {
119: // return processedFiles;
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)
147: throws BuildException {
148: throw new BuildException(
149: "ProjectHelper.parse() must be implemented "
150: + "in a helper plugin "
151: + this .getClass().getName());
152: }
153:
154: /**
155: * Discovers a project helper instance. Uses the same patterns
156: * as JAXP, commons-logging, etc: a system property, a JDK1.3
157: * service discovery, default.
158: *
159: * @return a ProjectHelper, either a custom implementation
160: * if one is available and configured, or the default implementation
161: * otherwise.
162: *
163: * @exception BuildException if a specified helper class cannot
164: * be loaded/instantiated.
165: */
166: public static ProjectHelper getProjectHelper()
167: throws BuildException {
168: // Identify the class loader we will be using. Ant may be
169: // in a webapp or embedded in a different app
170: ProjectHelper helper = null;
171:
172: // First, try the system property
173: String helperClass = System.getProperty(HELPER_PROPERTY);
174: try {
175: if (helperClass != null) {
176: helper = newHelper(helperClass);
177: }
178: } catch (SecurityException e) {
179: System.out.println("Unable to load ProjectHelper class \""
180: + helperClass + " specified in system property "
181: + HELPER_PROPERTY);
182: }
183:
184: // A JDK1.3 'service' ( like in JAXP ). That will plug a helper
185: // automatically if in CLASSPATH, with the right META-INF/services.
186: if (helper == null) {
187: try {
188: ClassLoader classLoader = LoaderUtils
189: .getContextClassLoader();
190: InputStream is = null;
191: if (classLoader != null) {
192: is = classLoader.getResourceAsStream(SERVICE_ID);
193: }
194: if (is == null) {
195: is = ClassLoader
196: .getSystemResourceAsStream(SERVICE_ID);
197: }
198:
199: if (is != null) {
200: // This code is needed by EBCDIC and other strange systems.
201: // It's a fix for bugs reported in xerces
202: InputStreamReader isr;
203: try {
204: isr = new InputStreamReader(is, "UTF-8");
205: } catch (java.io.UnsupportedEncodingException e) {
206: isr = new InputStreamReader(is);
207: }
208: BufferedReader rd = new BufferedReader(isr);
209:
210: String helperClassName = rd.readLine();
211: rd.close();
212:
213: if (helperClassName != null
214: && !"".equals(helperClassName)) {
215:
216: helper = newHelper(helperClassName);
217: }
218: }
219: } catch (Exception ex) {
220: System.out.println("Unable to load ProjectHelper "
221: + "from service \"" + SERVICE_ID);
222: }
223: }
224:
225: if (helper != null) {
226: return helper;
227: } else {
228: return new ProjectHelper2();
229: }
230: }
231:
232: /**
233: * Creates a new helper instance from the name of the class.
234: * It'll first try the thread class loader, then Class.forName()
235: * will load from the same loader that loaded this class.
236: *
237: * @param helperClass The name of the class to create an instance
238: * of. Must not be <code>null</code>.
239: *
240: * @return a new instance of the specified class.
241: *
242: * @exception BuildException if the class cannot be found or
243: * cannot be appropriate instantiated.
244: */
245: private static ProjectHelper newHelper(String helperClass)
246: throws BuildException {
247: ClassLoader classLoader = LoaderUtils.getContextClassLoader();
248: try {
249: Class clazz = null;
250: if (classLoader != null) {
251: try {
252: clazz = classLoader.loadClass(helperClass);
253: } catch (ClassNotFoundException ex) {
254: // try next method
255: }
256: }
257: if (clazz == null) {
258: clazz = Class.forName(helperClass);
259: }
260: return ((ProjectHelper) clazz.newInstance());
261: } catch (Exception e) {
262: throw new BuildException(e);
263: }
264: }
265:
266: /**
267: * JDK1.1 compatible access to the context class loader.
268: * Cut&paste from JAXP.
269: *
270: * @deprecated since 1.6.x.
271: * Use LoaderUtils.getContextClassLoader()
272: *
273: * @return the current context class loader, or <code>null</code>
274: * if the context class loader is unavailable.
275: */
276: public static ClassLoader getContextClassLoader() {
277: if (!LoaderUtils.isContextLoaderAvailable()) {
278: return null;
279: }
280:
281: return LoaderUtils.getContextClassLoader();
282: }
283:
284: // -------------------- Static utils, used by most helpers ----------------
285:
286: /**
287: * Configures an object using an introspection handler.
288: *
289: * @param target The target object to be configured.
290: * Must not be <code>null</code>.
291: * @param attrs A list of attributes to configure within the target.
292: * Must not be <code>null</code>.
293: * @param project The project containing the target.
294: * Must not be <code>null</code>.
295: *
296: * @deprecated since 1.6.x.
297: * Use IntrospectionHelper for each property.
298: *
299: * @exception BuildException if any of the attributes can't be handled by
300: * the target
301: */
302: public static void configure(Object target, AttributeList attrs,
303: Project project) throws BuildException {
304: if (target instanceof TypeAdapter) {
305: target = ((TypeAdapter) target).getProxy();
306: }
307:
308: IntrospectionHelper ih = IntrospectionHelper.getHelper(project,
309: target.getClass());
310:
311: for (int i = 0; i < attrs.getLength(); i++) {
312: // reflect these into the target
313: String value = replaceProperties(project,
314: attrs.getValue(i), project.getProperties());
315: try {
316: ih.setAttribute(project, target, attrs.getName(i)
317: .toLowerCase(Locale.US), value);
318:
319: } catch (BuildException be) {
320: // id attribute must be set externally
321: if (!attrs.getName(i).equals("id")) {
322: throw be;
323: }
324: }
325: }
326: }
327:
328: /**
329: * Adds the content of #PCDATA sections to an element.
330: *
331: * @param project The project containing the target.
332: * Must not be <code>null</code>.
333: * @param target The target object to be configured.
334: * Must not be <code>null</code>.
335: * @param buf A character array of the text within the element.
336: * Will not be <code>null</code>.
337: * @param start The start element in the array.
338: * @param count The number of characters to read from the array.
339: *
340: * @exception BuildException if the target object doesn't accept text
341: */
342: public static void addText(Project project, Object target,
343: char[] buf, int start, int count) throws BuildException {
344: addText(project, target, new String(buf, start, count));
345: }
346:
347: /**
348: * Adds the content of #PCDATA sections to an element.
349: *
350: * @param project The project containing the target.
351: * Must not be <code>null</code>.
352: * @param target The target object to be configured.
353: * Must not be <code>null</code>.
354: * @param text Text to add to the target.
355: * May be <code>null</code>, in which case this
356: * method call is a no-op.
357: *
358: * @exception BuildException if the target object doesn't accept text
359: */
360: public static void addText(Project project, Object target,
361: String text) throws BuildException {
362:
363: if (text == null) {
364: return;
365: }
366:
367: if (target instanceof TypeAdapter) {
368: target = ((TypeAdapter) target).getProxy();
369: }
370:
371: IntrospectionHelper.getHelper(project, target.getClass())
372: .addText(project, target, text);
373: }
374:
375: /**
376: * Stores a configured child element within its parent object.
377: *
378: * @param project Project containing the objects.
379: * May be <code>null</code>.
380: * @param parent Parent object to add child to.
381: * Must not be <code>null</code>.
382: * @param child Child object to store in parent.
383: * Should not be <code>null</code>.
384: * @param tag Name of element which generated the child.
385: * May be <code>null</code>, in which case
386: * the child is not stored.
387: */
388: public static void storeChild(Project project, Object parent,
389: Object child, String tag) {
390: IntrospectionHelper ih = IntrospectionHelper.getHelper(project,
391: parent.getClass());
392: ih.storeElement(project, parent, child, tag);
393: }
394:
395: /**
396: * Replaces <code>${xxx}</code> style constructions in the given value with
397: * the string value of the corresponding properties.
398: *
399: * @param project The project containing the properties to replace.
400: * Must not be <code>null</code>.
401: *
402: * @param value The string to be scanned for property references.
403: * May be <code>null</code>.
404: *
405: * @exception BuildException if the string contains an opening
406: * <code>${</code> without a closing
407: * <code>}</code>
408: * @return the original string with the properties replaced, or
409: * <code>null</code> if the original string is <code>null</code>.
410: *
411: * @deprecated since 1.6.x.
412: * Use project.replaceProperties().
413: * @since 1.5
414: */
415: public static String replaceProperties(Project project, String value)
416: throws BuildException {
417: // needed since project properties are not accessible
418: return project.replaceProperties(value);
419: }
420:
421: /**
422: * Replaces <code>${xxx}</code> style constructions in the given value
423: * with the string value of the corresponding data types.
424: *
425: * @param project The container project. This is used solely for
426: * logging purposes. Must not be <code>null</code>.
427: * @param value The string to be scanned for property references.
428: * May be <code>null</code>, in which case this
429: * method returns immediately with no effect.
430: * @param keys Mapping (String to String) of property names to their
431: * values. Must not be <code>null</code>.
432: *
433: * @exception BuildException if the string contains an opening
434: * <code>${</code> without a closing
435: * <code>}</code>
436: * @return the original string with the properties replaced, or
437: * <code>null</code> if the original string is <code>null</code>.
438: * @deprecated since 1.6.x.
439: * Use PropertyHelper.
440: */
441: public static String replaceProperties(Project project,
442: String value, Hashtable keys) throws BuildException {
443: PropertyHelper ph = PropertyHelper.getPropertyHelper(project);
444: return ph.replaceProperties(null, value, keys);
445: }
446:
447: /**
448: * Parses a string containing <code>${xxx}</code> style property
449: * references into two lists. The first list is a collection
450: * of text fragments, while the other is a set of string property names.
451: * <code>null</code> entries in the first list indicate a property
452: * reference from the second list.
453: *
454: * @param value Text to parse. Must not be <code>null</code>.
455: * @param fragments List to add text fragments to.
456: * Must not be <code>null</code>.
457: * @param propertyRefs List to add property names to.
458: * Must not be <code>null</code>.
459: *
460: * @deprecated since 1.6.x.
461: * Use PropertyHelper.
462: * @exception BuildException if the string contains an opening
463: * <code>${</code> without a closing
464: * <code>}</code>
465: */
466: public static void parsePropertyString(String value,
467: Vector fragments, Vector propertyRefs)
468: throws BuildException {
469: PropertyHelper.parsePropertyStringDefault(value, fragments,
470: propertyRefs);
471: }
472:
473: /**
474: * Map a namespaced {uri,name} to an internal string format.
475: * For BC purposes the names from the ant core uri will be
476: * mapped to "name", other names will be mapped to
477: * uri + ":" + name.
478: * @param uri The namepace URI
479: * @param name The localname
480: * @return The stringified form of the ns name
481: */
482: public static String genComponentName(String uri, String name) {
483: if (uri == null || uri.equals("") || uri.equals(ANT_CORE_URI)) {
484: return name;
485: }
486: return uri + ":" + name;
487: }
488:
489: /**
490: * extract a uri from a component name
491: *
492: * @param componentName The stringified form for {uri, name}
493: * @return The uri or "" if not present
494: */
495: public static String extractUriFromComponentName(
496: String componentName) {
497: if (componentName == null) {
498: return "";
499: }
500: int index = componentName.lastIndexOf(':');
501: if (index == -1) {
502: return "";
503: }
504: return componentName.substring(0, index);
505: }
506:
507: /**
508: * extract the element name from a component name
509: *
510: * @param componentName The stringified form for {uri, name}
511: * @return The element name of the component
512: */
513: public static String extractNameFromComponentName(
514: String componentName) {
515: int index = componentName.lastIndexOf(':');
516: if (index == -1) {
517: return componentName;
518: }
519: return componentName.substring(index + 1);
520: }
521:
522: /**
523: * Add location to build exception.
524: * @param ex the build exception, if the build exception
525: * does not include
526: * @param newLocation the location of the calling task (may be null)
527: * @return a new build exception based in the build exception with
528: * location set to newLocation. If the original exception
529: * did not have a location, just return the build exception
530: */
531: public static BuildException addLocationToBuildException(
532: BuildException ex, Location newLocation) {
533: if (ex.getLocation() == null || ex.getMessage() == null) {
534: return ex;
535: }
536: String errorMessage = "The following error occurred while executing this line:"
537: + System.getProperty("line.separator")
538: + ex.getLocation().toString() + ex.getMessage();
539: if (newLocation == null) {
540: return new BuildException(errorMessage, ex);
541: } else {
542: return new BuildException(errorMessage, ex, newLocation);
543: }
544: }
545: }
|