1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package com.tomgibara.pronto.util;
19
20 import java.security.AccessController;
21 import java.security.PrivilegedAction;
22
23 /**
24 * <p>
25 * This class can be extended to provide a factory implementation that:
26 * </p>
27 *
28 * <ul>
29 * <li>Looks to a specified system property for the name of a singleton class
30 * to instantiate.</li>
31 * <li>Instantiates a default class if no property is present.</li>
32 * <li>Caches the resulting instance (or resulting exception) for every call to
33 * getInstance()</li>
34 * </ul>
35 *
36 * <p>
37 * Note that instantiation of the instance is eager, but any exception is
38 * recorded and repeatedly rethrown on each call to getInstance. The eager
39 * instantiation means that this factory implementation is fast, requires no
40 * synchronization, and is thread-safe.
41 * </p>
42 *
43 * <p>
44 * It is expected that this class will be used by extension, or delegation:
45 * supplying the appropriate strings to the constructor and wrapping the
46 * getInstance method to narrow the thrown Exception.
47 * </p>
48 *
49 * @param <T>
50 * the type of object produced by the factory
51 * @author Tom Gibara
52 *
53 */
54
55 public class FactoryHelper<T> {
56
57
58
59 /**
60 * An exception which was cached during the initialization of the instance
61 * field, or null if the instance was created successfully.
62 */
63 private final Exception exception;
64
65 /**
66 * The instance which was created by this factory during its construction.
67 */
68 private final T instance;
69
70
71
72 /**
73 * Constructs a new factory which will create an instance of the class
74 * specified by either the value of the specified system property, or the
75 * supplied class name - exactly one of which may be null. Any exception
76 * which arises from attempting to instantiate the class is cached and
77 * thrown on any subsequent call to getInstance(). If no class loader is
78 * supplied, then the context class loader is used; If the context class
79 * loader is null the class loader of this class is used.
80 *
81 * @param className
82 * the class to which this factory is to create, may be null
83 * @param classLoader
84 * the class loader from which to load the class, may be null
85 * @param propertyName
86 * a system property which overrides the specified class name
87 */
88
89 @SuppressWarnings("unchecked")
90 public FactoryHelper(final String className, final ClassLoader classLoader, final String propertyName) {
91 if (className == null && propertyName == null) throw new IllegalArgumentException(
92 "both className and propertyName are null");
93 ClassLoader cl = classLoader;
94 if (cl == null) cl = Thread.currentThread().getContextClassLoader();
95 if (cl == null) cl = getClass().getClassLoader();
96
97 T i = null;
98 Exception e = null;
99 try {
100 String cn;
101 if (propertyName == null) {
102 cn = className;
103 } else {
104 cn = AccessController.doPrivileged(new PrivilegedAction<String>() {
105 public String run() {
106 return System.getProperty(propertyName, className);
107 }
108 });
109 if (cn == null) throw new NullPointerException(String.format(
110 "Unable to obtain a className from property: %s", propertyName));
111 }
112 Class<T> clss = (Class<T>) cl.loadClass(cn);
113 i = clss.newInstance();
114 } catch (Exception ex) {
115 e = ex;
116 } finally {
117 exception = e;
118 instance = i;
119 }
120 }
121
122
123
124 /**
125 * The object instance created by this factory. The same object is returned
126 * on each call to this method. Note that the exception thrown by this
127 * method is 'recycled'.
128 *
129 * @return the instance created by this factory
130 * @throws Exception
131 * the exception which occured trying to create the instance.
132 */
133
134 public T getInstance() throws Exception {
135 if (exception != null) throw exception;
136 return instance;
137 }
138
139 }