001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.bytecode.hook.impl;
006:
007: import com.tc.aspectwerkz.transform.TransformationConstants;
008: import com.tc.net.NIOWorkarounds;
009: import com.tc.object.bytecode.Manager;
010: import com.tc.object.bytecode.ManagerUtil;
011: import com.tc.object.bytecode.hook.ClassLoaderPreProcessorImpl;
012: import com.tc.object.bytecode.hook.ClassPostProcessor;
013: import com.tc.object.bytecode.hook.ClassPreProcessor;
014: import com.tc.object.bytecode.hook.DSOContext;
015: import com.tc.object.loaders.ClassProvider;
016: import com.tc.object.loaders.NamedClassLoader;
017: import com.tc.object.loaders.StandardClassProvider;
018: import com.tc.text.Banner;
019:
020: import java.io.ByteArrayOutputStream;
021: import java.io.File;
022: import java.io.FileFilter;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.PrintStream;
026: import java.lang.reflect.InvocationTargetException;
027: import java.lang.reflect.Method;
028: import java.net.MalformedURLException;
029: import java.net.URL;
030: import java.net.URLClassLoader;
031: import java.security.ProtectionDomain;
032: import java.util.ArrayList;
033: import java.util.Map;
034: import java.util.WeakHashMap;
035: import java.util.logging.LogManager;
036:
037: /**
038: * Helper class called by the modified version of java.lang.ClassLoader
039: */
040: public class ClassProcessorHelper {
041:
042: // Directory where Terracotta jars (and dependencies) can be found
043: private static final String TC_INSTALL_ROOT_SYSPROP = "tc.install-root";
044:
045: // Property to indicate whether the Terracotta classloader is active
046: private static final String TC_ACTIVE_SYSPROP = "tc.active";
047:
048: // NOTE: This is not intended to be a public/documented system property,
049: // it is for dev use only. It is NOT for QA or customer use
050: private static final String TC_CLASSPATH_SYSPROP = "tc.classpath";
051:
052: // Used for converting resource names into class names
053: private static final String CLASS_SUFFIX = ".class";
054: private static final int CLASS_SUFFIX_LENGTH = CLASS_SUFFIX
055: .length();
056:
057: private static final boolean GLOBAL_MODE_DEFAULT = true;
058:
059: public static final boolean USE_GLOBAL_CONTEXT;
060:
061: private static final State initState = new State();
062:
063: private static final String tcInstallRootSysProp = System
064: .getProperty(TC_INSTALL_ROOT_SYSPROP);
065:
066: // This map should only hold a weak reference to the loader (key).
067: // If we didn't we'd prevent loaders from being GC'd
068: private static final Map contextMap = new WeakHashMap();
069:
070: private static final StandardClassProvider globalProvider = new StandardClassProvider();
071:
072: private static URLClassLoader tcLoader;
073: private static DSOContext gloalContext;
074:
075: private static final boolean TRACE;
076: private static final PrintStream TRACE_STREAM;
077:
078: private static boolean systemLoaderInitialized = false;
079:
080: static {
081:
082: try {
083: // Make sure that the DSOContext class is loaded before using the
084: // TC functionalities. This is needed for the IBM JDK when Hashtable is
085: // instrumented for auto-locking in the bootjar.
086: Class.forName(DSOContext.class.getName());
087:
088: String global = System.getProperty("tc.dso.globalmode",
089: null);
090: if (global != null) {
091: USE_GLOBAL_CONTEXT = Boolean.valueOf(global)
092: .booleanValue();
093: } else {
094: USE_GLOBAL_CONTEXT = GLOBAL_MODE_DEFAULT;
095: }
096:
097: // See if we should trace or not -- if so grab System.[out|err] and keep a local reference to it. Applications
098: // like WebSphere like to intercept this later and we can get caught in a loop and get a stack overflow
099: final String traceOutput = System.getProperty(
100: "l1.classloader.trace.output", "none");
101: PrintStream ts = null;
102: if (traceOutput != null) {
103: if ("stdout".equals(traceOutput)) {
104: ts = System.out;
105: } else if ("stderr".equals(traceOutput)) {
106: ts = System.err;
107: }
108: }
109: TRACE_STREAM = ts;
110: TRACE = TRACE_STREAM != null;
111:
112: } catch (Throwable t) {
113: Util.exit(t);
114: throw new AssertionError(); // this has to be here to make the compiler happy
115: }
116: }
117:
118: private static URLClassLoader createTCLoader() throws Exception {
119: return new URLClassLoader(buildTerracottaClassPath(), null);
120: }
121:
122: /**
123: * Get resource URL
124: *
125: * @param name Resource name
126: * @param cl Loading classloader
127: * @return URL to load resource from
128: */
129: public static URL getTCResource(String name, ClassLoader cl) {
130: String className = null;
131: if (name.endsWith(CLASS_SUFFIX)) {
132: className = name.substring(0,
133: name.length() - CLASS_SUFFIX_LENGTH).replace('/',
134: '.');
135: }
136:
137: URL resource = getClassResource(className, cl);
138:
139: if (null == resource) {
140: if (!isAWRuntimeDependency(className)) {
141: return null;
142: }
143:
144: try {
145: resource = tcLoader.findResource(name); // getResource() would cause an endless loop
146: } catch (Exception e) {
147: resource = null;
148: }
149: }
150:
151: return resource;
152: }
153:
154: /**
155: * Get TC class definition
156: *
157: * @param name Class name
158: * @param cl Classloader
159: * @return Class bytes
160: * @throws ClassNotFoundException If class not found
161: */
162: public static byte[] getTCClass(String name, ClassLoader cl)
163: throws ClassNotFoundException {
164: URL resource = getClassResource(name, cl);
165:
166: if (null == resource) {
167: if (!isAWRuntimeDependency(name)) {
168: return null;
169: }
170:
171: resource = tcLoader.findResource(name.replace('.', '/')
172: + ".class"); // getResource() would cause an endless loop
173: }
174:
175: if (null == resource) {
176: return null;
177: }
178:
179: return getResourceBytes(resource);
180: }
181:
182: private static byte[] getResourceBytes(URL url)
183: throws ClassNotFoundException {
184: InputStream is = null;
185: try {
186: is = url.openStream();
187: byte[] b = new byte[is.available()];
188: int len = 0;
189: int n;
190: while ((n = is.read(b, len, b.length - len)) > 0) {
191: len += n;
192: if (len < b.length) {
193: byte[] c = new byte[b.length + 1000];
194: System.arraycopy(b, 0, c, 0, len);
195: b = c;
196: }
197: }
198: if (len == b.length) {
199: return b;
200: }
201: byte[] c = new byte[len];
202: System.arraycopy(b, 0, c, 0, len);
203: return c;
204: } catch (Exception e) {
205: throw new ClassNotFoundException("Unable to load "
206: + url.toString() + "; " + e.toString(), e);
207: } finally {
208: try {
209: is.close();
210: } catch (Exception ex) {
211: // ignore
212: }
213: }
214: }
215:
216: private static URL getClassResource(String name, ClassLoader cl) {
217: if (name != null) {
218: DSOContext context = getContext(cl);
219: if (context != null) {
220: return context.getClassResource(name);
221: }
222: }
223:
224: return null;
225: }
226:
227: private static boolean isAWRuntimeDependency(String name) {
228: if (null == name) {
229: return false;
230: }
231: return name.startsWith("com.tcspring.");
232: // || name.startsWith("com.tc.aspectwerkz.definition.deployer.AspectModule")
233: // || name.equals("com.tc.aspectwerkz.aspect.AspectContainer")
234: // || name.equals("com.tc.aspectwerkz.aspect.AbstractAspectContainer");
235: }
236:
237: private static void handleError(Throwable t) {
238: if (t instanceof InvocationTargetException) {
239: t = ((InvocationTargetException) t).getTargetException();
240: }
241:
242: if (t instanceof RuntimeException) {
243: throw (RuntimeException) t;
244: }
245: if (t instanceof Error) {
246: throw (Error) t;
247: }
248:
249: throw new RuntimeException(t);
250: }
251:
252: static File getTCInstallDir(boolean systemClassPathAllowed) {
253: if (tcInstallRootSysProp == null) {
254: if (systemClassPathAllowed) {
255: try {
256: ClassLoader.getSystemClassLoader().loadClass(
257: "com.tc.object.NotInBootJar");
258: return null;
259: } catch (ClassNotFoundException cnfe) {
260: // ignore
261: }
262: }
263:
264: Banner
265: .errorBanner("Terracotta home directory is not set. Please set it with -D"
266: + TC_INSTALL_ROOT_SYSPROP
267: + "=<path-to-Terracotta-install>");
268: Util.exit();
269: }
270:
271: File tcInstallDir = new File(tcInstallRootSysProp);
272:
273: if (!tcInstallDir.exists() || !tcInstallDir.isDirectory()
274: || !tcInstallDir.canRead()) {
275: Banner
276: .errorBanner("Terracotta install directory ["
277: + tcInstallDir.getAbsolutePath()
278: + "] is not accessible. This value is set via system property "
279: + TC_INSTALL_ROOT_SYSPROP);
280: Util.exit();
281: }
282:
283: return tcInstallDir;
284: }
285:
286: private static URL[] buildTerracottaClassPath() throws Exception {
287: if (System.getProperty(TC_CLASSPATH_SYSPROP) != null) {
288: return buildDevClassPath();
289: }
290:
291: File tcHomeDir = getTCInstallDir(true);
292: if (tcHomeDir == null) {
293: ClassLoader classPathLoader = sun.misc.Launcher
294: .getLauncher().getClassLoader();
295: URL[] systemURLS = ((URLClassLoader) classPathLoader)
296: .getURLs();
297: return (URL[]) systemURLS.clone();
298: }
299:
300: File tcLib = new File(tcHomeDir, "lib");
301: if (!tcLib.exists() || !tcLib.isDirectory() || !tcLib.canRead()) {
302: Banner
303: .errorBanner("Terracotta lib directory ["
304: + tcLib.getAbsolutePath()
305: + "] is not accessible. This value is based off of the system property "
306: + TC_INSTALL_ROOT_SYSPROP);
307: Util.exit();
308: }
309:
310: File[] entries = tcLib.listFiles(new JarFilter());
311:
312: if (entries.length == 0) {
313: Banner
314: .errorBanner("Absolutely no .jar files found in Terracotta common lib directory ["
315: + tcLib.getAbsolutePath()
316: + "]. Please check the value of your "
317: + TC_INSTALL_ROOT_SYSPROP
318: + " system property");
319: Util.exit();
320: }
321:
322: URL[] rv = new URL[entries.length];
323: for (int i = 0; i < entries.length; i++) {
324: String jar = entries[i].getAbsolutePath().replace(
325: File.separatorChar, '/');
326: rv[i] = new URL("file", "", jar);
327: }
328: return rv;
329: }
330:
331: private static String slurpFile(String path) throws IOException {
332: URL url = new URL(path);
333: InputStream in = url.openStream();
334: ByteArrayOutputStream bos = new ByteArrayOutputStream();
335:
336: try {
337: byte[] buf = new byte[1024];
338: int len;
339: while ((len = in.read(buf, 0, buf.length)) >= 0) {
340: bos.write(buf, 0, len);
341: }
342: } finally {
343: try {
344: in.close();
345: } catch (IOException ioe) {
346: // ignore
347: }
348:
349: try {
350: bos.close();
351: } catch (IOException ioe) {
352: // ignore
353: }
354: }
355:
356: return new String(bos.toByteArray());
357: }
358:
359: private static URL[] buildDevClassPath()
360: throws MalformedURLException, IOException {
361: // For development use only. This is handy since you can put the eclipse/ant build output directories
362: // here and not bother creating a tc.jar every time you change some source
363:
364: String tcClasspath = System.getProperty(TC_CLASSPATH_SYSPROP);
365: if (tcClasspath.startsWith("file:/")) {
366: tcClasspath = slurpFile(tcClasspath);
367: }
368:
369: String[] parts = tcClasspath.split(File.pathSeparator);
370: ArrayList urls = new ArrayList();
371:
372: for (int i = 0; i < parts.length; i++) {
373: String part = parts[i];
374: if (part.length() > 0) {
375:
376: File file = new File(part);
377: part = file.getAbsolutePath().replace(
378: File.separatorChar, '/');
379:
380: if (!part.startsWith("/")) {
381: part = "/" + part;
382: }
383:
384: if (file.isDirectory()) {
385: if (!part.endsWith("/")) {
386: part = part + "/";
387: }
388: }
389:
390: urls.add(new URL("file", "", part));
391: }
392: }
393:
394: return (URL[]) urls.toArray(new URL[urls.size()]);
395: }
396:
397: private static Method getContextMethod(String name, Class[] args)
398: throws ClassNotFoundException, NoSuchMethodException {
399: Class c = tcLoader
400: .loadClass("com.tc.object.bytecode.hook.impl.DSOContextImpl");
401: return c.getDeclaredMethod(name, args);
402: }
403:
404: public static void init() {
405: if (initState.attemptInit()) {
406: try {
407: // This avoids a deadlock (see LKC-853, LKC-1387)
408: java.security.Security.getProviders();
409:
410: // Avoid another deadlock (DEV-1047)
411: LogManager.getLogManager();
412:
413: // Workaround bug in NIO on solaris 10
414: NIOWorkarounds.solaris10Workaround();
415:
416: tcLoader = createTCLoader();
417:
418: // do this before doing anything with the TC loader
419: initTCLogging();
420:
421: if (USE_GLOBAL_CONTEXT) {
422: gloalContext = createGlobalContext();
423: }
424:
425: initState.initialized();
426:
427: System.setProperty(TC_ACTIVE_SYSPROP, Boolean.TRUE
428: .toString());
429: } catch (Throwable t) {
430: t.printStackTrace();
431: handleError(t);
432: throw new AssertionError(); // shouldn't get here
433: }
434: }
435: }
436:
437: private static void initTCLogging() throws ClassNotFoundException,
438: NoSuchMethodException, IllegalAccessException,
439: InvocationTargetException {
440: // This code is here because users can set various Log4J properties that will, for example, cause Log4J to try
441: // to use arbitrary classes as appenders. If users try to use one of their own classes as an appender, we'll try
442: // to load it in our classloader and fail in fairly unpleasant ways.
443: //
444: // Yes, saving and restoring a system property really sucks, but there isn't really a better way to do it. Users
445: // can request that Log4J read config from an arbitrary URL otherwise, and there's no way to intercept that at
446: // all. As a result, this seems like a better solution.
447: //
448: // See LKC-1974 for more details.
449:
450: String oldDefaultInitOverrideValue = null;
451:
452: try {
453: oldDefaultInitOverrideValue = System.setProperty(
454: "log4j.defaultInitOverride", "true");
455: Class loggerClass = tcLoader
456: .loadClass("org.apache.log4j.Logger");
457: Method theMethod = loggerClass.getDeclaredMethod(
458: "getRootLogger", new Class[0]);
459: theMethod.invoke(null, (Object[]) null);
460: } finally {
461: if (oldDefaultInitOverrideValue == null) {
462: System.getProperties().remove(
463: "log4j.defaultInitOverride");
464: } else {
465: System.setProperty("log4j.defaultInitOverride",
466: oldDefaultInitOverrideValue);
467: }
468: }
469: }
470:
471: public static void registerGlobalLoader(NamedClassLoader loader) {
472: if (!USE_GLOBAL_CONTEXT) {
473: throw new IllegalStateException("Not global DSO mode");
474: }
475: if (TRACE)
476: traceNamedLoader(loader);
477: globalProvider.registerNamedLoader(loader);
478: }
479:
480: /**
481: * Shut down the ClassProcessorHelper
482: */
483: public static void shutdown() {
484: if (!USE_GLOBAL_CONTEXT) {
485: throw new IllegalStateException("Not global DSO mode");
486: }
487: try {
488: if (gloalContext != null) {
489: gloalContext.getManager().stop();
490: }
491: } catch (Throwable t) {
492: t.printStackTrace();
493: }
494: }
495:
496: /**
497: * Check whether this web app is using DSO sessions
498: *
499: * @param appName Web app name
500: * @return True if DSO sessions enabled
501: */
502: public static boolean isDSOSessions(String appName) {
503: appName = ("/".equals(appName)) ? "ROOT" : appName;
504: try {
505: Method m = getContextMethod("isDSOSessions",
506: new Class[] { String.class });
507: boolean rv = ((Boolean) m.invoke(null,
508: new Object[] { appName })).booleanValue();
509: return rv;
510: } catch (Throwable t) {
511: handleError(t);
512: throw new AssertionError(); // shouldn't get here
513: }
514: }
515:
516: /**
517: * WARNING: Used by test framework only
518: *
519: * @param loader Loader
520: * @param context DSOContext
521: */
522: public static void setContext(ClassLoader loader, DSOContext context) {
523: if (USE_GLOBAL_CONTEXT) {
524: throw new IllegalStateException(
525: "DSO Context is global in this VM");
526: }
527:
528: if ((loader == null) || (context == null)) {
529: // bad dog
530: throw new IllegalArgumentException(
531: "Loader and/or context may not be null");
532: }
533:
534: synchronized (contextMap) {
535: contextMap.put(loader, context);
536: }
537: }
538:
539: /**
540: * WARNING: used by test framework only
541: */
542: public static Manager getManager(ClassLoader caller) {
543: if (USE_GLOBAL_CONTEXT) {
544: return gloalContext.getManager();
545: }
546:
547: DSOContext context;
548: synchronized (contextMap) {
549: context = (DSOContext) contextMap.get(caller);
550: }
551: if (context == null) {
552: return null;
553: }
554: return context.getManager();
555: }
556:
557: /**
558: * Get the DSOContext for this classloader
559: *
560: * @param cl Loader
561: * @return Context
562: */
563: public static DSOContext getContext(ClassLoader cl) {
564: if (USE_GLOBAL_CONTEXT)
565: return gloalContext;
566:
567: synchronized (contextMap) {
568: return (DSOContext) contextMap.get(cl);
569: }
570: }
571:
572: private static DSOContext createGlobalContext() {
573: try {
574: Method m = getContextMethod("createGlobalContext",
575: new Class[] { ClassProvider.class });
576: DSOContext context = (DSOContext) m.invoke(null,
577: new Object[] { globalProvider });
578: context.getManager().init();
579: return context;
580: } catch (Throwable t) {
581: t.printStackTrace();
582: System.exit(-1);
583: throw new AssertionError(); // shouldn't get here
584: }
585: }
586:
587: /**
588: * byte code instrumentation of class loaded <br>
589: * XXX::NOTE:: Do NOT optimize to return same input byte array if the class was instrumented (I can't imagine why we
590: * would). Our instrumentation in java.lang.ClassLoader checks the returned byte array to see if the class is
591: * instrumented or not to maintain the array offset.
592: *
593: * @param caller Loader defining class
594: * @param name Class name
595: * @param b Data
596: * @param off Offset into b
597: * @param len Length of class data
598: * @param pd Protection domain for class
599: * @return Modified class array
600: *
601: * @see ClassLoaderPreProcessorImpl
602: */
603: public static byte[] defineClass0Pre(ClassLoader caller,
604: String name, byte[] b, int off, int len, ProtectionDomain pd) {
605: if (skipClass(caller)) {
606: return b;
607: }
608:
609: // needed for JRockit
610: name = (name != null) ? name.replace('/', '.') : null;
611:
612: if (TRACE)
613: traceLookup(caller, name);
614:
615: if (isAWDependency(name)) {
616: return b;
617: }
618: if (isDSODependency(name)) {
619: return b;
620: }
621:
622: if (!initState.isInitialized()) {
623: return b;
624: }
625:
626: ManagerUtil.enable();
627:
628: ClassPreProcessor preProcessor = getPreProcessor(caller);
629: if (preProcessor == null) {
630: return b;
631: }
632:
633: return preProcessor.preProcess(name, b, off, len, caller);
634: }
635:
636: private static boolean skipClass(ClassLoader caller) {
637: return (caller == tcLoader);
638: }
639:
640: /**
641: * Post process class during definition
642: *
643: * @param clazz Class being defined
644: * @param caller Classloader doing definition
645: */
646: public static void defineClass0Post(Class clazz, ClassLoader caller) {
647: ClassPostProcessor postProcessor = getPostProcessor(caller);
648: if (!initState.isInitialized()) {
649: return;
650: }
651:
652: if (skipClass(caller)) {
653: return;
654: }
655:
656: if (postProcessor == null) {
657: return;
658: }
659:
660: postProcessor.postProcess(clazz, caller);
661: }
662:
663: /**
664: * @return Global Manager
665: */
666: public static Manager getGlobalManager() {
667: return gloalContext.getManager();
668: }
669:
670: private static ClassPreProcessor getPreProcessor(ClassLoader caller) {
671: if (USE_GLOBAL_CONTEXT) {
672: return gloalContext;
673: }
674:
675: synchronized (contextMap) {
676: return (ClassPreProcessor) contextMap.get(caller);
677: }
678: }
679:
680: private static ClassPostProcessor getPostProcessor(
681: ClassLoader caller) {
682: if (USE_GLOBAL_CONTEXT) {
683: return gloalContext;
684: }
685:
686: synchronized (contextMap) {
687: return (ClassPostProcessor) contextMap.get(caller);
688: }
689: }
690:
691: /**
692: * Check whether this is an AspectWerkz dependency
693: *
694: * @param className Class name
695: * @return True if AspectWerkz dependency
696: */
697: public static boolean isAWDependency(final String className) {
698: return (className == null)
699: || className.endsWith("_AWFactory")// TODO AVF refactor
700: || className
701: .endsWith(TransformationConstants.JOIN_POINT_CLASS_SUFFIX)
702: || className.startsWith("com.tc.aspectwerkz.")
703: || className.startsWith("com.tc.asm.")
704: || className.startsWith("com.tc.jrexx.")
705: || className.startsWith("org.dom4j.")
706: || className.startsWith("org.xml.sax.")
707: || className.startsWith("javax.xml.parsers.")
708: || className.startsWith("sun.reflect.Generated"); // issue on J2SE 5 reflection - AW-245
709: }
710:
711: /**
712: * Check whether this is a DSO dependency
713: *
714: * @param className Class name
715: * @return True if DSO dependency
716: */
717: public static boolean isDSODependency(final String className) {
718: return false;
719: // return (className == null) || className.startsWith("DO_NOT_USE.") || className.startsWith("com.tc.")
720: // || className.startsWith("org.w3c.dom.") || className.startsWith("org.apache.log4j.")
721: // || className.startsWith("org.apache.commons.io.") || className.startsWith("org.apache.commons.lang.")
722: // || className.startsWith("org.apache.commons.logging.") || className.startsWith("javax.xml.")
723: // || className.startsWith("org.apache.xmlbeans.") || className.startsWith("org.apache.xerces.");
724: }
725:
726: /**
727: * Get type of lock used by sessions
728: *
729: * @param appName Web app context
730: * @return Lock type
731: */
732: public static int getSessionLockType(String appName) {
733: return gloalContext.getSessionLockType(appName);
734: }
735:
736: private static void traceNamedLoader(final NamedClassLoader ncl) {
737: trace("loader[" + ncl + "] of type[" + ncl.getClass().getName()
738: + "] registered as[" + ncl.__tc_getClassLoaderName()
739: + "]");
740: }
741:
742: private static void traceLookup(final ClassLoader cl,
743: final String clazz) {
744: trace("loader[" + cl + "] of type[" + cl.getClass().getName()
745: + "] looking for class[" + clazz + "]");
746: }
747:
748: private static void trace(final String msg) {
749: TRACE_STREAM.println("<TRACE> TC classloading: " + msg);
750: TRACE_STREAM.flush();
751: }
752:
753: public static void loggingInitialized() {
754: final boolean attempt;
755:
756: synchronized (ClassProcessorHelper.class) {
757: attempt = systemLoaderInitialized;
758: }
759:
760: if (attempt)
761: init();
762: }
763:
764: public static void systemLoaderInitialized() {
765: final boolean attempt;
766:
767: synchronized (ClassProcessorHelper.class) {
768: if (systemLoaderInitialized) {
769: throw new AssertionError("already set");
770: }
771: systemLoaderInitialized = true;
772: attempt = !inLoggingStaticInit();
773: }
774:
775: if (attempt)
776: init();
777: }
778:
779: private static final boolean inLoggingStaticInit() {
780: StackTraceElement[] stack = new Throwable().getStackTrace();
781: for (int i = 0; i < stack.length; i++) {
782: StackTraceElement frame = stack[i];
783:
784: if ("java.util.logging.LogManager".equals(frame
785: .getClassName())
786: && "<clinit>".equals(frame.getMethodName())) {
787: return true;
788: }
789: }
790:
791: return false;
792: }
793:
794: /**
795: * File filter for JAR files
796: */
797: public static class JarFilter implements FileFilter {
798: public boolean accept(File pathname) {
799: return pathname.isFile()
800: && pathname.getAbsolutePath().toLowerCase()
801: .endsWith(".jar");
802: }
803: }
804:
805: /**
806: * ClassProcessorHelper initialization state
807: */
808: public static final class State {
809: private final int NOT_INTIALIZED = 0;
810: private final int INITIALIZING = 1;
811: private final int INITIALIZED = 2;
812: private int state = NOT_INTIALIZED;
813:
814: final synchronized boolean attemptInit() {
815: if (state == NOT_INTIALIZED) {
816: state = INITIALIZING;
817: return true;
818: }
819: return false;
820: }
821:
822: final synchronized void initialized() {
823: if (state != INITIALIZING) {
824: throw new IllegalStateException("State was " + state);
825: }
826: state = INITIALIZED;
827: }
828:
829: final synchronized boolean isInitialized() {
830: return state == INITIALIZED;
831: }
832:
833: }
834:
835: }
|