001: /*
002: * @(#)SPILoader.java
003: *
004: * Copyright (C) 2002-2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Part of the GroboUtils package at:
009: * http://groboutils.sourceforge.net
010: *
011: * Permission is hereby granted, free of charge, to any person obtaining a
012: * copy of this software and associated documentation files (the "Software"),
013: * to deal in the Software without restriction, including without limitation
014: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
015: * and/or sell copies of the Software, and to permit persons to whom the
016: * Software is furnished to do so, subject to the following conditions:
017: *
018: * The above copyright notice and this permission notice shall be included in
019: * all copies or substantial portions of the Software.
020: *
021: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
022: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
023: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
024: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
025: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
026: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
027: * DEALINGS IN THE SOFTWARE.
028: */
029: package net.sourceforge.groboutils.util.classes.v1;
030:
031: import java.util.Properties;
032: import java.util.Enumeration;
033: import java.util.NoSuchElementException;
034:
035: import java.net.URL;
036:
037: import java.io.IOException;
038: import java.io.InputStream;
039:
040: import org.apache.log4j.Logger;
041:
042: /**
043: * Loads Service Provider Interface (SPI) classes from the given classloader
044: * (if any). This will search for files in the form
045: * <tt>/META-INF/services/<i>classname</i></tt>. The discovered file will be
046: * parsed using <tt>java.util.Properties</tt> parsing method, but only the
047: * keys will be recognized (note that if a line contains no ':' or '=', then
048: * the line will be recognized as a key with an empty-string value). The found
049: * keys will be used as class names, constructed, and returned, using the
050: * <tt>ClassLoadHelper</tt> class in this package.
051: * <P>
052: * This is intended to follow the SPI interface spec, partially described in
053: * the JDK 1.4 documentation in the package docs for <tt>java.awt.im.spi</tt>.
054: * <P>
055: * Note that by using a Properties instance, this class is limited to only
056: * one instance of a class type per META-INF service file. This is assumed to
057: * be an adequate restriction, since in most circumstances, there should only
058: * be one instance of a service provider loaded per need.
059: *
060: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
061: * @since June 28, 2002
062: * @version $Date: 2003/05/08 14:12:21 $
063: */
064: public class SPILoader {
065: private static final Logger LOG = Logger.getLogger(SPILoader.class
066: .getName());
067:
068: /**
069: * List of all resource URLs to the class's SPI files.
070: */
071: private Enumeration spiUrls;
072:
073: /**
074: * Current SPI url's property (classname) keys.
075: */
076: private Enumeration propKeys = null;
077:
078: private String nextKey = null;
079:
080: private Class base;
081:
082: private ClassLoadHelper clh;
083:
084: /**
085: * Use the context (thread) classloader or the system class loader.
086: *
087: * @param spiBase the base class that all loaded SPI classes must
088: * implement. This is also used to define the name of the files
089: * to load from the META-INF directory.
090: * @exception IOException if there is an I/O problem loading the
091: * initial set of resources.
092: */
093: public SPILoader(Class spiBase) throws IOException {
094: this (spiBase, null);
095: }
096:
097: /**
098: * Create a new SPILoader, loading the service files from the given
099: * class loader classpath. If <tt>cl</tt> is <tt>null</tt>, then the
100: * context (thread) class loader or system class loader will be used
101: * instead.
102: *
103: * @param spiBase the base class that all loaded SPI classes must
104: * implement. This is also used to define the name of the files
105: * to load from the META-INF directory.
106: * @param cl classloader to load files from.
107: * @exception IOException if there is an I/O problem loading the
108: * initial set of resources.
109: */
110: public SPILoader(Class spiBase, ClassLoader cl) throws IOException {
111: if (spiBase == null) {
112: throw new IllegalArgumentException(
113: "spiBase cannot be null.");
114: }
115:
116: if (cl == null) {
117: this .clh = new ClassLoadHelper(spiBase);
118: } else {
119: this .clh = new ClassLoadHelper(cl);
120: }
121:
122: String metaName = "/META-INF/services/" + spiBase.getName();
123:
124: this .spiUrls = this .clh.getResources(metaName);
125: if (this .spiUrls == null) {
126: throw new IOException("No URLs were discovered.");
127: }
128: this .base = spiBase;
129: }
130:
131: /**
132: * Discovers if there is another provider class instance to load.
133: *
134: * @return <tt>true</tt> if there is another provider, otherwise
135: * <tt>false</tt>.
136: */
137: public boolean hasNext() throws IOException {
138: while (this .nextKey == null) {
139: if (this .propKeys == null
140: || !this .propKeys.hasMoreElements()) {
141: if (!this .spiUrls.hasMoreElements()) {
142: LOG.debug("No more URLs to process");
143: return false;
144: }
145: URL url = (URL) this .spiUrls.nextElement();
146: LOG.debug("Processing next URL: " + url);
147: Properties props = new Properties();
148: LOG.debug("Opening URL stream");
149: InputStream is = url.openStream();
150: try {
151: props.load(is);
152: } finally {
153: is.close();
154: }
155: this .propKeys = props.keys();
156: LOG.debug("URL contains " + props.size()
157: + " class names");
158: } else {
159: this .nextKey = (String) this .propKeys.nextElement();
160: LOG.debug("Next classname is " + this .nextKey);
161: }
162: }
163: return true;
164: }
165:
166: /**
167: * Returns a new instance of the next class name. If the defined
168: * next class is invalid, then an IOException is thrown. The returned
169: * object is guaranteed to be non-null and an instance of the constructor
170: * baseClass.
171: *
172: * @return the next provider in the list of discovered SPI providers.
173: * @exception IOException if there was an I/O error, or if the referenced
174: * resource defines an invalid class to load.
175: * @exception NoSuchElementException if the enumeration is already at
176: * the end of the list.
177: */
178: public Object nextProvier() throws IOException {
179: // ensure we have a next key to go to
180: hasNext();
181:
182: if (this .nextKey == null) {
183: // end of list
184: throw new NoSuchElementException("end of list");
185: }
186: String cn = this .nextKey;
187:
188: // ensure we advance to the next key next iteration.
189: this .nextKey = null;
190:
191: Object o;
192: try {
193: o = this .clh.createObject(cn);
194: } catch (IllegalStateException ise) {
195: LOG.info("create object for type " + cn
196: + " threw exception.", ise);
197: throw new IOException(ise.getMessage());
198: }
199:
200: if (o == null) {
201: LOG
202: .info("create object for type " + cn
203: + " returned null.");
204: throw new IOException(
205: "Could not create an instance of type " + cn);
206: }
207:
208: if (!this .base.isInstance(o)) {
209: throw new IOException("SPI defined class " + cn
210: + ", but expected class of type "
211: + this.base.getName());
212: }
213: return o;
214: }
215: }
|