View Javadoc
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&lt;BeanA, BeanA&gt; 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&lt;BeanA, BeanA&gt; 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 }