1 /*
2 * #%L
3 * Nuiton Utils
4 * %%
5 * Copyright (C) 2004 - 2010 CodeLutin
6 * %%
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Lesser Public License for more details.
16 *
17 * You should have received a copy of the GNU General Lesser Public
18 * License along with this program. If not, see
19 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20 * #L%
21 */
22 package org.nuiton.util.beans;
23
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Set;
31
32 /**
33 * Factory of {@link Binder}.
34 *
35 * <h1>Obtain a new binder</h1>
36 * To obtain a new binder you can use the {@code newBinder(XXX)} methods.
37 *
38 * For example to obtain a mirrored binder (same source and target type) which
39 * will be able to copy all accepting properties, use this code :
40 * <pre>
41 * Binder<BeanA, BeanA> binder = BinderFactory.newBinder(BeanA.class);
42 * </pre>
43 * <h1>Usage of contextale binder</h1>
44 * It is possible to use different binder for same source and target type, using a
45 * extra context name parameter, like this :
46 * <pre>
47 * Binder<BeanA, BeanA> binder = BinderFactory.newBinder(BeanA.class, "mycontext");
48 * </pre>
49 *
50 * This is usefull when you register your own binder model in the factory (see
51 * next section) to bind different things from the same type of objects...
52 *
53 * <h1>Register a new binder model</h1>
54 * To register a new binder's model use one of the method {@code registerBinderModel(XXX)}.
55 *
56 *
57 * More documentation will come soon, yu can see the package info javadoc or
58 * unit tests...
59 *
60 * @author Tony Chemit - chemit@codelutin.com
61 * @since 1.5.3
62 */
63 public class BinderFactory {
64
65 /** Logger. */
66 private static final Log log = LogFactory.getLog(BinderFactory.class);
67
68 /** Cache of registred binders indexed by their unique entry */
69 protected static BindelModelEntryMap binderModels;
70
71 /**
72 * Gets the registred mirror binder (source type = target type) with no
73 * context name specified.
74 *
75 * @param sourceType the type of source and target
76 * @param <S> the type of source and target
77 * @return the registred binder or {@code null} if not found.
78 */
79 public static <S> Binder<S, S> newBinder(Class<S> sourceType) {
80 return newBinder0(sourceType, sourceType, null, Binder.class);
81 }
82
83 /**
84 * Gets the registred mirror binder (source type = target type) with the
85 * given context name.
86 *
87 * @param sourceType the type of source and target
88 * @param contextName the context's name of the searched binder
89 * @param <S> the type of source and target
90 * @return the registred binder or {@code null} if not found.
91 */
92 public static <S> Binder<S, S> newBinder(Class<S> sourceType,
93 String contextName) {
94 return newBinder0(sourceType, sourceType, contextName, Binder.class);
95 }
96
97 /**
98 * Gets the registred binder given his types with no context name.
99 *
100 * @param sourceType the type of source
101 * @param targetType the type of target
102 * @param <S> the type of source
103 * @param <T> the type of target
104 * @return the registred binder or {@code null} if not found.
105 */
106 public static <S, T> Binder<S, T> newBinder(Class<S> sourceType,
107 Class<T> targetType) {
108 return newBinder0(sourceType, targetType, null, Binder.class);
109 }
110
111
112 /**
113 * Gets the registred binder given his types with no context name.
114 *
115 * @param sourceType the type of source
116 * @param targetType the type of target
117 * @param contextName the context's name of the searched binder
118 * @param <S> the type of source
119 * @param <T> the type of target
120 * @return the registred binder or {@code null} if not found.
121 */
122 public static <S, T> Binder<S, T> newBinder(Class<S> sourceType,
123 Class<T> targetType,
124 String contextName) {
125 return newBinder0(sourceType, targetType, contextName, Binder.class);
126 }
127
128 /**
129 * Gets the registred binder given his types and his context's name.
130 *
131 * @param sourceType the type of source
132 * @param targetType the type of target
133 * @param contextName the context's name of the searched binder
134 * @param binderType type of binder required
135 * @param <S> the type of source
136 * @param <T> the type of target
137 * @param <B> the type of binder
138 * @return the new instanciated binder.
139 */
140 public static <S, T, B extends Binder<S, T>> B newBinder(Class<S> sourceType,
141 Class<T> targetType,
142 String contextName,
143 Class<B> binderType) {
144 B binder = (B) newBinder0(sourceType, targetType, contextName, binderType);
145 return binder;
146 }
147
148 public static <S, T> Binder.BinderModel<S, T> registerBinderModel(BinderModelBuilder<S, T> binderModelBuilder) throws IllegalArgumentException {
149 Binder.BinderModel<S, T> model = registerBinderModel(binderModelBuilder, null);
150 return model;
151 }
152
153 public static <S, T> Binder.BinderModel<S, T> registerBinderModel(Binder<S, T> binder) throws IllegalArgumentException {
154 Binder.BinderModel<S, T> model = registerBinderModel(binder, null);
155 return model;
156 }
157
158 public static <S, T> Binder.BinderModel<S, T> registerBinderModel(Binder.BinderModel<S, T> model) throws IllegalArgumentException {
159
160 registerBinderModel(model, null);
161 return model;
162 }
163
164 public static <S, T> Binder.BinderModel<S, T> registerBinderModel(BinderModelBuilder<S, T> binderModelBuilder,
165 String contextName) throws IllegalArgumentException {
166 Binder.BinderModel<S, T> model = binderModelBuilder.getModel();
167 registerBinderModel(model, contextName);
168 return model;
169 }
170
171 public static <S, T> Binder.BinderModel<S, T> registerBinderModel(Binder<S, T> binder,
172 String contextName) throws IllegalArgumentException {
173 Binder.BinderModel<S, T> model = binder.getModel();
174 registerBinderModel(model, contextName);
175 return model;
176 }
177
178 public static <S, T> Binder.BinderModel<S, T> registerBinderModel(Binder.BinderModel<S, T> model,
179 String contextName) throws IllegalArgumentException {
180
181 // check if the given model is not already registred for the given context
182 Binder.BinderModel<S, T> registredModel =
183 getBinderModels().get(model, contextName);
184
185 // let's add this model into cache of models
186 BinderModelEntry key = new BinderModelEntry(model, contextName);
187
188 if (registredModel != null) {
189
190 // this model is already registred, remove it from cache
191 if (log.isWarnEnabled()) {
192 log.warn("Remove existing binder model from cache : " +
193 toString(registredModel, contextName));
194 }
195 }
196
197 // add new model into cache
198 getBinderModels().put(key, model);
199 return model;
200 }
201
202 /**
203 * Given a {@code model} and a {@code binderType}, instanciate a new binder
204 * and returns it.
205 *
206 * <strong>Note: </strong> This method will <strong>NOT</strong> register
207 * the model in the factory. If you want to reuse your model, please use
208 * one of the {@code registerBinderModel(XXX)} method.
209 *
210 * @param model the model used by the binder
211 * @param binderType the type of the binder
212 * @param <S> the source type
213 * @param <T> the target type
214 * @param <B> the type of the binder
215 * @return the new instanciated binder
216 * @since 2.1
217 */
218 public static <S, T, B extends Binder<S, T>> B newBinder(Binder.BinderModel<S, T> model,
219 Class<B> binderType) {
220
221 B binder;
222 try {
223 binder = binderType.getConstructor().newInstance();
224 } catch (Exception e) {
225 throw new IllegalStateException(
226 "Could not instanciate binder of type " + binderType, e);
227 }
228
229 binder.setModel(model);
230 return binder;
231 }
232
233 /**
234 * Clear the cache of registred binder models.
235 *
236 * <b>Note :</b> This is a convienient method for test purposes and should
237 * be used in a normal usage of this provider.
238 */
239 public static void clear() {
240 if (binderModels != null) {
241 binderModels.clear();
242 binderModels = null;
243 }
244 }
245
246 /**
247 * Tells if there is a cached binder model for the given parameters.
248 *
249 * @param sourceType the type of source
250 * @param targetType the type of target
251 * @param contextName the context's name of the searched binder
252 * @param <S> the type of source
253 * @param <T> the type of target
254 * @return {@code true} if there is a cached binder model for the given
255 * parameters, {@code false} otherwise.
256 */
257 public static <S, T> boolean isBinderModelExists(Class<S> sourceType,
258 Class<T> targetType,
259 String contextName) {
260 Binder.BinderModel<S, T> model =
261 getBinderModels().get(sourceType, targetType, contextName);
262 return model != null;
263 }
264
265 /**
266 * Obtain a cached binder model.
267 *
268 * @param sourceType the type of source
269 * @param targetType the type of target
270 * @param contextName the context's name of the searched binder
271 * @param <S> the type of source
272 * @param <T> the type of target
273 * @return the cached binder model or {@code null} if not found.
274 */
275 public static <S, T> Binder.BinderModel<S, T> getCachedBinderModel(Class<S> sourceType,
276 Class<T> targetType,
277 String contextName) {
278 Binder.BinderModel<S, T> model =
279 getBinderModels().get(sourceType, targetType, contextName);
280 return model;
281 }
282
283 protected static BindelModelEntryMap getBinderModels() {
284 if (binderModels == null) {
285 binderModels = new BindelModelEntryMap();
286 }
287 return binderModels;
288 }
289
290 protected static String toString(Binder.BinderModel<?, ?> model, String contextName) {
291 return toString(model.getSourceType(), model.getTargetType(), contextName);
292 }
293
294 protected static String toString(Class<?> sourceType, Class<?> targetType, String contextName) {
295 return "<" + sourceType.getName() + " - " + targetType.getName() + " > [" + contextName + "] ";
296 }
297
298 /**
299 * Instanciate a new binder given his types and his context's name.
300 *
301 * If the corresponding binder model does not exist, then it will be created
302 * and cached (using the {@link BinderModelBuilder#newDefaultBuilder(Class, Class)} method).
303 *
304 * @param sourceType the type of source
305 * @param targetType the type of target
306 * @param contextName the context's name of the searched binder
307 * @param binderType type of binder required
308 * @param <S> the type of source
309 * @param <T> the type of target
310 * @param <B> the type of binder
311 * @return the new instanciated binder.
312 */
313 protected static <S, T, B extends Binder<S, T>> Binder<S, T> newBinder0(Class<S> sourceType,
314 Class<T> targetType,
315 String contextName,
316 Class<B> binderType) {
317
318 // obtain the cached model
319 Binder.BinderModel<S, T> model =
320 getBinderModels().get(sourceType, targetType, contextName);
321
322 if (model == null) {
323
324 // model not yet registred, let's create it
325
326 if (log.isInfoEnabled()) {
327 log.info("No binder model found for " +
328 toString(sourceType, targetType, contextName) +
329 ", will create a new default one.");
330 }
331
332 BinderModelBuilder<S, T> builder =
333 BinderModelBuilder.newDefaultBuilder(sourceType, targetType);
334
335 // register the new binder model
336 model = registerBinderModel(builder, contextName);
337 }
338
339 B binder = newBinder(model, binderType);
340 return binder;
341 }
342
343 public static class BindelModelEntryMap implements Map<BinderModelEntry, Binder.BinderModel<?, ?>> {
344
345 protected final Map<BinderModelEntry, Binder.BinderModel<?, ?>> delegate;
346
347 public BindelModelEntryMap() {
348 delegate = new HashMap<BinderModelEntry, Binder.BinderModel<?, ?>>();
349 }
350
351 public <S, T> Binder.BinderModel<S, T> get(Class<S> source,
352 Class<T> target,
353 String contextName) {
354 Binder.BinderModel<S, T> result = null;
355
356 for (BinderModelEntry key : binderModels.keySet()) {
357 if (!key.getSourceType().equals(source)) {
358 continue;
359 }
360 if (!key.getTargetType().equals(target)) {
361 continue;
362 }
363
364 if (key.getName() == null) {
365 if (contextName != null) {
366 continue;
367 }
368 } else {
369 if (!key.getName().equals(contextName)) {
370 continue;
371 }
372 }
373
374 result = (Binder.BinderModel<S, T>) binderModels.get(key);
375 break;
376 }
377 return result;
378 }
379
380 public <S, T> Binder.BinderModel<S, T> get(Binder.BinderModel<S, T> model,
381 String contextName) {
382
383 Class<S> source = model.getSourceType();
384 Class<T> target = model.getTargetType();
385 Binder.BinderModel<S, T> result = get(source, target, contextName);
386 return result;
387 }
388
389 @Override
390 public int size() {
391 return delegate.size();
392 }
393
394 @Override
395 public boolean isEmpty() {
396 return delegate.isEmpty();
397 }
398
399 @Override
400 public boolean containsKey(Object key) {
401 return delegate.containsKey(key);
402 }
403
404 @Override
405 public boolean containsValue(Object value) {
406 return delegate.containsValue(value);
407 }
408
409 @Override
410 public Binder.BinderModel<?, ?> get(Object key) {
411 return delegate.get(key);
412 }
413
414 public Binder.BinderModel<?, ?> put(BinderModelEntry key, Binder.BinderModel<?, ?> value) {
415 return delegate.put(key, value);
416 }
417
418 @Override
419 public Binder.BinderModel<?, ?> remove(Object key) {
420 return delegate.remove(key);
421 }
422
423 public void putAll(Map<? extends BinderModelEntry, ? extends Binder.BinderModel<?, ?>> m) {
424 delegate.putAll(m);
425 }
426
427 @Override
428 public void clear() {
429 delegate.clear();
430 }
431
432 @Override
433 public Set<BinderModelEntry> keySet() {
434 return delegate.keySet();
435 }
436
437 @Override
438 public Collection<Binder.BinderModel<?, ?>> values() {
439 return delegate.values();
440 }
441
442 @Override
443 public Set<Entry<BinderModelEntry, Binder.BinderModel<?, ?>>> entrySet() {
444 return delegate.entrySet();
445 }
446
447 }
448
449 /**
450 * Definition of an binder model entry (source and target types + context name).
451 *
452 * <b>Note :</b>When no context is specified, we always use a
453 * {@code null} context name.
454 */
455 public static class BinderModelEntry {
456
457 protected final Class<?> sourceType;
458
459 protected final Class<?> targetType;
460
461 protected final String name;
462
463 public BinderModelEntry(Class<?> sourceType,
464 Class<?> targetType,
465 String name) {
466 this.sourceType = sourceType;
467 this.targetType = targetType;
468 this.name = name;
469 }
470
471 public BinderModelEntry(Binder.BinderModel<?, ?> model, String contextName) {
472 this(model.getSourceType(), model.getTargetType(), contextName);
473 }
474
475 public Class<?> getSourceType() {
476 return sourceType;
477 }
478
479 public Class<?> getTargetType() {
480 return targetType;
481 }
482
483 public String getName() {
484 return name;
485 }
486
487 @Override
488 public boolean equals(Object o) {
489 if (this == o) {
490 return true;
491 }
492 if (o == null || getClass() != o.getClass()) {
493 return false;
494 }
495
496 BinderModelEntry that = (BinderModelEntry) o;
497
498 return (name == null ? that.name == null : name.equals(that.name)) &&
499 sourceType.equals(that.sourceType) &&
500 targetType.equals(that.targetType);
501 }
502
503 @Override
504 public int hashCode() {
505 int result = sourceType.hashCode();
506 result = 31 * result + targetType.hashCode();
507 result = 31 * result + (name != null ? name.hashCode() : 0);
508 return result;
509 }
510
511 @Override
512 public String toString() {
513 StringBuilder buffer = new StringBuilder("<");
514 buffer.append(super.toString());
515 buffer.append(", sourceType: ").append(getSourceType()).append(',');
516 buffer.append(" targetType: ").append(getTargetType()).append(',');
517 buffer.append(" name: ").append(getName()).append('>');
518
519 return buffer.toString();
520 }
521 }
522 }