1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.locators;
17
18 import java.util.Locale;
19
20 import net.sf.jguiraffe.di.ClassLoaderProvider;
21
22 import org.apache.commons.beanutils.ConversionException;
23 import org.apache.commons.beanutils.Converter;
24
25 /**
26 * <p>
27 * A specialized {@code Converter} implementation dealing with {@link Locator}
28 * objects.
29 * </p>
30 * <p>
31 * This converter implementation is able to transform text representations of
32 * locators into concrete {@link Locator} instances. This is pretty convenient,
33 * especially for bean declarations; here a compact text representation for
34 * locators is much more readable than a verbose declaration of a factory method
35 * invocation.
36 * </p>
37 * <p>
38 * This converter supports most of the standard {@link Locator} implementations
39 * provided by this package. A text representation of a locator starts with a
40 * prefix, followed by a colon. Then the specific data of the concrete
41 * {@link Locator} subclass is appended. The following table lists the supported
42 * prefixes with their meaning and examples:
43 * <table border="1">
44 * <tr>
45 * <th>Prefix</th>
46 * <th>Description</th>
47 * <th>Example</th>
48 * </tr>
49 * <tr>
50 * <td valign="top">classpath</td>
51 * <td>Creates a {@link ClassPathLocator} instance. The data is interpreted as a
52 * resource name which is looked up on the current class path. Optionally, a
53 * class loader name can be provided separated by a semicolon. If this is used,
54 * the corresponding class loader is obtained from the
55 * {@link ClassLoaderProvider}; otherwise, the default class loader is used.</td>
56 * <td>classpath:myresource.properties<br>
57 * classpath:myresource.properties;myClassLoader</td>
58 * </tr>
59 * <tr>
60 * <td valign="top">file</td>
61 * <td>Creates a {@link FileLocator} instance. The data is interpreted as a
62 * relative or absolute file name. It is directly passed to the
63 * {@link FileLocator} instance to be created.</td>
64 * <td>file:target/data.txt</td>
65 * </tr>
66 * <tr>
67 * <td valign="top">url</td>
68 * <td>Creates a {@link URLLocator} instance. The data is interpreted as a URL
69 * in text form. It is directly passed to the {@link URLLocator} instance to be
70 * created.</td>
71 * <td>url:http://www.mydomain.com/image.jpg</td>
72 * </tr>
73 * </table>
74 * </p>
75 * <p>
76 * Prefixes are not case sensitive. If an unknown prefix is encountered, a
77 * {@code ConversionException} is thrown. An instance of this class is
78 * registered as base class converter by the form builder per default. Using the
79 * data type conversion mechanism provided by the dependency injection
80 * framework, it is possible to add further converters for custom
81 * {@link Locator} implementations.
82 * </p>
83 * <p>
84 * This class does not have any internal state. Thus an instance can be shared
85 * between multiple components and called concurrently.
86 * </p>
87 *
88 * @author Oliver Heger
89 * @version $Id: LocatorConverter.java 213 2012-07-14 19:40:51Z oheger $
90 */
91 public class LocatorConverter implements Converter
92 {
93 /**
94 * Constant for the prefix separator. This character is used to separate the
95 * prefix which identifies the type of the locator from its data.
96 */
97 public static final char PREFIX_SEPARATOR = ':';
98
99 /**
100 * The separator for a class loader name. This character is evaluated for
101 * locator declarations of type classpath.
102 */
103 private static final char CL_SEPARATOR = ';';
104
105 /** The current class loader provider. */
106 private final ClassLoaderProvider classLoaderProvider;
107
108 /**
109 * Creates a new instance of {@code LocatorConverter} and initializes it
110 * with the given {@code ClassLoaderProvider}. Class loaders for class path
111 * locators are obtained from this provider.
112 *
113 * @param clp the {@code ClassLoaderProvider}
114 * @since 1.2
115 */
116 public LocatorConverter(ClassLoaderProvider clp)
117 {
118 classLoaderProvider = clp;
119 }
120
121 /**
122 * Creates a new instance of {@code LocatorConverter} without a class loader
123 * provider. A converter constructed this way cannot resolve any class
124 * loader names. This constructor exists for reasons of backwards
125 * compatibility. It is recommended to always provide a
126 * {@code ClassLoaderProvider}.
127 */
128 public LocatorConverter()
129 {
130 this(null);
131 }
132
133 /**
134 * Returns the {@code ClassLoaderProvider} used by this converter. This
135 * object is used to determine class loaders when locators for class path
136 * resources are to be created. Result may be <b>null</b> if no
137 * {@code ClassLoaderProvider} has been set.
138 *
139 * @return the current {@code ClassLoaderProvider}
140 * @since 1.2
141 */
142 public ClassLoaderProvider getClassLoaderProvider()
143 {
144 return classLoaderProvider;
145 }
146
147 /**
148 * Tries to convert the specified object to a {@link Locator}. The
149 * conversion is based on prefixes as described in the class comment.
150 *
151 * @param type the target class
152 * @param value the object to be converted (must not be <b>null</b>)
153 * @return the converted object; this is a concrete implementation of the
154 * {@link Locator} interface
155 * @throws ConversionException if conversion is not possible
156 */
157 public Object convert(@SuppressWarnings("rawtypes") Class type, Object value)
158 {
159 if (value == null)
160 {
161 throw new ConversionException("Cannot convert null Locator!");
162 }
163
164 return convertLocator(String.valueOf(value));
165 }
166
167 /**
168 * Obtains a {@code LocatorRepresentation} instance which corresponds to the
169 * specified prefix extracted from the textual representation of a locator.
170 * If this is not possible, an exception is thrown.
171 *
172 * @param locatorPrefix the prefix identifying the concrete locator type
173 * @return the corresponding {@code LocatorRepresentation} instance
174 * @throws ConversionException if no fitting instance can be found
175 */
176 private LocatorRepresentation getRepresentation(String locatorPrefix)
177 {
178 try
179 {
180 return LocatorRepresentation.valueOf(locatorPrefix
181 .toUpperCase(Locale.ENGLISH));
182 }
183 catch (IllegalArgumentException iex)
184 {
185 throw new ConversionException("Failed conversion to a Locator! "
186 + "Unknown prefix: " + locatorPrefix);
187 }
188 }
189
190 /**
191 * Converts the given textual representation of a locator to a concrete
192 * {@link Locator} instance.
193 *
194 * @param rep the string-based representation of the locator
195 * @return the corresponding {@link Locator} instance
196 * @throws ConversionException if conversion is not possible
197 */
198 private Locator convertLocator(String rep)
199 {
200 int pos = rep.indexOf(PREFIX_SEPARATOR);
201 if (pos < 0)
202 {
203 throw new ConversionException(
204 "Invalid syntax of a Locator representation: '" + rep
205 + "'! Cannot find a prefix for the Locator type.");
206 }
207 if (pos == rep.length() - 1)
208 {
209 throw new ConversionException("No data provided for locator: "
210 + rep);
211 }
212
213 LocatorRepresentation locRep = getRepresentation(rep.substring(0, pos));
214 try
215 {
216 return locRep.createLocator(rep.substring(pos + 1),
217 getClassLoaderProvider());
218 }
219 catch (LocatorException lex)
220 {
221 throw new ConversionException("Conversion to Locator failed for "
222 + rep, lex);
223 }
224 }
225
226 /**
227 * An internally used enumeration class for the text representations of
228 * locators. This class is used to identify the desired locator type and to
229 * create a corresponding instance.
230 */
231 private static enum LocatorRepresentation
232 {
233 /** Constant for a class path locator. */
234 CLASSPATH
235 {
236 /**
237 * Creates a {@link ClassPathLocator}. The passed in data is
238 * interpreted as the resource name. If it contains a class loader
239 * name (separated by a semicolon), this class loader is obtained
240 * from the given {@code ClassLoaderProvider} and passed to the
241 * newly created {@code Locator}.
242 *
243 * @param data data for the locator
244 * @param clp the {@code ClassLoaderProvider}
245 * @return the new locator instance
246 */
247 @Override
248 public Locator createLocator(String data, ClassLoaderProvider clp)
249 {
250 String resourceName;
251 String clName;
252 int posCLName = data.indexOf(CL_SEPARATOR);
253 if (posCLName > 0)
254 {
255 resourceName = data.substring(0, posCLName);
256 clName = data.substring(posCLName + 1);
257 }
258 else
259 {
260 resourceName = data;
261 clName = null;
262 }
263 ClassLoader cl =
264 (clp != null) ? clp.getClassLoader(clName) : null;
265
266 return ClassPathLocator.getInstance(resourceName, cl);
267 }
268 },
269
270 /** Constant for a file locator. */
271 FILE
272 {
273 /**
274 * Creates a {@link FileLocator}. The passed in data is interpreted
275 * as the file name.
276 *
277 * @param data data for the locator
278 * @param clp the {@code ClassLoaderProvider}
279 * @return the new locator instance
280 */
281 @Override
282 public Locator createLocator(String data, ClassLoaderProvider clp)
283 {
284 return FileLocator.getInstance(data);
285 }
286 },
287
288 /** Constant for a URL locator. */
289 URL
290 {
291 /**
292 * Creates a {@link URLLocator}. The passed in data is interpreted
293 * as the text representation of the URL.
294 *
295 * @param data data for the locator
296 * @param clp the {@code ClassLoaderProvider}
297 * @return the new locator instance
298 */
299 @Override
300 public Locator createLocator(String data, ClassLoaderProvider clp)
301 {
302 return URLLocator.getInstance(data);
303 }
304 };
305
306 /**
307 * Creates the concrete {@link Locator} implementation represented by
308 * this object. The passed in data was read from the specification
309 * string. It is provided to the locator.
310 *
311 * @param data the data for the new locator
312 * @param clp the provider for class loaders
313 * @return the locator instance
314 */
315 public abstract Locator createLocator(String data,
316 ClassLoaderProvider clp);
317 }
318 }