001: /* ClassLocator.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Tue Aug 30 09:56:06 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.util.resource;
020:
021: import java.util.Enumeration;
022: import java.util.LinkedHashMap;
023: import java.util.Map;
024: import java.util.LinkedHashSet;
025: import java.util.Set;
026: import java.util.LinkedList;
027: import java.util.List;
028: import java.util.Collections;
029: import java.util.Iterator;
030: import java.io.InputStream;
031: import java.io.IOException;
032: import java.net.URL;
033:
034: import org.zkoss.lang.D;
035: import org.zkoss.util.CollectionsX;
036: import org.zkoss.util.logging.Log;
037: import org.zkoss.idom.Document;
038: import org.zkoss.idom.Element;
039: import org.zkoss.idom.util.IDOMs;
040: import org.zkoss.idom.input.SAXBuilder;
041:
042: /**
043: * The locator searches the current thread's context class loader,
044: * and then this class's class loader.
045: *
046: * <p>It is important to use this locator if you want to load something
047: * in other jar files.
048: *
049: * <p>Besides {@link Locator}, it also provides additional methods,
050: * such as {@link #getResources}.
051: *
052: * <p>Since this locator is used frequently, {@link Locators#getDefault}
053: * is provided to return an instance of this class,
054: *
055: * @author tomyeh
056: */
057: public class ClassLocator implements Locator {
058: private static final Log log = Log.lookup(ClassLocator.class);
059:
060: public ClassLocator() {
061: }
062:
063: /** Returns an enumeration of resources.
064: * Unlike {@link #getDependentXMLResources}, it doesn't resolve the dependence
065: * among the resouces.
066: *
067: * @param name the resouce name, such as "metainfo/i3-com.xml".
068: */
069: public Enumeration getResources(String name) throws IOException {
070: name = resolveName(name);
071: ClassLoader cl = Thread.currentThread().getContextClassLoader();
072: if (cl != null) {
073: final Enumeration en = cl.getResources(name);
074: if (en.hasMoreElements())
075: return en;
076: }
077: cl = ClassLocator.class.getClassLoader();
078: if (cl != null) {
079: final Enumeration en = cl.getResources(name);
080: if (en.hasMoreElements())
081: return en;
082: }
083: return ClassLoader.getSystemResources(name);
084: }
085:
086: /** Returns a list of resources ({@link Resource}) after resolving
087: * the dependence.
088: * The resource is returned in the format of {@link Resource}
089: *
090: * <p>To resolve the dependence, it assumes each resource has two
091: * element whose name is identified by elName and elDepends.
092: * The elName element specifies the unique name of each resource.
093: * The elDepends element specifies a list of names of resources
094: * that this resource depends on. If not found, it assumes it could
095: * be loaded first.
096: *
097: * @param name the resouce name, such as "metainfo/i3-comp.xml".
098: * @param elName the element used to specify the name.
099: * @param elDepends the element used to specify the dependence.
100: * @return a list of {@link Resource} of the specified name.
101: */
102: public List getDependentXMLResources(String name, String elName,
103: String elDepends) throws IOException {
104: final Map rcmap = new LinkedHashMap();
105: for (Enumeration en = getResources(name); en.hasMoreElements();) {
106: final URL url = (URL) en.nextElement();
107: final XMLResource xr = new XMLResource(url, elName,
108: elDepends);
109: final XMLResource old = (XMLResource) rcmap
110: .put(xr.name, xr);
111: if (old != null)
112: log
113: .warning("Replicate resource: " + xr.name
114: + "\nOverwrite " + old.url + "\nwith "
115: + xr.url);
116: //it is possible if zcommon.jar is placed in both
117: //WEB-INF/lib and shared/lib, i.e., appear twice in the class path
118: //We overwrite because the order is the parent class loader first
119: //so WEB-INF/lib is placed after
120: }
121: if (D.ON && rcmap.isEmpty() && log.debugable())
122: log.debug("No resouce is found for " + name);
123:
124: final List rcs = new LinkedList(); //a list of Document
125: final Set resolving = new LinkedHashSet();
126: //a set of names used to prevent dead-loop
127: while (!rcmap.isEmpty()) {
128: final Iterator it = rcmap.values().iterator();
129: final XMLResource xr = (XMLResource) it.next();
130: it.remove();
131: resolveDependency(xr, rcs, rcmap, resolving);
132: assert D.OFF || resolving.isEmpty();
133: }
134: return rcs;
135: }
136:
137: private static void resolveDependency(XMLResource xr, List rcs,
138: Map rcmap, Set resolving) {
139: if (!resolving.add(xr.name))
140: throw new IllegalStateException(
141: "Recusrive reference among " + resolving);
142:
143: for (Iterator it = xr.depends.iterator(); it.hasNext();) {
144: final String nm = (String) it.next();
145: final XMLResource dep = (XMLResource) rcmap.remove(nm);
146: if (dep != null) //not resolved yet
147: resolveDependency(dep, rcs, rcmap, resolving); //recusrively
148: }
149:
150: rcs.add(new Resource(xr.url, xr.document));
151: resolving.remove(xr.name);
152:
153: if (D.ON && log.debugable())
154: log.debug("Adding resolved resource: " + xr.name);
155: }
156:
157: /** Info used with getDependentXMLResource. */
158: private static class XMLResource {
159: private final String name;
160: private final URL url;
161: private final Document document;
162: private final List depends;
163:
164: private XMLResource(URL url, String elName, String elDepends)
165: throws IOException {
166: if (D.ON && log.debugable())
167: log.debug("Loading " + url);
168: try {
169: this .document = new SAXBuilder(false, false, true)
170: .build(url);
171: } catch (Exception ex) {
172: if (ex instanceof IOException)
173: throw (IOException) ex;
174: if (ex instanceof RuntimeException)
175: throw (RuntimeException) ex;
176: final IOException ioex = new IOException(
177: "Unable to load " + url);
178: ioex.initCause(ex);
179: throw ioex;
180: }
181:
182: this .url = url;
183: final Element root = this .document.getRootElement();
184: this .name = IDOMs.getRequiredElementValue(root, elName);
185: final String deps = root.getElementValue(elDepends, true);
186: if (deps == null || deps.length() == 0) {
187: this .depends = Collections.EMPTY_LIST;
188: } else {
189: this .depends = new LinkedList();
190: CollectionsX.parse(this .depends, deps, ',');
191: if (D.ON && log.finerable())
192: log
193: .finer(this .name + " depends on "
194: + this .depends);
195: }
196: }
197:
198: public String toString() {
199: return "[" + name + ": " + url + " depends on " + depends
200: + ']';
201: }
202: };
203:
204: /** An item of the list returned by {@link ClassLocator#getDependentXMLResources}.
205: */
206: public static class Resource {
207: /** The URL of the resource. */
208: public final URL url;
209: /** The content of the resource. */
210: public final Document document;
211:
212: private Resource(URL url, Document document) {
213: this .url = url;
214: this .document = document;
215: }
216:
217: public String toString() {
218: return "[res: " + url + ']';
219: }
220: }
221:
222: //-- Locator --//
223: /** Always returns null.
224: */
225: public String getDirectory() {
226: return null;
227: }
228:
229: public URL getResource(String name) {
230: final ClassLoader cl = Thread.currentThread()
231: .getContextClassLoader();
232: final URL url = cl != null ? cl.getResource(resolveName(name))
233: : null;
234: return url != null ? url : ClassLocator.class.getResource(name);
235: }
236:
237: public InputStream getResourceAsStream(String name) {
238: final ClassLoader cl = Thread.currentThread()
239: .getContextClassLoader();
240: final InputStream is = cl != null ? cl
241: .getResourceAsStream(resolveName(name)) : null;
242: return is != null ? is : ClassLocator.class
243: .getResourceAsStream(name);
244: }
245:
246: private static String resolveName(String name) {
247: return name != null && name.startsWith("/") ? name.substring(1)
248: : name;
249: }
250:
251: //-- Object --//
252: public int hashCode() {
253: return 1123;
254: }
255:
256: public boolean equals(Object o) {
257: return o instanceof ClassLocator;
258: }
259: }
|