KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > cojen > util > BeanPropertyAccessor


1 /*
2  * Copyright 2004 Brian S O'Neill
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.cojen.util;
18
19 import java.lang.ref.SoftReference JavaDoc;
20 import java.lang.reflect.Method JavaDoc;
21 import java.util.ArrayList JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.math.BigInteger JavaDoc;
26 import org.cojen.classfile.ClassFile;
27 import org.cojen.classfile.CodeBuilder;
28 import org.cojen.classfile.Label;
29 import org.cojen.classfile.LocalVariable;
30 import org.cojen.classfile.MethodInfo;
31 import org.cojen.classfile.Modifiers;
32 import org.cojen.classfile.Opcode;
33 import org.cojen.classfile.TypeDesc;
34
35 /**
36  * Provides a simple and efficient means of reading and writing bean
37  * properties. BeanPropertyAccessor auto-generates code, eliminating the
38  * need to invoke methods via reflection. Bean access methods are bound-to
39  * directly, using a special hash/switch design pattern.
40  *
41  * @author Brian S O'Neill
42  */

43 public abstract class BeanPropertyAccessor {
44     // Maps classes to softly referenced BeanPropertyAccessors.
45
private static Map JavaDoc cAccessors = new WeakIdentityMap();
46
47     /**
48      * Returns a new or cached BeanPropertyAccessor for the given class.
49      */

50     public static BeanPropertyAccessor forClass(Class JavaDoc clazz) {
51         synchronized (cAccessors) {
52             BeanPropertyAccessor bpa;
53             SoftReference JavaDoc ref = (SoftReference JavaDoc) cAccessors.get(clazz);
54             if (ref != null) {
55                 bpa = (BeanPropertyAccessor)ref.get();
56                 if (bpa != null) {
57                     return bpa;
58                 }
59             }
60             bpa = generate(clazz);
61             cAccessors.put(clazz, new SoftReference JavaDoc(bpa));
62             return bpa;
63         }
64     }
65
66     private static BeanPropertyAccessor generate(Class JavaDoc beanType) {
67         ClassInjector ci = ClassInjector.create
68             (BeanPropertyAccessor.class.getName(), beanType.getClassLoader());
69         Class JavaDoc clazz = ci.defineClass(generateClassFile(ci.getClassName(), beanType));
70
71         try {
72             return (BeanPropertyAccessor)clazz.newInstance();
73         } catch (InstantiationException JavaDoc e) {
74             throw new InternalError JavaDoc(e.toString());
75         } catch (IllegalAccessException JavaDoc e) {
76             throw new InternalError JavaDoc(e.toString());
77         }
78     }
79
80     private static ClassFile generateClassFile(String JavaDoc className,
81                                                Class JavaDoc beanType)
82     {
83         BeanProperty[][] props = getBeanProperties(beanType);
84
85         ClassFile cf = new ClassFile(className, BeanPropertyAccessor.class);
86         cf.markSynthetic();
87         cf.setSourceFile(BeanPropertyAccessor.class.getName());
88         try {
89             cf.setTarget(System.getProperty("java.specification.version"));
90         } catch (Exception JavaDoc e) {
91         }
92
93         MethodInfo ctor = cf.addConstructor(Modifiers.PUBLIC, null);
94         ctor.markSynthetic();
95         CodeBuilder builder = new CodeBuilder(ctor);
96
97         builder.loadThis();
98         builder.invokeSuperConstructor(null);
99         builder.returnVoid();
100
101         generateMethod(cf, beanType, props[0], true);
102         generateMethod(cf, beanType, props[1], false);
103
104         return cf;
105     }
106
107     private static void generateMethod(ClassFile cf,
108                                        Class JavaDoc beanType,
109                                        BeanProperty[] properties,
110                                        boolean forRead)
111     {
112         TypeDesc objectType = TypeDesc.OBJECT;
113         TypeDesc stringType = TypeDesc.STRING;
114         TypeDesc intType = TypeDesc.INT;
115         TypeDesc booleanType = TypeDesc.BOOLEAN;
116         TypeDesc exceptionType =
117             TypeDesc.forClass(NoSuchPropertyException.class);
118
119         MethodInfo mi;
120         if (forRead) {
121             TypeDesc[] params = {objectType, stringType};
122             mi = cf.addMethod
123                 (Modifiers.PUBLIC, "getPropertyValue", objectType, params);
124         } else {
125             TypeDesc[] params = new TypeDesc[] {
126                 objectType, stringType, objectType
127             };
128             mi = cf.addMethod
129                 (Modifiers.PUBLIC, "setPropertyValue", null, params);
130         }
131
132         mi.markSynthetic();
133         CodeBuilder builder = new CodeBuilder(mi);
134
135         LocalVariable beanVar = builder.getParameter(0);
136         LocalVariable propertyVar = builder.getParameter(1);
137         LocalVariable valueVar;
138         if (forRead) {
139             valueVar = null;
140         } else {
141             valueVar = builder.getParameter(2);
142         }
143
144         builder.loadLocal(beanVar);
145         builder.checkCast(TypeDesc.forClass(beanType));
146         builder.storeLocal(beanVar);
147
148         if (properties.length > 0) {
149             int[] cases = new int[hashCapacity(properties.length)];
150             int caseCount = cases.length;
151             for (int i=0; i<caseCount; i++) {
152                 cases[i] = i;
153             }
154
155             Label[] switchLabels = new Label[caseCount];
156             Label noMatch = builder.createLabel();
157             List JavaDoc[] caseMethods = caseMethods(caseCount, properties);
158             
159             for (int i=0; i<caseCount; i++) {
160                 List JavaDoc matches = caseMethods[i];
161                 if (matches == null || matches.size() == 0) {
162                     switchLabels[i] = noMatch;
163                 } else {
164                     switchLabels[i] = builder.createLabel();
165                 }
166             }
167
168             if (properties.length > 1) {
169                 builder.loadLocal(propertyVar);
170                 builder.invokeVirtual(String JavaDoc.class.getName(),
171                                       "hashCode", intType, null);
172                 builder.loadConstant(0x7fffffff);
173                 builder.math(Opcode.IAND);
174                 builder.loadConstant(caseCount);
175                 builder.math(Opcode.IREM);
176             
177                 builder.switchBranch(cases, switchLabels, noMatch);
178             }
179             
180             // Params to invoke String.equals.
181
TypeDesc[] params = {objectType};
182             
183             for (int i=0; i<caseCount; i++) {
184                 List JavaDoc matches = caseMethods[i];
185                 if (matches == null || matches.size() == 0) {
186                     continue;
187                 }
188                 
189                 switchLabels[i].setLocation();
190                 
191                 int matchCount = matches.size();
192                 for (int j=0; j<matchCount; j++) {
193                     BeanProperty bp = (BeanProperty)matches.get(j);
194                     
195                     // Test against name to find exact match.
196

197                     builder.loadConstant(bp.getName());
198                     builder.loadLocal(propertyVar);
199                     builder.invokeVirtual(String JavaDoc.class.getName(),
200                                           "equals", booleanType, params);
201                     
202                     Label notEqual;
203                     
204                     if (j == matchCount - 1) {
205                         notEqual = null;
206                         builder.ifZeroComparisonBranch(noMatch, "==");
207                     } else {
208                         notEqual = builder.createLabel();
209                         builder.ifZeroComparisonBranch(notEqual, "==");
210                     }
211                     
212                     if (forRead) {
213                         builder.loadLocal(beanVar);
214                         builder.invoke(bp.getReadMethod());
215                         TypeDesc type = TypeDesc.forClass(bp.getType());
216                         builder.convert(type, type.toObjectType());
217                         builder.returnValue(TypeDesc.OBJECT);
218                     } else {
219                         builder.loadLocal(beanVar);
220                         builder.loadLocal(valueVar);
221                         TypeDesc type = TypeDesc.forClass(bp.getType());
222                         builder.checkCast(type.toObjectType());
223                         builder.convert(type.toObjectType(), type);
224                         builder.invoke(bp.getWriteMethod());
225                         builder.returnVoid();
226                     }
227                     
228                     if (notEqual != null) {
229                         notEqual.setLocation();
230                     }
231                 }
232             }
233             
234             noMatch.setLocation();
235         }
236
237         builder.newObject(exceptionType);
238         builder.dup();
239         builder.loadLocal(propertyVar);
240         builder.loadConstant(forRead);
241
242         // Params to invoke NoSuchPropertyException.<init>.
243
TypeDesc[] params = {stringType, booleanType};
244
245         builder.invokeConstructor
246             (NoSuchPropertyException.class.getName(), params);
247         builder.throwObject();
248     }
249
250     /**
251      * Returns a prime number, at least twice as large as needed. This should
252      * minimize hash collisions. Since all the hash keys are known up front,
253      * the capacity could be tweaked until there are no collisions, but this
254      * technique is easier and deterministic.
255      */

256     private static int hashCapacity(int min) {
257         BigInteger JavaDoc capacity = BigInteger.valueOf(min * 2 + 1);
258         while (!capacity.isProbablePrime(100)) {
259             capacity = capacity.add(BigInteger.valueOf(2));
260         }
261         return capacity.intValue();
262     }
263
264     /**
265      * Returns an array of Lists of BeanProperties. The first index
266      * matches a switch case, the second index provides a list of all the
267      * BeanProperties whose name hash matched on the case.
268      */

269     private static List JavaDoc[] caseMethods(int caseCount,
270                                       BeanProperty[] props) {
271         List JavaDoc[] cases = new List JavaDoc[caseCount];
272
273         for (int i=0; i<props.length; i++) {
274             BeanProperty prop = props[i];
275             int hashCode = prop.getName().hashCode();
276             int caseValue = (hashCode & 0x7fffffff) % caseCount;
277             List JavaDoc matches = cases[caseValue];
278             if (matches == null) {
279                 matches = cases[caseValue] = new ArrayList JavaDoc();
280             }
281             matches.add(prop);
282         }
283
284         return cases;
285     }
286
287     /**
288      * Returns two arrays of BeanProperties. Array 0 contains read
289      * BeanProperties, array 1 contains the write BeanProperties.
290      */

291     private static BeanProperty[][] getBeanProperties(Class JavaDoc beanType) {
292         List JavaDoc readProperties = new ArrayList JavaDoc();
293         List JavaDoc writeProperties = new ArrayList JavaDoc();
294
295         Map JavaDoc map = BeanIntrospector.getAllProperties(beanType);
296
297         Iterator JavaDoc it = map.values().iterator();
298         while (it.hasNext()) {
299             BeanProperty bp = (BeanProperty)it.next();
300             if (bp.getReadMethod() != null) {
301                 readProperties.add(bp);
302             }
303             if (bp.getWriteMethod() != null) {
304                 writeProperties.add(bp);
305             }
306         }
307
308         BeanProperty[][] props = new BeanProperty[2][];
309         
310         props[0] = new BeanProperty[readProperties.size()];
311         readProperties.toArray(props[0]);
312         props[1] = new BeanProperty[writeProperties.size()];
313         writeProperties.toArray(props[1]);
314
315         return props;
316     }
317
318     protected BeanPropertyAccessor() {
319     }
320
321     // The actual public methods that will need to be defined.
322

323     public abstract Object JavaDoc getPropertyValue(Object JavaDoc bean, String JavaDoc property)
324         throws NoSuchPropertyException;
325
326     public abstract void setPropertyValue(Object JavaDoc bean, String JavaDoc property,
327                                           Object JavaDoc value)
328         throws NoSuchPropertyException;
329
330     // Auto-generated code sample:
331
/*
332     public Object getPropertyValue(Object bean, String property) {
333         Bean bean = (Bean)bean;
334         
335         switch ((property.hashCode() & 0x7fffffff) % 11) {
336         case 0:
337             if ("name".equals(property)) {
338                 return bean.getName();
339             }
340             break;
341         case 1:
342             // No case
343             break;
344         case 2:
345             // Hash collision
346             if ("value".equals(property)) {
347                 return bean.getValue();
348             } else if ("age".equals(property)) {
349                 return new Integer(bean.getAge());
350             }
351             break;
352         case 3:
353             if ("start".equals(property)) {
354                 return bean.getStart();
355             }
356             break;
357         case 4:
358         case 5:
359         case 6:
360             // No case
361             break;
362         case 7:
363             if ("end".equals(property)) {
364                 return bean.isEnd() ? Boolean.TRUE : Boolean.FALSE;
365             }
366             break;
367         case 8:
368         case 9:
369         case 10:
370             // No case
371             break;
372         }
373         
374         throw new NoSuchPropertyException(property, true);
375     }
376
377     public void setPropertyValue(Object bean, String property, Object value) {
378         Bean bean = (Bean)bean;
379         
380         switch ((property.hashCode() & 0x7fffffff) % 11) {
381         case 0:
382             if ("name".equals(property)) {
383                 bean.setName(value);
384             }
385             break;
386         case 1:
387             // No case
388             break;
389         case 2:
390             // Hash collision
391             if ("value".equals(property)) {
392                 bean.setValue(value);
393             } else if ("age".equals(property)) {
394                 bean.setAge(((Integer)value).intValue());
395             }
396             break;
397         case 3:
398             if ("start".equals(property)) {
399                 bean.setStart(value);
400             }
401             break;
402         case 4:
403         case 5:
404         case 6:
405             // No case
406             break;
407         case 7:
408             if ("end".equals(property)) {
409                 bean.setEnd(((Boolean)value).booleanValue());
410             }
411             break;
412         case 8:
413         case 9:
414         case 10:
415             // No case
416             break;
417         }
418         
419         throw new NoSuchPropertyException(property, false);
420     }
421     */

422 }
423
Popular Tags