Added
Link Here
|
1 |
/* |
2 |
* Sun Public License Notice |
3 |
* |
4 |
* The contents of this file are subject to the Sun Public License |
5 |
* Version 1.0 (the "License"). You may not use this file except in |
6 |
* compliance with the License. A copy of the License is available at |
7 |
* http://www.sun.com/ |
8 |
* |
9 |
* The Original Code is NetBeans. The Initial Developer of the Original |
10 |
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun |
11 |
* Microsystems, Inc. All Rights Reserved. |
12 |
*/ |
13 |
|
14 |
package org.netbeans; |
15 |
|
16 |
import java.util.*; |
17 |
import java.net.URL; |
18 |
import java.io.IOException; |
19 |
|
20 |
/** |
21 |
* A class loader that has multiple parents and uses them for loading |
22 |
* classes and resources. It can be used in tree hierarchy, where it |
23 |
* can exploit its capability to not throw ClassNotFoundException when |
24 |
* communicating with other ProxyClassLoader. |
25 |
* It itself doesn't load classes or resources, but allows subclasses |
26 |
* to add such functionality. |
27 |
* |
28 |
* @author Petr Nejedly, Jesse Glick |
29 |
*/ |
30 |
public class ProxyClassLoader extends ClassLoader { |
31 |
/** empty enumeration */ |
32 |
private static final Enumeration EMPTY = new ArrayEnumeration (new Object[0]); |
33 |
|
34 |
|
35 |
// Map<String,ClassLoader> |
36 |
// packages are given in format "org/netbeans/modules/foo/" |
37 |
private final Map domainsByPackage = new HashMap(); |
38 |
// Map<String,Package> |
39 |
private HashMap packages = new HashMap(); |
40 |
|
41 |
// All parentf of this classloader, including their parents recursively |
42 |
private ClassLoader[] parents; |
43 |
|
44 |
/** if true, we have been destroyed */ |
45 |
private boolean dead = false; |
46 |
|
47 |
/** Create a multi-parented classloader. |
48 |
* @param parents list of parent classloaders. |
49 |
* @throws IllegalArgumentException if there are any nulls or duplicate |
50 |
* parent loaders or cycles. |
51 |
*/ |
52 |
public ProxyClassLoader( ClassLoader[] parents ) { |
53 |
if (parents.length == 0) { |
54 |
throw new IllegalArgumentException ("ProxyClassLoader must have a parent"); // NOI18N |
55 |
} |
56 |
|
57 |
Set check = new HashSet(Arrays.asList(parents)); // Set<ClassLoader> |
58 |
if (check.size() < parents.length) throw new IllegalArgumentException("duplicate parents"); // NOI18N |
59 |
if (check.contains(null)) throw new IllegalArgumentException("null parent"); // NOI18N |
60 |
|
61 |
this.parents = coalesceParents(parents); |
62 |
} |
63 |
|
64 |
// this is used only by system classloader, maybe we can redesign it a bit |
65 |
// to live without this functionality, then destroy may also go away |
66 |
/** Add new parents dynamically. |
67 |
* @param parents the new parents to add (append to list) |
68 |
* @throws IllegalArgumentException in case of a null or cyclic parent (duplicate OK) |
69 |
*/ |
70 |
public synchronized void append(ClassLoader[] nueparents) throws IllegalArgumentException { |
71 |
// XXX should this be synchronized? |
72 |
if (nueparents == null) throw new IllegalArgumentException("null parents array"); // NOI18N |
73 |
for (int i = 0; i < nueparents.length; i++) { |
74 |
if (nueparents[i] == null) throw new IllegalArgumentException("null parent"); // NOI18N |
75 |
} |
76 |
|
77 |
parents = coalesceAppend(parents, nueparents); |
78 |
} |
79 |
|
80 |
|
81 |
|
82 |
/** Try to destroy this classloader. |
83 |
* Subsequent attempts to use it will log an error (at most one though). |
84 |
*/ |
85 |
public void destroy() { |
86 |
dead = true; |
87 |
} |
88 |
|
89 |
private void zombieCheck(String hint) { |
90 |
if (dead) { |
91 |
IllegalStateException ise = new IllegalStateException("WARNING - attempting to use a zombie classloader " + this + " on " + hint + ". This means classes from a disabled module are still active. May or may not be a problem."); // NOI18N |
92 |
JarClassLoader.notify(0, ise); |
93 |
// don't warn again for same loader... this was enough |
94 |
dead = false; |
95 |
} |
96 |
} |
97 |
|
98 |
/** |
99 |
* Loads the class with the specified name. The implementation of |
100 |
* this method searches for classes in the following order:<p> |
101 |
* <ol> |
102 |
* <li> Calls {@link #findLoadedClass(String)} to check if the class has |
103 |
* already been loaded. |
104 |
* <li> Checks the caches whether another class from the same package |
105 |
* was already loaded and uses the same classloader |
106 |
* <li> Tries to find the class using parent loaders in their order. |
107 |
* <li> Calls the {@link #simpleFindClass(String,String)} method to find |
108 |
* the class using this class loader. |
109 |
* </ol> |
110 |
* |
111 |
* @param name the name of the class |
112 |
* @param resolve if <code>true</code> then resolve the class |
113 |
* @return the resulting <code>Class</code> object |
114 |
* @exception ClassNotFoundException if the class could not be found |
115 |
*/ |
116 |
protected synchronized final Class loadClass(String name, boolean resolve) |
117 |
throws ClassNotFoundException { |
118 |
zombieCheck(name); |
119 |
// XXX this section is a candidate for local optimization: |
120 |
String filename = name.replace('.', '/').concat(".class"); // NOI18N |
121 |
int idx = filename.lastIndexOf('/'); // NOI18N |
122 |
if (idx == -1) throw new ClassNotFoundException("Will not load classes from default package"); // NOI18N |
123 |
String pkg = filename.substring(0, idx + 1); // "org/netbeans/modules/foo/" |
124 |
Class c = smartLoadClass(name, filename, pkg); |
125 |
if(c == null) throw new ClassNotFoundException(name); |
126 |
if (resolve) resolveClass(c); |
127 |
return c; |
128 |
} |
129 |
|
130 |
/** This ClassLoader can't load anything itself. Subclasses |
131 |
* may override this method to do some class loading themselves. The |
132 |
* implementation should not throw any exception, just return |
133 |
* <CODE>null</CODE> if it can't load required class. |
134 |
* |
135 |
* @param name the name of the class |
136 |
* @param fileName the expected filename of the classfile, like |
137 |
* <CODE>java/lang/Object.class</CODE> for <CODE>java.lang.Object</CODE> |
138 |
* The ClassLoader implementation may or may not use it, depending |
139 |
* whether it is usefull to it. |
140 |
* @return the resulting <code>Class</code> object or <code>null</code> |
141 |
*/ |
142 |
protected Class simpleFindClass(String name, String fileName) { |
143 |
return null; |
144 |
} |
145 |
|
146 |
|
147 |
/** |
148 |
* Finds the resource with the given name. The implementation of |
149 |
* this method searches for resources in the following order:<p> |
150 |
* <ol> |
151 |
* <li> Checks the caches whether another resource or class from the same |
152 |
* package was already loaded and uses the same classloader. |
153 |
* <li> Tries to find the resources using parent loaders in their order. |
154 |
* <li> Calls the {@link #findResource(String)} method to find |
155 |
* the resources using this class loader. |
156 |
* </ol> |
157 |
* |
158 |
* @param name a "/"-separated path name that identifies the resource. |
159 |
* @return a URL for reading the resource, or <code>null</code> if |
160 |
* the resource could not be found. |
161 |
* @see #findResource(String) |
162 |
*/ |
163 |
public final URL getResource(final String name) { |
164 |
zombieCheck(name); |
165 |
|
166 |
final int slashIdx = name.lastIndexOf('/'); |
167 |
if (slashIdx == -1) return null; // won't load from the default package |
168 |
final String pkg = name.substring(0, slashIdx + 1); |
169 |
|
170 |
if (isSpecialResource(pkg)) { |
171 |
// Disable domain cache for this one, do a simple check. |
172 |
for (int i = 0; i < parents.length; i++) { |
173 |
if (!shouldDelegateResource(pkg, parents[i])) continue; |
174 |
URL u; |
175 |
if (parents[i] instanceof ProxyClassLoader) { |
176 |
u = ((ProxyClassLoader)parents[i]).findResource(name); |
177 |
} else { |
178 |
u = parents[i].getResource(name); |
179 |
} |
180 |
if (u != null) return u; |
181 |
} |
182 |
return findResource(name); |
183 |
} |
184 |
|
185 |
ClassLoader owner = (ClassLoader)domainsByPackage.get(pkg); |
186 |
|
187 |
if (owner != null) { // known package |
188 |
// Note that shouldDelegateResource should already be true for this! |
189 |
if (owner instanceof ProxyClassLoader) { |
190 |
return ((ProxyClassLoader)owner).findResource(name); // we have its parents, skip them |
191 |
} else { |
192 |
return owner.getResource(name); // know nothing about this loader and his structure |
193 |
} |
194 |
} |
195 |
|
196 |
// virgin package |
197 |
URL retVal = null; |
198 |
for (int i = 0; i < parents.length; i++) { |
199 |
owner = parents[i]; |
200 |
if (!shouldDelegateResource(pkg, owner)) continue; |
201 |
if (owner instanceof ProxyClassLoader) { |
202 |
retVal = ((ProxyClassLoader)owner).findResource(name); // skip parents (checked already) |
203 |
} else { |
204 |
retVal = owner.getResource(name); // know nothing about this loader and his structure |
205 |
} |
206 |
if (retVal != null) { |
207 |
domainsByPackage.put(pkg, owner); |
208 |
return retVal; |
209 |
} |
210 |
} |
211 |
|
212 |
// try it ourself |
213 |
retVal = findResource(name); |
214 |
if (retVal != null) { |
215 |
domainsByPackage.put(pkg, this); |
216 |
} |
217 |
return retVal; |
218 |
} |
219 |
|
220 |
/** This ClassLoader can't load anything itself. Subclasses |
221 |
* may override this method to do some resource loading themselves. |
222 |
* |
223 |
* @param name the resource name |
224 |
* @return a URL for reading the resource, or <code>null</code> |
225 |
* if the resource could not be found. |
226 |
*/ |
227 |
protected URL findResource(String name) { |
228 |
return null; |
229 |
} |
230 |
|
231 |
/** |
232 |
* Finds all the resource with the given name. The implementation of |
233 |
* this method uses the {@link #simpleFindResources(String)} method to find |
234 |
* all the resources available from this classloader and adds all the |
235 |
* resources from all the parents. |
236 |
* |
237 |
* @param name the resource name |
238 |
* @return an Enumeration of URLs for the resources |
239 |
* @throws IOException if I/O errors occur |
240 |
*/ |
241 |
protected final synchronized Enumeration findResources(String name) throws IOException { |
242 |
zombieCheck(name); |
243 |
final int slashIdx = name.lastIndexOf('/'); |
244 |
if (slashIdx == -1) return EMPTY; // won't load from the default package |
245 |
final String pkg = name.substring(0, slashIdx + 1); |
246 |
|
247 |
// Don't bother optimizing this call by domains. |
248 |
// It is mostly used for resources for which isSpecialResource would be true anyway. |
249 |
Enumeration[] es = new Enumeration[parents.length + 1]; |
250 |
for (int i = 0; i < parents.length; i++) { |
251 |
if (!shouldDelegateResource(pkg, parents[i])) { |
252 |
es[i] = EMPTY; |
253 |
continue; |
254 |
} |
255 |
if (parents[i] instanceof ProxyClassLoader) { |
256 |
es[i] = ((ProxyClassLoader)parents[i]).simpleFindResources(name); |
257 |
} else { |
258 |
es[i] = parents[i].getResources(name); |
259 |
} |
260 |
} |
261 |
es[parents.length] = simpleFindResources(name); |
262 |
// Should not be duplicates, assuming the parent loaders are properly distinct |
263 |
// from one another and do not overlap in JAR usage, which they ought not. |
264 |
// Anyway MetaInfServicesLookup, the most important client of this method, does |
265 |
// its own duplicate filtering already. |
266 |
return new AAEnum (es); |
267 |
} |
268 |
|
269 |
/** This ClassLoader can't load anything itself. Subclasses |
270 |
* may override this method to do some resource loading themselves, this |
271 |
* implementation simply delegates to findResources method of the superclass |
272 |
* that should return empty Enumeration. |
273 |
* |
274 |
* @param name the resource name |
275 |
* @return an Enumeration of URLs for the resources |
276 |
* @throws IOException if I/O errors occur |
277 |
*/ |
278 |
protected Enumeration simpleFindResources(String name) throws IOException { |
279 |
return super.findResources(name); |
280 |
} |
281 |
|
282 |
|
283 |
/** |
284 |
* Returns a Package that has been defined by this class loader or any |
285 |
* of its parents. |
286 |
* |
287 |
* @param name the package name |
288 |
* @return the Package corresponding to the given name, or null if not found |
289 |
*/ |
290 |
protected Package getPackage(String name) { |
291 |
zombieCheck(name); |
292 |
|
293 |
int idx = name.lastIndexOf('.'); |
294 |
if (idx == -1) return null; |
295 |
String spkg = name.substring(0, idx + 1).replace('.', '/'); |
296 |
|
297 |
synchronized (packages) { |
298 |
Package pkg = (Package)packages.get(name); |
299 |
if (pkg != null) return pkg; |
300 |
|
301 |
for (int i = 0; i < parents.length; i++) { |
302 |
ClassLoader par = parents[i]; |
303 |
if (par instanceof ProxyClassLoader && shouldDelegateResource(spkg, par)) { |
304 |
pkg = ((ProxyClassLoader)par).getPackage(name); |
305 |
if(pkg != null) break; |
306 |
} |
307 |
} |
308 |
// do our own lookup |
309 |
if (pkg == null) pkg = super.getPackage(name); |
310 |
// cache results |
311 |
if (pkg != null) packages.put(name, pkg); |
312 |
|
313 |
return pkg; |
314 |
} |
315 |
} |
316 |
|
317 |
/** This is here just for locking serialization purposes. |
318 |
* Delegates to super.definePackage with proper locking. |
319 |
*/ |
320 |
protected Package definePackage(String name, String specTitle, |
321 |
String specVersion, String specVendor, String implTitle, |
322 |
String implVersion, String implVendor, URL sealBase ) |
323 |
throws IllegalArgumentException { |
324 |
synchronized (packages) { |
325 |
return super.definePackage (name, specTitle, specVersion, specVendor, implTitle, |
326 |
implVersion, implVendor, sealBase); |
327 |
} |
328 |
} |
329 |
|
330 |
/** |
331 |
* Returns all of the Packages defined by this class loader and its parents. |
332 |
* |
333 |
* @return the array of <code>Package</code> objects defined by this |
334 |
* <code>ClassLoader</code> |
335 |
*/ |
336 |
protected synchronized Package[] getPackages() { |
337 |
zombieCheck(null); |
338 |
Map all = new HashMap(); // Map<String,Package> |
339 |
addPackages(all, super.getPackages()); |
340 |
for (int i = 0; i < parents.length; i++) { |
341 |
ClassLoader par = parents[i]; |
342 |
if (par instanceof ProxyClassLoader) { |
343 |
// XXX should ideally use shouldDelegateResource here... |
344 |
addPackages(all, ((ProxyClassLoader)par).getPackages()); |
345 |
} |
346 |
} |
347 |
synchronized (packages) { |
348 |
Iterator it = all.entrySet().iterator(); |
349 |
while (it.hasNext()) { |
350 |
Map.Entry entry = (Map.Entry)it.next(); |
351 |
Object name = entry.getKey(); |
352 |
if (! packages.containsKey(name)) { |
353 |
packages.put(name, entry.getValue()); |
354 |
} |
355 |
} |
356 |
} |
357 |
return (Package[])all.values().toArray(new Package[all.size()]); |
358 |
} |
359 |
|
360 |
public Package getPackageAccessibly(String name) { |
361 |
return getPackage(name); |
362 |
} |
363 |
|
364 |
public Package[] getPackagesAccessibly() { |
365 |
return getPackages(); |
366 |
} |
367 |
|
368 |
|
369 |
|
370 |
/** Coalesce parent classloaders into an optimized set. |
371 |
* This means that all parents of the specified classloaders |
372 |
* are also added recursively, removing duplicates along the way. |
373 |
* Search order should be preserved (parents before children, stable w.r.t. inputs). |
374 |
* @param loaders list of suggested parents (no nulls or duplicates permitted) |
375 |
* @return optimized list of parents (no nulls or duplicates) |
376 |
* @throws IllegalArgumentException if there are cycles |
377 |
*/ |
378 |
private ClassLoader[] coalesceParents(ClassLoader[] loaders) throws IllegalArgumentException { |
379 |
int likelySize = loaders.length * 3 + 1; |
380 |
Set resultingUnique = new HashSet(likelySize); // Set<ClassLoader> |
381 |
List resulting = new ArrayList(likelySize); // List<ClassLoader> |
382 |
for (int i = 0; i < loaders.length; i++) { |
383 |
addRec(resultingUnique, resulting, loaders[i]); |
384 |
} |
385 |
ClassLoader[] ret = (ClassLoader[])resulting.toArray(new ClassLoader[resulting.size()]); |
386 |
return ret; |
387 |
} |
388 |
|
389 |
/** Coalesce a new set of loaders into the existing ones. |
390 |
*/ |
391 |
private ClassLoader[] coalesceAppend(ClassLoader[] existing, ClassLoader[] appended) throws IllegalArgumentException { |
392 |
int likelySize = existing.length + 3; |
393 |
Set resultingUnique = new HashSet(likelySize); |
394 |
List existingL = Arrays.asList(existing); |
395 |
resultingUnique.addAll(existingL); |
396 |
if (resultingUnique.containsAll(Arrays.asList(appended))) { |
397 |
// No change required. |
398 |
return existing; |
399 |
} |
400 |
List resulting = new ArrayList(likelySize); |
401 |
resulting.addAll(existingL); |
402 |
int fromIdx = resulting.size(); |
403 |
for (int i = 0; i < appended.length; i++) { |
404 |
addRec(resultingUnique, resulting, appended[i]); |
405 |
} |
406 |
ClassLoader[] ret = (ClassLoader[])resulting.toArray(new ClassLoader[resulting.size()]); |
407 |
return ret; |
408 |
} |
409 |
|
410 |
private void addRec(Set resultingUnique, List resulting, ClassLoader loader) throws IllegalArgumentException { |
411 |
if (loader == this) throw new IllegalArgumentException("cycle in parents"); // NOI18N |
412 |
if (resultingUnique.contains(loader)) return; |
413 |
if (loader instanceof ProxyClassLoader) { |
414 |
ClassLoader[] parents = ((ProxyClassLoader)loader).parents; |
415 |
for (int i = 0; i < parents.length; i++) { |
416 |
addRec(resultingUnique, resulting, parents[i]); |
417 |
} |
418 |
} |
419 |
resultingUnique.add(loader); |
420 |
resulting.add(loader); |
421 |
} |
422 |
|
423 |
/** A method that finds a class either in itself or in parents. |
424 |
* It uses dual signaling for class not found: it can either return null |
425 |
* or throw CNFE itself. |
426 |
* @param name class name, e.g. "org.netbeans.modules.foo.Clazz" |
427 |
* @param fileName resource name, e.g. "org/netbeans/modules/foo/Clazz.class" |
428 |
* @param pkg package component, e.g. "org/netbeans/modules/foo/" |
429 |
* @return a class or null if not found. It can also throw an exception. |
430 |
* @throws ClassNotFoundException in case it doesn't found a class |
431 |
* and a parent eglible for loading it thrown it already. |
432 |
*/ |
433 |
private final Class smartLoadClass(String name, String fileName, String pkg) throws ClassNotFoundException { |
434 |
// First, check if the class has already been loaded |
435 |
Class c = findLoadedClass(name); |
436 |
if(c != null) return c; |
437 |
|
438 |
final ClassLoader owner = isSpecialResource(pkg) ? null : (ClassLoader)domainsByPackage.get(pkg); |
439 |
if (owner == this) { |
440 |
return simpleFindClass(name,fileName); |
441 |
} |
442 |
if (owner != null) { |
443 |
// Note that shouldDelegateResource should already be true as we hit this pkg before. |
444 |
if (owner instanceof ProxyClassLoader) { |
445 |
return ((ProxyClassLoader)owner).fullFindClass(name,fileName); |
446 |
} else { |
447 |
return owner.loadClass(name); // May throw CNFE, will be propagated |
448 |
} |
449 |
} |
450 |
|
451 |
// Virgin package, do the parent scan |
452 |
c = loadInOrder(name, fileName, pkg); |
453 |
|
454 |
if (c != null) { |
455 |
final ClassLoader owner2 = c.getClassLoader(); // who got it? |
456 |
domainsByPackage.put(pkg, owner2); |
457 |
} |
458 |
return c; |
459 |
} |
460 |
|
461 |
|
462 |
private final Class loadInOrder( String name, String fileName, String pkg ) throws ClassNotFoundException { |
463 |
ClassNotFoundException cached = null; |
464 |
for (int i = 0; i < parents.length; i++) { |
465 |
ClassLoader par = parents[i]; |
466 |
if (!shouldDelegateResource(pkg, par)) continue; |
467 |
if (par instanceof ProxyClassLoader) { |
468 |
Class c = ((ProxyClassLoader)par).fullFindClass(name,fileName); |
469 |
if (c != null) return c; |
470 |
} else { |
471 |
try { |
472 |
return par.loadClass(name); |
473 |
} catch( ClassNotFoundException cnfe ) { |
474 |
cached = cnfe; |
475 |
} |
476 |
} |
477 |
} |
478 |
|
479 |
Class c = simpleFindClass(name,fileName); // Try it ourselves |
480 |
if (c != null) return c; |
481 |
if (cached != null) throw cached; |
482 |
return null; |
483 |
} |
484 |
|
485 |
private synchronized Class fullFindClass(String name, String fileName) { |
486 |
Class c = findLoadedClass(name); |
487 |
return (c == null) ? simpleFindClass(name, fileName) : c; |
488 |
} |
489 |
|
490 |
private void addPackages(Map all, Package[] pkgs) { |
491 |
// Would be easier if Package.equals() was just defined sensibly... |
492 |
for (int i = 0; i < pkgs.length; i++) { |
493 |
all.put(pkgs[i].getName(), pkgs[i]); |
494 |
} |
495 |
} |
496 |
|
497 |
/** Test whether a given resource name is something that any JAR might |
498 |
* have, and for which the domain cache should be disabled. |
499 |
* The result must not change from one call to the next with the same argument. |
500 |
* By default the domain cache is disabled only for META-INF/* JAR information. |
501 |
* @param pkg the package component of the resource path ending with a slash, |
502 |
* e.g. "org/netbeans/modules/foo/" |
503 |
* @return true if it is a special resource, false for normal domain-cached resource |
504 |
* @since org.netbeans.core/1 1.3 |
505 |
*/ |
506 |
protected boolean isSpecialResource(String pkg) { |
507 |
if (pkg.startsWith("META-INF/")) return true; // NOI18N |
508 |
return false; |
509 |
} |
510 |
|
511 |
/** Test whether a given resource request (for a class or not) should be |
512 |
* searched for in the specified parent classloader or not. |
513 |
* The result must not change from one call to the next with the same arguments. |
514 |
* By default, always true. Subclasses may override to "mask" certain |
515 |
* packages from view, possibly according to the classloader chain. |
516 |
* @param pkg the package component of the resource path ending with a slash, |
517 |
* e.g. "org/netbeans/modules/foo/" |
518 |
* @param parent a classloader which is a direct or indirect parent of this one |
519 |
* @return true if the request should be delegated to this parent; false to |
520 |
* only search elsewhere (other parents, this loader's own namespace) |
521 |
* @since org.netbeans.core/1 1.3 |
522 |
*/ |
523 |
protected boolean shouldDelegateResource(String pkg, ClassLoader parent) { |
524 |
return true; |
525 |
} |
526 |
|
527 |
|
528 |
private static final class ArrayEnumeration implements Enumeration { |
529 |
/** The array */ |
530 |
private Object[] array; |
531 |
/** Current index in the array */ |
532 |
private int index = 0; |
533 |
|
534 |
/** Constructs a new ArrayEnumeration for specified array */ |
535 |
public ArrayEnumeration (Object[] array) { |
536 |
this.array = array; |
537 |
} |
538 |
|
539 |
/** Tests if this enumeration contains more elements. |
540 |
* @return <code>true</code> if this enumeration contains more elements; |
541 |
* <code>false</code> otherwise. |
542 |
*/ |
543 |
public boolean hasMoreElements() { |
544 |
return (index < array.length); |
545 |
} |
546 |
|
547 |
/** Returns the next element of this enumeration. |
548 |
* @return the next element of this enumeration. |
549 |
* @exception NoSuchElementException if no more elements exist. |
550 |
*/ |
551 |
public Object nextElement() { |
552 |
try { |
553 |
return array[index++]; |
554 |
} catch (ArrayIndexOutOfBoundsException e) { |
555 |
throw new NoSuchElementException(); |
556 |
} |
557 |
} |
558 |
} |
559 |
|
560 |
private static final class AAEnum implements Enumeration { |
561 |
/** The array */ |
562 |
private Enumeration[] array; |
563 |
/** Current index in the array */ |
564 |
private int index = 0; |
565 |
|
566 |
/** Constructs a new ArrayEnumeration for specified array */ |
567 |
public AAEnum (Enumeration[] array) { |
568 |
this.array = array; |
569 |
} |
570 |
|
571 |
/** Tests if this enumeration contains more elements. |
572 |
* @return <code>true</code> if this enumeration contains more elements; |
573 |
* <code>false</code> otherwise. |
574 |
*/ |
575 |
public boolean hasMoreElements() { |
576 |
for (;;) { |
577 |
if (index == array.length) { |
578 |
return false; |
579 |
} |
580 |
|
581 |
if (array[index].hasMoreElements ()) { |
582 |
return true; |
583 |
} |
584 |
|
585 |
index++; |
586 |
} |
587 |
} |
588 |
|
589 |
/** Returns the next element of this enumeration. |
590 |
* @return the next element of this enumeration. |
591 |
* @exception NoSuchElementException if no more elements exist. |
592 |
*/ |
593 |
public Object nextElement() { |
594 |
try { |
595 |
return array[index].nextElement (); |
596 |
} catch (NoSuchElementException ex) { |
597 |
if (hasMoreElements ()) { |
598 |
// try once more |
599 |
return nextElement (); |
600 |
} |
601 |
throw ex; |
602 |
} catch (ArrayIndexOutOfBoundsException e) { |
603 |
throw new NoSuchElementException(); |
604 |
} |
605 |
} |
606 |
} |
607 |
|
608 |
} |