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.taskdefs;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.net.URL;
025: import java.util.Map;
026: import java.util.HashMap;
027: import java.util.Enumeration;
028: import java.util.Locale;
029: import java.util.NoSuchElementException;
030: import java.util.Properties;
031:
032: import org.apache.tools.ant.AntTypeDefinition;
033: import org.apache.tools.ant.ComponentHelper;
034: import org.apache.tools.ant.BuildException;
035: import org.apache.tools.ant.Project;
036: import org.apache.tools.ant.ProjectHelper;
037: import org.apache.tools.ant.MagicNames;
038: import org.apache.tools.ant.util.FileUtils;
039: import org.apache.tools.ant.types.EnumeratedAttribute;
040:
041: /**
042: * Base class for Taskdef and Typedef - handles all
043: * the attributes for Typedef. The uri and class
044: * handling is handled by DefBase
045: *
046: * @since Ant 1.4
047: */
048: public abstract class Definer extends DefBase {
049:
050: /**
051: * the extension of an antlib file for autoloading.
052: * {@value[
053: */
054: private static final String ANTLIB_XML = "/antlib.xml";
055:
056: private static class ResourceStack extends ThreadLocal {
057: public Object initialValue() {
058: return new HashMap();
059: }
060:
061: Map getStack() {
062: return (Map) get();
063: }
064: }
065:
066: private static ResourceStack resourceStack = new ResourceStack();
067: private String name;
068: private String classname;
069: private File file;
070: private String resource;
071:
072: private int format = Format.PROPERTIES;
073: private boolean definerSet = false;
074: private int onError = OnError.FAIL;
075: private String adapter;
076: private String adaptTo;
077:
078: private Class adapterClass;
079: private Class adaptToClass;
080:
081: /**
082: * Enumerated type for onError attribute
083: *
084: * @see EnumeratedAttribute
085: */
086: public static class OnError extends EnumeratedAttribute {
087: /** Enumerated values */
088: public static final int FAIL = 0, REPORT = 1, IGNORE = 2,
089: FAIL_ALL = 3;
090:
091: /**
092: * text value of onerror option {@value}
093: */
094: public static final String POLICY_FAIL = "fail";
095: /**
096: * text value of onerror option {@value}
097: */
098: public static final String POLICY_REPORT = "report";
099: /**
100: * text value of onerror option {@value}
101: */
102: public static final String POLICY_IGNORE = "ignore";
103: /**
104: * text value of onerror option {@value}
105: */
106: public static final String POLICY_FAILALL = "failall";
107:
108: /**
109: * Constructor
110: */
111: public OnError() {
112: super ();
113: }
114:
115: /**
116: * Constructor using a string.
117: * @param value the value of the attribute
118: */
119: public OnError(String value) {
120: setValue(value);
121: }
122:
123: /**
124: * get the values
125: * @return an array of the allowed values for this attribute.
126: */
127: public String[] getValues() {
128: return new String[] { POLICY_FAIL, POLICY_REPORT,
129: POLICY_IGNORE, POLICY_FAILALL };
130: }
131: }
132:
133: /**
134: * Enumerated type for format attribute
135: *
136: * @see EnumeratedAttribute
137: */
138: public static class Format extends EnumeratedAttribute {
139: /** Enumerated values */
140: public static final int PROPERTIES = 0, XML = 1;
141:
142: /**
143: * get the values
144: * @return an array of the allowed values for this attribute.
145: */
146: public String[] getValues() {
147: return new String[] { "properties", "xml" };
148: }
149: }
150:
151: /**
152: * What to do if there is an error in loading the class.
153: * <dl>
154: * <li>error - throw build exception</li>
155: * <li>report - output at warning level</li>
156: * <li>ignore - output at debug level</li>
157: * </dl>
158: *
159: * @param onError an <code>OnError</code> value
160: */
161: public void setOnError(OnError onError) {
162: this .onError = onError.getIndex();
163: }
164:
165: /**
166: * Sets the format of the file or resource
167: * @param format the enumerated value - xml or properties
168: */
169: public void setFormat(Format format) {
170: this .format = format.getIndex();
171: }
172:
173: /**
174: * @return the name for this definition
175: */
176: public String getName() {
177: return name;
178: }
179:
180: /**
181: * @return the file containing definitions
182: */
183: public File getFile() {
184: return file;
185: }
186:
187: /**
188: * @return the resource containing definitions
189: */
190: public String getResource() {
191: return resource;
192: }
193:
194: /**
195: * Run the definition.
196: *
197: * @exception BuildException if an error occurs
198: */
199: public void execute() throws BuildException {
200: ClassLoader al = createLoader();
201:
202: if (!definerSet) {
203: //we arent fully defined yet. this is an error unless
204: //we are in an antlib, in which case the resource name is determined
205: //automatically.
206: //NB: URIs in the ant core package will be "" at this point.
207: if (getURI() == null) {
208: throw new BuildException(
209: "name, file or resource attribute of "
210: + getTaskName() + " is undefined",
211: getLocation());
212: }
213:
214: if (getURI().startsWith(MagicNames.ANTLIB_PREFIX)) {
215: //convert the URI to a resource
216: String uri1 = getURI();
217: setResource(makeResourceFromURI(uri1));
218: } else {
219: throw new BuildException(
220: "Only antlib URIs can be located from the URI alone,"
221: + "not the URI " + getURI());
222: }
223: }
224:
225: if (name != null) {
226: if (classname == null) {
227: throw new BuildException("classname attribute of "
228: + getTaskName() + " element " + "is undefined",
229: getLocation());
230: }
231: addDefinition(al, name, classname);
232: } else {
233: if (classname != null) {
234: String msg = "You must not specify classname "
235: + "together with file or resource.";
236: throw new BuildException(msg, getLocation());
237: }
238: Enumeration/*<URL>*/urls = null;
239: if (file != null) {
240: final URL url = fileToURL();
241: if (url == null) {
242: return;
243: }
244: urls = new Enumeration() {
245: private boolean more = true;
246:
247: public boolean hasMoreElements() {
248: return more;
249: }
250:
251: public Object nextElement()
252: throws NoSuchElementException {
253: if (more) {
254: more = false;
255: return url;
256: } else {
257: throw new NoSuchElementException();
258: }
259: }
260: };
261: } else {
262: urls = resourceToURLs(al);
263: }
264:
265: while (urls.hasMoreElements()) {
266: URL url = (URL) urls.nextElement();
267:
268: int fmt = this .format;
269: if (url.toString().toLowerCase(Locale.US).endsWith(
270: ".xml")) {
271: fmt = Format.XML;
272: }
273:
274: if (fmt == Format.PROPERTIES) {
275: loadProperties(al, url);
276: break;
277: } else {
278: if (resourceStack.getStack().get(url) != null) {
279: log("Warning: Recursive loading of " + url
280: + " ignored" + " at " + getLocation()
281: + " originally loaded at "
282: + resourceStack.getStack().get(url),
283: Project.MSG_WARN);
284: } else {
285: try {
286: resourceStack.getStack().put(url,
287: getLocation());
288: loadAntlib(al, url);
289: } finally {
290: resourceStack.getStack().remove(url);
291: }
292: }
293: }
294: }
295: }
296: }
297:
298: /**
299: * This is where the logic to map from a URI to an antlib resource
300: * is kept.
301: * @param uri the xml namespace uri that to convert.
302: * @return the name of a resource. It may not exist
303: */
304:
305: public static String makeResourceFromURI(String uri) {
306: String path = uri.substring(MagicNames.ANTLIB_PREFIX.length());
307: String resource;
308: if (path.startsWith("//")) {
309: //handle new style full paths to an antlib, in which
310: //all but the forward slashes are allowed.
311: resource = path.substring("//".length());
312: if (!resource.endsWith(".xml")) {
313: //if we haven't already named an XML file, it gets antlib.xml
314: resource = resource + ANTLIB_XML;
315: }
316: } else {
317: //convert from a package to a path
318: resource = path.replace('.', '/') + ANTLIB_XML;
319: }
320: return resource;
321: }
322:
323: /**
324: * Convert a file to a file: URL.
325: *
326: * @return the URL, or null if it isn't valid and the active error policy
327: * is not to raise a fault
328: * @throws BuildException if the file is missing/not a file and the
329: * policy requires failure at this point.
330: */
331: private URL fileToURL() {
332: String message = null;
333: if (!(file.exists())) {
334: message = "File " + file + " does not exist";
335: }
336: if (message == null && !(file.isFile())) {
337: message = "File " + file + " is not a file";
338: }
339: try {
340: if (message == null) {
341: return file.toURL();
342: }
343: } catch (Exception ex) {
344: message = "File " + file + " cannot use as URL: "
345: + ex.toString();
346: }
347: // Here if there is an error
348: switch (onError) {
349: case OnError.FAIL_ALL:
350: throw new BuildException(message);
351: case OnError.FAIL:
352: // Fall Through
353: case OnError.REPORT:
354: log(message, Project.MSG_WARN);
355: break;
356: case OnError.IGNORE:
357: // log at a lower level
358: log(message, Project.MSG_VERBOSE);
359: break;
360: default:
361: // Ignore the problem
362: break;
363: }
364: return null;
365: }
366:
367: private Enumeration/*<URL>*/resourceToURLs(ClassLoader classLoader) {
368: Enumeration ret;
369: try {
370: ret = classLoader.getResources(resource);
371: } catch (IOException e) {
372: throw new BuildException("Could not fetch resources named "
373: + resource, e, getLocation());
374: }
375: if (!ret.hasMoreElements()) {
376: String message = "Could not load definitions from resource "
377: + resource + ". It could not be found.";
378: switch (onError) {
379: case OnError.FAIL_ALL:
380: throw new BuildException(message);
381: case OnError.FAIL:
382: case OnError.REPORT:
383: log(message, Project.MSG_WARN);
384: break;
385: case OnError.IGNORE:
386: log(message, Project.MSG_VERBOSE);
387: break;
388: default:
389: // Ignore the problem
390: break;
391: }
392: }
393: return ret;
394: }
395:
396: /**
397: * Load type definitions as properties from a URL.
398: *
399: * @param al the classloader to use
400: * @param url the url to get the definitions from
401: */
402: protected void loadProperties(ClassLoader al, URL url) {
403: InputStream is = null;
404: try {
405: is = url.openStream();
406: if (is == null) {
407: log("Could not load definitions from " + url,
408: Project.MSG_WARN);
409: return;
410: }
411: Properties props = new Properties();
412: props.load(is);
413: Enumeration keys = props.keys();
414: while (keys.hasMoreElements()) {
415: name = ((String) keys.nextElement());
416: classname = props.getProperty(name);
417: addDefinition(al, name, classname);
418: }
419: } catch (IOException ex) {
420: throw new BuildException(ex, getLocation());
421: } finally {
422: FileUtils.close(is);
423: }
424: }
425:
426: /**
427: * Load an antlib from a URL.
428: *
429: * @param classLoader the classloader to use.
430: * @param url the url to load the definitions from.
431: */
432: private void loadAntlib(ClassLoader classLoader, URL url) {
433: try {
434: Antlib antlib = Antlib.createAntlib(getProject(), url,
435: getURI());
436: antlib.setClassLoader(classLoader);
437: antlib.setURI(getURI());
438: antlib.execute();
439: } catch (BuildException ex) {
440: throw ProjectHelper.addLocationToBuildException(ex,
441: getLocation());
442: }
443: }
444:
445: /**
446: * Name of the property file to load
447: * ant name/classname pairs from.
448: * @param file the file
449: */
450: public void setFile(File file) {
451: if (definerSet) {
452: tooManyDefinitions();
453: }
454: definerSet = true;
455: this .file = file;
456: }
457:
458: /**
459: * Name of the property resource to load
460: * ant name/classname pairs from.
461: * @param res the resource to use
462: */
463: public void setResource(String res) {
464: if (definerSet) {
465: tooManyDefinitions();
466: }
467: definerSet = true;
468: this .resource = res;
469: }
470:
471: /**
472: * Antlib attribute, sets resource and uri.
473: * uri is set the antlib value and, resource is set
474: * to the antlib.xml resource in the classpath.
475: * For example antlib="antlib:org.acme.bland.cola"
476: * corresponds to uri="antlib:org.acme.bland.cola"
477: * resource="org/acme/bland/cola/antlib.xml".
478: * ASF Bugzilla Bug 31999
479: * @param antlib the value to set.
480: */
481: public void setAntlib(String antlib) {
482: if (definerSet) {
483: tooManyDefinitions();
484: }
485: if (!antlib.startsWith("antlib:")) {
486: throw new BuildException(
487: "Invalid antlib attribute - it must start with antlib:");
488: }
489: setURI(antlib);
490: this .resource = antlib.substring("antlib:".length()).replace(
491: '.', '/')
492: + "/antlib.xml";
493: definerSet = true;
494: }
495:
496: /**
497: * Name of the definition
498: * @param name the name of the definition
499: */
500: public void setName(String name) {
501: if (definerSet) {
502: tooManyDefinitions();
503: }
504: definerSet = true;
505: this .name = name;
506: }
507:
508: /**
509: * Returns the classname of the object we are defining.
510: * May be <code>null</code>.
511: * @return the class name
512: */
513: public String getClassname() {
514: return classname;
515: }
516:
517: /**
518: * The full class name of the object being defined.
519: * Required, unless file or resource have
520: * been specified.
521: * @param classname the name of the class
522: */
523: public void setClassname(String classname) {
524: this .classname = classname;
525: }
526:
527: /**
528: * Set the class name of the adapter class.
529: * An adapter class is used to proxy the
530: * definition class. It is used if the
531: * definition class is not assignable to
532: * the adaptto class, or if the adaptto
533: * class is not present.
534: *
535: * @param adapter the name of the adapter class
536: */
537:
538: public void setAdapter(String adapter) {
539: this .adapter = adapter;
540: }
541:
542: /**
543: * Set the adapter class.
544: *
545: * @param adapterClass the class to use to adapt the definition class
546: */
547: protected void setAdapterClass(Class adapterClass) {
548: this .adapterClass = adapterClass;
549: }
550:
551: /**
552: * Set the classname of the class that the definition
553: * must be compatible with, either directly or
554: * by use of the adapter class.
555: *
556: * @param adaptTo the name of the adaptto class
557: */
558: public void setAdaptTo(String adaptTo) {
559: this .adaptTo = adaptTo;
560: }
561:
562: /**
563: * Set the class for adaptToClass, to be
564: * used by derived classes, used instead of
565: * the adaptTo attribute.
566: *
567: * @param adaptToClass the class for adapto.
568: */
569: protected void setAdaptToClass(Class adaptToClass) {
570: this .adaptToClass = adaptToClass;
571: }
572:
573: /**
574: * Add a definition using the attributes of Definer
575: *
576: * @param al the ClassLoader to use
577: * @param name the name of the definition
578: * @param classname the classname of the definition
579: * @exception BuildException if an error occurs
580: */
581: protected void addDefinition(ClassLoader al, String name,
582: String classname) throws BuildException {
583: Class cl = null;
584: try {
585: try {
586: name = ProjectHelper.genComponentName(getURI(), name);
587:
588: if (onError != OnError.IGNORE) {
589: cl = Class.forName(classname, true, al);
590: }
591:
592: if (adapter != null) {
593: adapterClass = Class.forName(adapter, true, al);
594: }
595:
596: if (adaptTo != null) {
597: adaptToClass = Class.forName(adaptTo, true, al);
598: }
599:
600: AntTypeDefinition def = new AntTypeDefinition();
601: def.setName(name);
602: def.setClassName(classname);
603: def.setClass(cl);
604: def.setAdapterClass(adapterClass);
605: def.setAdaptToClass(adaptToClass);
606: def.setClassLoader(al);
607: if (cl != null) {
608: def.checkClass(getProject());
609: }
610: ComponentHelper.getComponentHelper(getProject())
611: .addDataTypeDefinition(def);
612: } catch (ClassNotFoundException cnfe) {
613: String msg = getTaskName() + " class " + classname
614: + " cannot be found";
615: throw new BuildException(msg, cnfe, getLocation());
616: } catch (NoClassDefFoundError ncdfe) {
617: String msg = getTaskName()
618: + " A class needed by class " + classname
619: + " cannot be found: " + ncdfe.getMessage();
620: throw new BuildException(msg, ncdfe, getLocation());
621: }
622: } catch (BuildException ex) {
623: switch (onError) {
624: case OnError.FAIL_ALL:
625: case OnError.FAIL:
626: throw ex;
627: case OnError.REPORT:
628: log(ex.getLocation() + "Warning: " + ex.getMessage(),
629: Project.MSG_WARN);
630: break;
631: default:
632: log(ex.getLocation() + ex.getMessage(),
633: Project.MSG_DEBUG);
634: }
635: }
636: }
637:
638: /**
639: * handle too many definitions by raising an exception.
640: * @throws BuildException always.
641: */
642: private void tooManyDefinitions() {
643: throw new BuildException(
644: "Only one of the attributes name, file and resource"
645: + " can be set", getLocation());
646: }
647: }
|