001    /**
002     * Copyright (c) 2001, Thai Open Source Software Center Ltd
003     * All rights reserved.
004     * 
005     * Redistribution and use in source and binary forms, with or without
006     * modification, are permitted provided that the following conditions are
007     * met:
008     * 
009     *     Redistributions of source code must retain the above copyright
010     *     notice, this list of conditions and the following disclaimer.
011     * 
012     *     Redistributions in binary form must reproduce the above copyright
013     *     notice, this list of conditions and the following disclaimer in
014     *     the documentation and/or other materials provided with the
015     *     distribution.
016     * 
017     *     Neither the name of the Thai Open Source Software Center Ltd nor
018     *     the names of its contributors may be used to endorse or promote
019     *     products derived from this software without specific prior written
020     *     permission.
021     * 
022     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
023     * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
024     * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
025     * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
026     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
027     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
028     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
029     * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
030     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
031     * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
032     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
033     */
034    package org.relaxng.datatype.helpers;
035    
036    import org.relaxng.datatype.DatatypeLibraryFactory;
037    import org.relaxng.datatype.DatatypeLibrary;
038    import java.util.Enumeration;
039    import java.util.NoSuchElementException;
040    import java.util.Vector;
041    import java.io.Reader;
042    import java.io.InputStream;
043    import java.io.InputStreamReader;
044    import java.io.BufferedReader;
045    import java.io.IOException;
046    import java.io.UnsupportedEncodingException;
047    import java.net.URL;
048    
049    /**
050     * Discovers the datatype library implementation from the classpath.
051     * 
052     * <p>
053     * The call of the createDatatypeLibrary method finds an implementation
054     * from a given datatype library URI at run-time.
055     */
056    public class DatatypeLibraryLoader implements DatatypeLibraryFactory {
057      private final Service service = new Service(DatatypeLibraryFactory.class);
058    
059      public DatatypeLibrary createDatatypeLibrary(String uri) {
060        for (Enumeration e = service.getProviders();
061             e.hasMoreElements();) {
062          DatatypeLibraryFactory factory
063            = (DatatypeLibraryFactory)e.nextElement();
064          DatatypeLibrary library = factory.createDatatypeLibrary(uri);
065          if (library != null)
066            return library;
067        }
068        return null;
069      }
070    
071            private static class Service {
072              private final Class serviceClass;
073              private final Enumeration configFiles;
074              private Enumeration classNames = null;
075              private final Vector providers = new Vector();
076              private Loader loader;
077    
078              private class ProviderEnumeration implements Enumeration {
079                private int nextIndex = 0;
080    
081                public boolean hasMoreElements() {
082                  return nextIndex < providers.size() || moreProviders();
083                }
084    
085                public Object nextElement() {
086                  try {
087                    return providers.elementAt(nextIndex++);
088                  }
089                  catch (ArrayIndexOutOfBoundsException e) {
090                    throw new NoSuchElementException();
091                  }
092                }
093              }
094    
095              private static class Singleton implements Enumeration {
096                private Object obj;
097                private Singleton(Object obj) {
098                  this.obj = obj;
099                }
100    
101                public boolean hasMoreElements() {
102                  return obj != null;
103                }
104    
105                public Object nextElement() {
106                  if (obj == null)
107                    throw new NoSuchElementException();
108                  Object tem = obj;
109                  obj = null;
110                  return tem;
111                }
112              }
113    
114              // JDK 1.1
115              private static class Loader {
116                Enumeration getResources(String resName) {
117                  ClassLoader cl = Loader.class.getClassLoader();
118                  URL url;
119                  if (cl == null)
120                    url = ClassLoader.getSystemResource(resName);
121                  else
122                    url = cl.getResource(resName);
123                  return new Singleton(url);
124                }
125    
126                Class loadClass(String name) throws ClassNotFoundException {
127                  return Class.forName(name);
128                }
129              }
130    
131              // JDK 1.2+
132              private static class Loader2 extends Loader {
133                private ClassLoader cl;
134    
135                Loader2() {
136                  cl = Loader2.class.getClassLoader();
137                  // If the thread context class loader has the class loader
138                  // of this class as an ancestor, use the thread context class
139                  // loader.  Otherwise, the thread context class loader
140                  // probably hasn't been set up properly, so don't use it.
141                  ClassLoader clt = Thread.currentThread().getContextClassLoader();
142                  for (ClassLoader tem = clt; tem != null; tem = tem.getParent())
143                    if (tem == cl) {
144                      cl = clt;
145                      break;
146                    }
147                }
148    
149                Enumeration getResources(String resName) {
150                  try {
151                    Enumeration resources = cl.getResources(resName);
152                    if (resources.hasMoreElements())
153                      return resources;
154                    // Some application servers apparently do not implement findResources
155                    // in their class loaders, so fall back to getResource.
156                    return new Singleton(cl.getResource(resName));
157                  }
158                  catch (IOException e) {
159                    return new Singleton(null);
160                  }
161                }
162    
163                Class loadClass(String name) throws ClassNotFoundException {
164                  return Class.forName(name, true, cl);
165                }
166              }
167    
168              public Service(Class cls) {
169                try {
170                  loader = new Loader2();
171                }
172                catch (NoSuchMethodError e) {
173                  loader = new Loader();
174                }
175                serviceClass = cls;
176                String resName = "META-INF/services/" + serviceClass.getName();
177                configFiles = loader.getResources(resName);
178              }
179    
180              public Enumeration getProviders() {
181                return new ProviderEnumeration();
182              }
183    
184              synchronized private boolean moreProviders() {
185                for (;;) {
186                  while (classNames == null) {
187                    if (!configFiles.hasMoreElements())
188                      return false;
189                    classNames = parseConfigFile((URL)configFiles.nextElement());
190                  }
191                  while (classNames.hasMoreElements()) {
192                    String className = (String)classNames.nextElement();
193                    try {
194                      Class cls = loader.loadClass(className);
195                      Object obj = cls.newInstance();
196                      if (serviceClass.isInstance(obj)) {
197                        providers.addElement(obj);
198                        return true;
199                      }
200                    }
201                    catch (ClassNotFoundException e) { }
202                    catch (InstantiationException e) { }
203                    catch (IllegalAccessException e) { }
204                    catch (LinkageError e) { }
205                  }
206                  classNames = null;
207                }
208              }
209    
210              private static final int START = 0;
211              private static final int IN_NAME = 1;
212              private static final int IN_COMMENT = 2;
213    
214              private static Enumeration parseConfigFile(URL url) {
215                try {
216                  InputStream in = url.openStream();
217                  Reader r;
218                  try {
219                    r = new InputStreamReader(in, "UTF-8");
220                  }
221                  catch (UnsupportedEncodingException e) {
222                    r = new InputStreamReader(in, "UTF8");
223                  }
224                  r = new BufferedReader(r);
225                  Vector tokens = new Vector();
226                  StringBuffer tokenBuf = new StringBuffer();
227                  int state = START;
228                  for (;;) {
229                    int n = r.read();
230                    if (n < 0)
231                      break;
232                    char c = (char)n;
233                    switch (c) {
234                    case '\r':
235                    case '\n':
236                      state = START;
237                      break;
238                    case ' ':
239                    case '\t':
240                      break;
241                    case '#':
242                      state = IN_COMMENT;
243                      break;
244                    default:
245                      if (state != IN_COMMENT) {
246                        state = IN_NAME;
247                        tokenBuf.append(c);
248                      }
249                      break;
250                    }
251                    if (tokenBuf.length() != 0 && state != IN_NAME) {
252                      tokens.addElement(tokenBuf.toString());
253                      tokenBuf.setLength(0);
254                    }
255                  }
256                  if (tokenBuf.length() != 0)
257                    tokens.addElement(tokenBuf.toString());
258                  return tokens.elements();
259                }
260                catch (IOException e) {
261                  return null;
262                }
263              }
264            }
265      
266    }
267