KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > digester > SetPropertiesRule


1 /* $Id: SetPropertiesRule.java 155412 2005-02-26 12:58:36Z dirkv $
2  *
3  * Copyright 2001-2004 The Apache Software Foundation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * 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 package org.apache.commons.digester;
20
21
22 import java.util.HashMap JavaDoc;
23
24 import org.apache.commons.beanutils.BeanUtils;
25 import org.apache.commons.beanutils.PropertyUtils;
26 import org.xml.sax.Attributes JavaDoc;
27
28
29 /**
30  * <p>Rule implementation that sets properties on the object at the top of the
31  * stack, based on attributes with corresponding names.</p>
32  *
33  * <p>This rule supports custom mapping of attribute names to property names.
34  * The default mapping for particular attributes can be overridden by using
35  * {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}.
36  * This allows attributes to be mapped to properties with different names.
37  * Certain attributes can also be marked to be ignored.</p>
38  */

39
40 public class SetPropertiesRule extends Rule {
41
42
43     // ----------------------------------------------------------- Constructors
44

45
46     /**
47      * Default constructor sets only the the associated Digester.
48      *
49      * @param digester The digester with which this rule is associated
50      *
51      * @deprecated The digester instance is now set in the {@link Digester#addRule} method.
52      * Use {@link #SetPropertiesRule()} instead.
53      */

54     public SetPropertiesRule(Digester digester) {
55
56         this();
57
58     }
59     
60
61     /**
62      * Base constructor.
63      */

64     public SetPropertiesRule() {
65
66         // nothing to set up
67

68     }
69     
70     /**
71      * <p>Convenience constructor overrides the mapping for just one property.</p>
72      *
73      * <p>For details about how this works, see
74      * {@link #SetPropertiesRule(String[] attributeNames, String[] propertyNames)}.</p>
75      *
76      * @param attributeName map this attribute
77      * @param propertyName to a property with this name
78      */

79     public SetPropertiesRule(String JavaDoc attributeName, String JavaDoc propertyName) {
80         
81         attributeNames = new String JavaDoc[1];
82         attributeNames[0] = attributeName;
83         propertyNames = new String JavaDoc[1];
84         propertyNames[0] = propertyName;
85     }
86     
87     /**
88      * <p>Constructor allows attribute->property mapping to be overriden.</p>
89      *
90      * <p>Two arrays are passed in.
91      * One contains the attribute names and the other the property names.
92      * The attribute name / property name pairs are match by position
93      * In order words, the first string in the attribute name list matches
94      * to the first string in the property name list and so on.</p>
95      *
96      * <p>If a property name is null or the attribute name has no matching
97      * property name, then this indicates that the attibute should be ignored.</p>
98      *
99      * <h5>Example One</h5>
100      * <p> The following constructs a rule that maps the <code>alt-city</code>
101      * attribute to the <code>city</code> property and the <code>alt-state</code>
102      * to the <code>state</code> property.
103      * All other attributes are mapped as usual using exact name matching.
104      * <code><pre>
105      * SetPropertiesRule(
106      * new String[] {"alt-city", "alt-state"},
107      * new String[] {"city", "state"});
108      * </pre></code>
109      *
110      * <h5>Example Two</h5>
111      * <p> The following constructs a rule that maps the <code>class</code>
112      * attribute to the <code>className</code> property.
113      * The attribute <code>ignore-me</code> is not mapped.
114      * All other attributes are mapped as usual using exact name matching.
115      * <code><pre>
116      * SetPropertiesRule(
117      * new String[] {"class", "ignore-me"},
118      * new String[] {"className"});
119      * </pre></code>
120      *
121      * @param attributeNames names of attributes to map
122      * @param propertyNames names of properties mapped to
123      */

124     public SetPropertiesRule(String JavaDoc[] attributeNames, String JavaDoc[] propertyNames) {
125         // create local copies
126
this.attributeNames = new String JavaDoc[attributeNames.length];
127         for (int i=0, size=attributeNames.length; i<size; i++) {
128             this.attributeNames[i] = attributeNames[i];
129         }
130         
131         this.propertyNames = new String JavaDoc[propertyNames.length];
132         for (int i=0, size=propertyNames.length; i<size; i++) {
133             this.propertyNames[i] = propertyNames[i];
134         }
135     }
136         
137     // ----------------------------------------------------- Instance Variables
138

139     /**
140      * Attribute names used to override natural attribute->property mapping
141      */

142     private String JavaDoc [] attributeNames;
143     /**
144      * Property names used to override natural attribute->property mapping
145      */

146     private String JavaDoc [] propertyNames;
147
148     /**
149      * Used to determine whether the parsing should fail if an property specified
150      * in the XML is missing from the bean. Default is true for backward compatibility.
151      */

152     private boolean ignoreMissingProperty = true;
153
154
155     // --------------------------------------------------------- Public Methods
156

157
158     /**
159      * Process the beginning of this element.
160      *
161      * @param attributes The attribute list of this element
162      */

163     public void begin(Attributes JavaDoc attributes) throws Exception JavaDoc {
164         
165         // Build a set of attribute names and corresponding values
166
HashMap JavaDoc values = new HashMap JavaDoc();
167         
168         // set up variables for custom names mappings
169
int attNamesLength = 0;
170         if (attributeNames != null) {
171             attNamesLength = attributeNames.length;
172         }
173         int propNamesLength = 0;
174         if (propertyNames != null) {
175             propNamesLength = propertyNames.length;
176         }
177         
178         
179         for (int i = 0; i < attributes.getLength(); i++) {
180             String JavaDoc name = attributes.getLocalName(i);
181             if ("".equals(name)) {
182                 name = attributes.getQName(i);
183             }
184             String JavaDoc value = attributes.getValue(i);
185             
186             // we'll now check for custom mappings
187
for (int n = 0; n<attNamesLength; n++) {
188                 if (name.equals(attributeNames[n])) {
189                     if (n < propNamesLength) {
190                         // set this to value from list
191
name = propertyNames[n];
192                     
193                     } else {
194                         // set name to null
195
// we'll check for this later
196
name = null;
197                     }
198                     break;
199                 }
200             }
201             
202             if (digester.log.isDebugEnabled()) {
203                 digester.log.debug("[SetPropertiesRule]{" + digester.match +
204                         "} Setting property '" + name + "' to '" +
205                         value + "'");
206             }
207             
208             if ((!ignoreMissingProperty) && (name != null)) {
209                 // The BeanUtils.populate method silently ignores items in
210
// the map (ie xml entities) which have no corresponding
211
// setter method, so here we check whether each xml attribute
212
// does have a corresponding property before calling the
213
// BeanUtils.populate method.
214
//
215
// Yes having the test and set as separate steps is ugly and
216
// inefficient. But BeanUtils.populate doesn't provide the
217
// functionality we need here, and changing the algorithm which
218
// determines the appropriate setter method to invoke is
219
// considered too risky.
220
//
221
// Using two different classes (PropertyUtils vs BeanUtils) to
222
// do the test and the set is also ugly; the codepaths
223
// are different which could potentially lead to trouble.
224
// However the BeanUtils/ProperyUtils code has been carefully
225
// compared and the PropertyUtils functionality does appear
226
// compatible so we'll accept the risk here.
227

228                 Object JavaDoc top = digester.peek();
229                 boolean test = PropertyUtils.isWriteable(top, name);
230                 if (!test)
231                     throw new NoSuchMethodException JavaDoc("Property " + name + " can't be set");
232             }
233             
234             if (name != null) {
235                 values.put(name, value);
236             }
237         }
238
239         // Populate the corresponding properties of the top object
240
Object JavaDoc top = digester.peek();
241         if (digester.log.isDebugEnabled()) {
242             if (top != null) {
243                 digester.log.debug("[SetPropertiesRule]{" + digester.match +
244                                    "} Set " + top.getClass().getName() +
245                                    " properties");
246             } else {
247                 digester.log.debug("[SetPropertiesRule]{" + digester.match +
248                                    "} Set NULL properties");
249             }
250         }
251         BeanUtils.populate(top, values);
252
253
254     }
255
256
257     /**
258      * <p>Add an additional attribute name to property name mapping.
259      * This is intended to be used from the xml rules.
260      */

261     public void addAlias(String JavaDoc attributeName, String JavaDoc propertyName) {
262         
263         // this is a bit tricky.
264
// we'll need to resize the array.
265
// probably should be synchronized but digester's not thread safe anyway
266
if (attributeNames == null) {
267             
268             attributeNames = new String JavaDoc[1];
269             attributeNames[0] = attributeName;
270             propertyNames = new String JavaDoc[1];
271             propertyNames[0] = propertyName;
272             
273         } else {
274             int length = attributeNames.length;
275             String JavaDoc [] tempAttributes = new String JavaDoc[length + 1];
276             for (int i=0; i<length; i++) {
277                 tempAttributes[i] = attributeNames[i];
278             }
279             tempAttributes[length] = attributeName;
280             
281             String JavaDoc [] tempProperties = new String JavaDoc[length + 1];
282             for (int i=0; i<length && i< propertyNames.length; i++) {
283                 tempProperties[i] = propertyNames[i];
284             }
285             tempProperties[length] = propertyName;
286             
287             propertyNames = tempProperties;
288             attributeNames = tempAttributes;
289         }
290     }
291   
292
293     /**
294      * Render a printable version of this Rule.
295      */

296     public String JavaDoc toString() {
297
298         StringBuffer JavaDoc sb = new StringBuffer JavaDoc("SetPropertiesRule[");
299         sb.append("]");
300         return (sb.toString());
301
302     }
303
304     /**
305      * <p>Are attributes found in the xml without matching properties to be ignored?
306      * </p><p>
307      * If false, the parsing will interrupt with an <code>NoSuchMethodException</code>
308      * if a property specified in the XML is not found. The default is true.
309      * </p>
310      * @return true if skipping the unmatched attributes.
311      */

312     public boolean isIgnoreMissingProperty() {
313
314         return this.ignoreMissingProperty;
315     }
316
317     /**
318      * Sets whether attributes found in the xml without matching properties
319      * should be ignored.
320      * If set to false, the parsing will throw an <code>NoSuchMethodException</code>
321      * if an unmatched
322      * attribute is found. This allows to trap misspellings in the XML file.
323      * @param ignoreMissingProperty false to stop the parsing on unmatched attributes.
324      */

325     public void setIgnoreMissingProperty(boolean ignoreMissingProperty) {
326
327         this.ignoreMissingProperty = ignoreMissingProperty;
328     }
329
330
331 }
332
Popular Tags