View Javadoc
1   /*
2    * #%L
3    * Nuiton Decorator
4    * %%
5    * Copyright (C) 2011 CodeLutin, Tony Chemit
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.decorator;
23  
24  import org.apache.commons.jxpath.JXPathContext;
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import java.io.Serializable;
29  import java.util.Arrays;
30  import java.util.Comparator;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  
35  /**
36   * JXPath decorator based on {@link String#format(String, Object...)} method.
37   * <p/>
38   * To use it, give to him a expression where all jxpath to apply on bean are
39   * boxed in <code>${}</code>.
40   * <p/>
41   * After the jxpath token you must specifiy the formatter to apply of the
42   * jxpath token.
43   * <p/>
44   * For example :
45   * <pre>
46   * Decorator&lt;Object&gt; d = DecoratorUtil.newJXPathDecorator(
47   * JXPathDecorator.class,"expr = ${expressions}$s");
48   * assert "expr = %1$s" == d.getExpression();
49   * assert 1 == d.getNbToken();
50   * assert java.util.Arrays.asList("expression") == d.getTokens();
51   * assert "expr = %1$s" == d.toString(d);
52   * </pre>
53   *
54   * @param <O> type of data to decorate
55   * @author tchemit <chemit@codelutin.com>
56   * @see Decorator
57   * @since 2.3
58   */
59  public class JXPathDecorator<O> extends Decorator<O> {
60  
61      private static final long serialVersionUID = 1L;
62  
63      /** Logger */
64      private static final Log log = LogFactory.getLog(JXPathDecorator.class);
65  
66      /** the computed context of the decorator */
67      protected Context<O> context;
68  
69      /** nb jxpath tokens to compute */
70      protected int nbToken;
71  
72      /** the initial expression used to compute the decorator context. */
73      protected String initialExpression;
74  
75      protected JXPathDecorator(Class<O> internalClass,
76                                String expression,
77                                Context<O> context)
78              throws IllegalArgumentException, NullPointerException {
79          super(internalClass);
80          initialExpression = expression;
81          if (context != null) {
82              setContext(context);
83          }
84      }
85  
86      @Override
87      public String toString(Object bean) {
88          if (bean == null) {
89              return null;
90          }
91          JXPathContext jxcontext = JXPathContext.newContext(bean);
92          Object[] args = new Object[nbToken];
93  
94          for (int i = 0; i < nbToken; i++) {
95              try {
96                  args[i] = getTokenValue(jxcontext, context.tokens[i]);
97              } catch (Exception e) {
98                  if (log.isErrorEnabled()) {
99                      log.error("can not obtain token " + context.tokens[i]
100                               + " on object of type " +
101                               bean.getClass().getName() +
102                               " for reason " + e.getMessage(), e);
103                 }
104 
105             }
106         }
107 
108         String result;
109         try {
110             result = String.format(context.expression, args);
111         } catch (Exception eee) {
112             if (log.isErrorEnabled()) {
113                 log.error("Could not format " + context.expression + "" +
114                           " with args : " + Arrays.toString(args), eee);
115             }
116             result = "";
117         }
118         return result;
119     }
120 
121     public String getProperty(int pos) {
122         return getTokens()[pos];
123     }
124 
125     public String getExpression() {
126         return context.expression;
127     }
128 
129     public String[] getTokens() {
130         return context.tokens;
131     }
132 
133     public int getNbToken() {
134         return nbToken;
135     }
136 
137     public String getInitialExpression() {
138         return initialExpression;
139     }
140 
141     @Override
142     public String toString() {
143         return super.toString() + '<' + context + '>';
144     }
145 
146     public void setContext(Context<O> context) {
147         this.context = context;
148         nbToken = context.tokens.length;
149         // always reset comparator
150         //this.context.comparator = null;
151         if (log.isDebugEnabled()) {
152             log.debug(context);
153         }
154     }
155 
156     @SuppressWarnings({"unchecked"})
157     protected Comparator<O> getComparator(int pos) {
158         ensureTokenIndex(this, pos);
159         return context.getComparator(pos);
160     }
161 
162     @SuppressWarnings({"unchecked"})
163     protected Comparable<Comparable<?>> getTokenValue(JXPathContext jxcontext,
164                                                       String token) {
165         // assume all values are comparable
166         return (Comparable<Comparable<?>>) jxcontext.getValue(token);
167     }
168 
169     public static class JXPathComparator<O> implements Comparator<O> {
170 
171         protected Map<O, Comparable<Comparable<?>>> valueCache;
172 
173         private final String expression;
174 
175         public JXPathComparator(String expression) {
176             this.expression = expression;
177             valueCache = new HashMap<O, Comparable<Comparable<?>>>();
178         }
179 
180         @Override
181         public int compare(O o1, O o2) {
182             Comparable<Comparable<?>> c1 = valueCache.get(o1);
183             Comparable<Comparable<?>> c2 = valueCache.get(o2);
184             if (c1 == null) {
185                 if (c2 == null) {
186                     return 0;
187                 }
188                 return 1;
189             }
190             if (c2 == null) {
191                 return -1;
192             }
193             return c1.compareTo(c2);
194         }
195 
196         public void clear() {
197             valueCache.clear();
198         }
199 
200         public void init(JXPathDecorator<O> decorator, List<O> datas) {
201             clear();
202             for (O data : datas) {
203                 JXPathContext jxcontext = JXPathContext.newContext(data);
204                 Comparable<Comparable<?>> key;
205                 key = decorator.getTokenValue(jxcontext, expression);
206                 valueCache.put(data, key);
207             }
208         }
209     }
210 
211     public static class Context<O> implements Serializable {
212 
213         /**
214          * expression to format using {@link String#format(String, Object...)},
215          * all variables are compute using using the jxpath tokens.
216          */
217         protected String expression;
218 
219         /** list of jxpath tokens to apply on expression */
220         protected String[] tokens;
221 
222         protected transient Comparator<O> comparator;
223 
224         private static final long serialVersionUID = 1L;
225 
226         public Context(String expression, String[] tokens) {
227             this.expression = expression;
228             this.tokens = tokens;
229         }
230 
231         public String getFirstProperty() {
232             return tokens[0];
233         }
234 
235         public Comparator<O> getComparator(int pos) {
236             if (comparator == null) {
237                 comparator = new JXPathComparator<O>(tokens[pos]);
238             }
239             return comparator;
240         }
241 
242         public void setComparator(Comparator<O> comparator) {
243             this.comparator = comparator;
244         }
245 
246         @Override
247         public String toString() {
248             return "<expression:" + expression + ", tokens:" +
249                    Arrays.toString(tokens) + '>';
250         }
251     }
252 
253     protected static void ensureTokenIndex(JXPathDecorator<?> decorator, int pos) {
254         if (pos < -1 || pos > decorator.getNbToken()) {
255             throw new ArrayIndexOutOfBoundsException(
256                     "token index " + pos + " is out of bound, can be inside [" +
257                     0 + ',' + decorator.nbToken + ']');
258         }
259     }
260 }