2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /*
19   * $Id: AbstractCommand.java 473459 2006-11-10 20:30:12Z gvanmatre $
20   */
21  package org.apache.shale.clay.component.chain;
22  
23  import java.net.URL;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.TreeMap;
30  import java.util.TreeSet;
31  
32  import org.apache.commons.chain.Catalog;
33  import org.apache.commons.chain.CatalogFactory;
34  import org.apache.commons.chain.Command;
35  import org.apache.commons.chain.Context;
36  import org.apache.commons.chain.config.ConfigParser;
37  import org.apache.shale.clay.config.Globals;
38  import org.apache.shale.clay.config.beans.SymbolBean;
39  import org.apache.shale.util.Messages;
40  import org.apache.shale.util.Tags;
41  
42  /***
43   * <p>
44   * The base class for the commands that create the component tree.
45   * </p>
46   */
47  public abstract class AbstractCommand implements Command {
48  
49      /***
50       * <p>
51       * Message resources for this class.
52       * </p>
53       */
54      private static Messages messages = new Messages(
55              "org.apache.shale.clay.Bundle", AbstractCommand.class
56              .getClassLoader());
57  
58      /***
59       * @return message resources
60       */
61      protected static Messages getMessages() {
62         return messages;
63      }
64  
65      /***
66       * <p>Shale tag helper class that contains utility methods for setting
67       * component binding and method properties.</p>
68       */
69      private Tags tagUtils = new Tags();
70  
71      /***
72       * @return Shale tag helper class that contains utility methods for setting
73       * component binding and method properties.
74       */
75      protected Tags getTagUtils() {
76          return tagUtils;
77      }
78  
79      /***
80       * <p>
81       * Returns the {@link Catalog} with a name of
82       * <code>Globals.CLAY_CATALOG_NAME</code> in the
83       * <code>Globals.CLAY_RESOURCE_NAME</code> configuration file.
84       * </p>
85       *
86       * @return commons chains catalog
87       * @exception Exception finding catalog
88       */
89      protected Catalog getCatalog() throws Exception {
90  
91          Catalog catalog = CatalogFactory.getInstance().getCatalog(
92                  Globals.CLAY_CATALOG_NAME);
93          if (catalog == null) {
94  
95              ConfigParser parser = new ConfigParser();
96              URL url = this.getClass().getClassLoader().getResource(
97                      Globals.CLAY_RESOURCE_NAME);
98              if (url == null) {
99                  throw new IllegalArgumentException(Globals.CLAY_RESOURCE_NAME);
100             }
101             parser.parse(url);
102 
103             catalog = CatalogFactory.getInstance().getCatalog(
104                     Globals.CLAY_CATALOG_NAME);
105 
106         }
107 
108         return catalog;
109 
110     }
111 
112     /***
113      * <p>
114      * Returns the {@link Catalog} with a name identified by the
115      * constant <code>Globals.CLAY_CUSTOMIZATION_CATALOG_NAME</code>.
116      * </p>
117      *
118      * @return commons chains customizations catalog
119      * @exception Exception finding customizations catalog
120      */
121     protected Catalog getCustomizationCatalog() throws Exception {
122 
123         Catalog catalog = CatalogFactory.getInstance().getCatalog(
124                 Globals.CLAY_CUSTOMIZATION_CATALOG_NAME);
125 
126         return catalog;
127     }
128 
129     /***
130      * <p>
131      * This call is used to substitue an attribute binding expression containing
132      * the <code>symbols</code> with the target property value in the {@link ClayContext}.
133      * The current attribute within the context is assumed.
134      * </p>
135      *
136      * @param context holding the symbols and the target attribute
137      * @return string with the symbols replaces
138      */
139     public static String replaceMnemonic(ClayContext context) {
140        return replaceMnemonic(context, context.getAttribute().getValue());
141     }
142 
143     /***
144      * <p>Evaluates nested symbols.  These are symbols that have references to other
145      * symbols as their values.  The nested symbols evaluation is sensitive to dependencies.
146      * The current scoped symbol table is found in the {@link ClayContext}.</p>
147      *
148      * @param context holding the symbols
149      */
150     public static void realizeSymbols(ClayContext context) {
151         Map symbols = context.getSymbols();
152         if (symbols == null || symbols.isEmpty()) {
153             return;
154         }
155 
156         Iterator si = symbols.entrySet().iterator();
157 
158         Map dependenciesMap = new TreeMap();
159         TreeSet allNestedSymbols = new TreeSet();
160 
161         while (si.hasNext()) {
162             Map.Entry e = (Map.Entry) si.next();
163             SymbolBean symbol = (SymbolBean) e.getValue();
164             if (symbol.getValue() != null) {
165                 List symbolDependencies = findSymbols(context, symbol.getValue());
166                 if (!symbolDependencies.isEmpty()) {
167                     dependenciesMap.put(symbol.getName(), symbolDependencies);
168                     allNestedSymbols.addAll(symbolDependencies);
169                     allNestedSymbols.add(symbol.getName());
170                 }
171             }
172         }
173 
174         List allNestedSymbolsOrdered = getOrderedByDependencies(allNestedSymbols, dependenciesMap);
175         for (int i = 0; i < allNestedSymbolsOrdered.size(); i++) {
176             String symbolName = (String) allNestedSymbolsOrdered.get(i);
177             SymbolBean sourceSymbol = (SymbolBean) symbols.get(symbolName);
178             if (sourceSymbol != null && sourceSymbol.getValue() != null) {
179                 String value =  replaceMnemonic(context, sourceSymbol.getValue());
180                 SymbolBean targetSymbol = new SymbolBean();
181                 targetSymbol.setDescription(sourceSymbol.getDescription());
182                 targetSymbol.setName(sourceSymbol.getName());
183                 targetSymbol.setValue(value);
184 
185                 symbols.put(targetSymbol.getName(), targetSymbol);
186             }
187         }
188 
189     }
190 
191     /***
192      * <p>Orders the symbols by their dependencies.  Each symbol
193      * can have multiple dependencies.</p>
194      *
195      * @param allNestedSymbols Set of symbol names
196      * @param dependenciesMap List of dependencies for each symbol
197      * @return ordered list of symbol names ordered by dependencies
198      */
199     private static List getOrderedByDependencies(Set allNestedSymbols, Map dependenciesMap) {
200 
201         List tmpList = new ArrayList(allNestedSymbols);
202 
203         ordered: for (int i = 0; i < tmpList.size(); i++) {
204             boolean swap = false;
205             for (int j = 0; j < tmpList.size(); j++) {
206                 String symbolName = (String) tmpList.get(j);
207                 List symbolDependencies  = (List) dependenciesMap.get(symbolName);
208                 if (symbolDependencies != null && symbolDependencies.size() > 0) {
209                     int max = -1;
210                     for (int n = 0; n < symbolDependencies.size(); n++) {
211                         max = Math.max(max, tmpList.indexOf(symbolDependencies.get(n)));
212                     }
213                     if (max > j) {
214                         String tmp = (String) tmpList.get(j);
215                         tmpList.remove(j);
216                         tmpList.add(max, tmp);
217                         swap = true;
218                         j = max;
219                     }
220                 }
221 
222             }
223             if (!swap) {
224                 break ordered;
225             }
226         }
227 
228         return tmpList;
229 
230     }
231 
232     /***
233      * <p>Returns a List of symbols found within the <code>symbolToken</code>.
234      * The comprehensive symbol table is found in the {@link ClayContext}.</p>
235      *
236      * @param context commons chains
237      * @param symbolToken target token having nested symbols
238      * @return list of nested symbols withing the target symbolToken
239      */
240     private static List findSymbols(ClayContext context, String symbolToken) {
241 
242         List targetList = new ArrayList();
243 
244         StringBuffer buff = new StringBuffer(symbolToken);
245         Map symbols = context.getSymbols();
246         Iterator si = symbols.entrySet().iterator();
247         boolean wasSymbolFound = false;
248         int i = buff.indexOf("@");
249         replace: while (i > -1 && si.hasNext()) {
250             Map.Entry e = (Map.Entry) si.next();
251             SymbolBean symbol = (SymbolBean) e.getValue();
252             String key = symbol.getName();
253             i = (wasSymbolFound ? buff.indexOf("@") : i);
254             if (i == -1) {
255                 break replace;
256             }
257 
258             next: while (i > -1 && i <= (buff.length() - key.length())) {
259 
260                 int n = -1;
261                 indexOf: for (int s = i; s <= (buff.length() - key.length()); s++) {
262                     for (int c = 0; c < key.length(); c++) {
263                         char skey = Character.toLowerCase(key.charAt(c));
264                         char tkey = Character.toLowerCase(buff.charAt(s + c));
265                         if (skey != tkey) {
266                             continue indexOf;
267                         }
268                     }
269                     // match found
270                     n = s;
271                     break indexOf;
272                 }
273 
274                 if (n > -1) {
275                     if (!targetList.contains(key)) {
276                         targetList.add(key);
277                     }
278                     i = n + key.length();
279                     wasSymbolFound = true;
280                 } else {
281                     break next;
282                 }
283             }
284 
285             e = null;
286             key = null;
287         }
288         symbols = null;
289         si = null;
290 
291         return targetList;
292     }
293 
294     /***
295      * <p>
296      * This call is used to substitue an attribute binding expression containing
297      * the <code>symbols</code> within the <code>sybmolToken</code>.
298      * </p>
299      *
300      * @param context commons chains holding the substitution symbols
301      * @param symbolToken target token having nested symbols
302      * @return value with the symbols replaced
303      */
304     public static String replaceMnemonic(ClayContext context, String symbolToken) {
305 
306         StringBuffer buff = new StringBuffer(symbolToken);
307         Map symbols = context.getSymbols();
308         Iterator si = symbols.entrySet().iterator();
309         boolean wasReplacementMade = false;
310         int i = buff.indexOf("@");
311         replace: while (i > -1 && si.hasNext()) {
312             Map.Entry e = (Map.Entry) si.next();
313             SymbolBean symbol = (SymbolBean) e.getValue();
314             String key = symbol.getName();
315             String value = (symbol.getValue() == null ? "" : symbol.getValue());
316             i = (wasReplacementMade ? buff.indexOf("@") : i);
317             if (i == -1) {
318               break replace;
319             }
320 
321             next: while (i > -1 && i <= (buff.length() - key.length())) {
322 
323                 int n = -1;
324                 indexOf: for (int s = i; s <= (buff.length() - key.length()); s++) {
325                     for (int c = 0; c < key.length(); c++) {
326                         char skey = Character.toLowerCase(key.charAt(c));
327                         char tkey = Character.toLowerCase(buff.charAt(s + c));
328                         if (skey != tkey) {
329                             continue indexOf;
330                         }
331                     }
332                     // match found
333                     n = s;
334                     break indexOf;
335                 }
336 
337                 if (n > -1) {
338                     buff.delete(n, n + key.length());
339                     buff.insert(n, value);
340                     i = n + value.length();
341                     wasReplacementMade = true;
342                 } else {
343                     break next;
344                 }
345             }
346 
347             e = null;
348             key = null;
349             value = null;
350         }
351         symbols = null;
352         si = null;
353 
354         //if a replacement was made and the target length is zero,
355         //return a null value; otherwise, an empty string.  Components
356         //like the "selectItem" need to be able to set a empty string
357         //value for the "itemLabel" or "itemValue" attributes.
358         if (buff.length() == 0 && wasReplacementMade) {
359            return null;
360         } else {
361            return buff.toString();
362         }
363 
364     }
365 
366     /***
367      * <p>
368      * This method comes from the <code>Command</code> interfaces. This method is
369      * invoked while executing the <code>Chain</code>.
370      * </p>
371      *
372      * @param context commons chains
373      * @return <code>true</code> if the chain is done
374      * @exception Exception checked
375      */
376     public abstract boolean execute(Context context) throws Exception;
377 
378     /***
379      * <p>Return true if the specified string contains an EL expression.</p>
380      *
381      * <p>This is taken almost verbatim from {@link javax.faces.webapp.UIComponentTag}
382      * in order to remove JSP dependencies from the renderers.</p>
383      *
384      * @param value String to be checked for being an expression
385      * @return <code>true</code> if the value is a binding expression
386      */
387     protected boolean isValueReference(String value) {
388 
389         if (value == null) {
390             return false;
391         }
392 
393         int start = value.indexOf("#{");
394         if (start < 0) {
395             return false;
396         }
397 
398         int end = value.lastIndexOf('}');
399         return (end >= 0) && (start < end);
400     }
401 
402 }
403