001: package org.drools.util.asm;
002:
003: /*
004: * Copyright 2005 JBoss Inc
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.beans.Introspector;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.ArrayList;
025: import java.util.HashMap;
026: import java.util.HashSet;
027: import java.util.List;
028: import java.util.Map;
029: import java.util.Set;
030:
031: import org.drools.asm.AnnotationVisitor;
032: import org.drools.asm.Attribute;
033: import org.drools.asm.ClassReader;
034: import org.drools.asm.ClassVisitor;
035: import org.drools.asm.FieldVisitor;
036: import org.drools.asm.MethodVisitor;
037: import org.drools.asm.Opcodes;
038:
039: /**
040: * Visit a POJO user class, and extract the property getter methods that are public, in the
041: * order in which they are declared actually in the class itself (not using introspection).
042: *
043: * This may be enhanced in the future to allow annotations or perhaps external meta data
044: * configure the order of the indexes, as this may provide fine tuning options in special cases.
045: *
046: * @author Michael Neale
047: */
048: public class ClassFieldInspector {
049:
050: private final List methods = new ArrayList();
051: private final Map fieldNames = new HashMap();
052: private final Map fieldTypes = new HashMap();
053: private final Map methodNames = new HashMap();
054: private final Set nonGetters = new HashSet();
055:
056: /**
057: * @param clazz The class that the fields to be shadowed are extracted for.
058: * @throws IOException
059: */
060: public ClassFieldInspector(final Class clazz) throws IOException {
061: this (clazz, true);
062: }
063:
064: public ClassFieldInspector(final Class clazz,
065: final boolean includeFinalMethods) throws IOException {
066: final String name = getResourcePath(clazz);
067: final InputStream stream = clazz.getResourceAsStream(name);
068:
069: if (stream != null) {
070: processClassWithByteCode(clazz, stream, includeFinalMethods);
071: } else {
072: processClassWithoutByteCode(clazz, includeFinalMethods);
073: }
074: }
075:
076: /** Walk up the inheritance hierarchy recursively, reading in fields */
077: private void processClassWithByteCode(final Class clazz,
078: final InputStream stream, final boolean includeFinalMethods)
079: throws IOException {
080:
081: final ClassReader reader = new ClassReader(stream);
082: final ClassFieldVisitor visitor = new ClassFieldVisitor(clazz,
083: includeFinalMethods, this );
084: reader.accept(visitor, false);
085: if (clazz.getSuperclass() != null) {
086: final String name = getResourcePath(clazz.getSuperclass());
087: final InputStream parentStream = clazz
088: .getResourceAsStream(name);
089: if (parentStream != null) {
090: processClassWithByteCode(clazz.getSuperclass(),
091: parentStream, includeFinalMethods);
092: } else {
093: processClassWithoutByteCode(clazz.getSuperclass(),
094: includeFinalMethods);
095: }
096: }
097: if (clazz.isInterface()) {
098: final Class[] interfaces = clazz.getInterfaces();
099: for (int i = 0; i < interfaces.length; i++) {
100: final String name = getResourcePath(interfaces[i]);
101: final InputStream parentStream = clazz
102: .getResourceAsStream(name);
103: if (parentStream != null) {
104: processClassWithByteCode(interfaces[i],
105: parentStream, includeFinalMethods);
106: } else {
107: processClassWithoutByteCode(interfaces[i],
108: includeFinalMethods);
109: }
110: }
111: }
112: }
113:
114: private void processClassWithoutByteCode(final Class clazz,
115: final boolean includeFinalMethods) {
116: final Method[] methods = clazz.getMethods();
117: for (int i = 0; i < methods.length; i++) {
118:
119: //only want public methods that start with 'get' or 'is'
120: //and have no args, and return a value
121: final int mask = includeFinalMethods ? Modifier.PUBLIC
122: : Modifier.PUBLIC | Modifier.FINAL;
123: if (((methods[i].getModifiers() & mask) == Modifier.PUBLIC)
124: && (methods[i].getParameterTypes().length == 0)
125: && (!methods[i].getName().equals("<init>"))
126: && (!methods[i].getName().equals("<clinit>"))
127: && (methods[i].getReturnType() != void.class)) {
128: final int fieldIndex = this .methods.size();
129: addToMapping(methods[i], fieldIndex);
130: }
131: }
132: }
133:
134: /**
135: * Convert it to a form so we can load the bytes from the classpath.
136: */
137: private String getResourcePath(final Class clazz) {
138: return "/" + clazz.getName().replace('.', '/') + ".class";
139: }
140:
141: /**
142: * Return a list in order of which the getters (and "is") methods were found.
143: * This should only be done once when compiling a rulebase ideally.
144: */
145: public List getPropertyGetters() {
146: return this .methods;
147: }
148:
149: /**
150: * Return a mapping of the field "names" (ie bean property name convention)
151: * to the numerical index by which they can be accessed.
152: */
153: public Map getFieldNames() {
154: return this .fieldNames;
155: }
156:
157: /**
158: * @return A mapping of field types (unboxed).
159: */
160: public Map getFieldTypes() {
161: return this .fieldTypes;
162: }
163:
164: /**
165: * @return A mapping of methods for the getters.
166: */
167: public Map getGetterMethods() {
168: return this .methodNames;
169: }
170:
171: private void addToMapping(final Method method, final int index) {
172: final String name = method.getName();
173: int offset;
174: if (name.startsWith("is")) {
175: offset = 2;
176: } else if (name.startsWith("get")) {
177: offset = 3;
178: } else {
179: offset = 0;
180: }
181: final String fieldName = calcFieldName(name, offset);
182: if (this .fieldNames.containsKey(fieldName)) {
183: //only want it once, the first one thats found
184: if (offset != 0 && this .nonGetters.contains(fieldName)) {
185: //replace the non getter method with the getter one
186: removeOldField(fieldName);
187: storeField(method, index, fieldName);
188: this .nonGetters.remove(fieldName);
189: }
190: } else {
191: storeField(method, index, fieldName);
192: if (offset == 0) {
193: this .nonGetters.add(fieldName);
194: }
195: }
196: }
197:
198: private void removeOldField(final String fieldName) {
199: this .fieldNames.remove(fieldName);
200: this .fieldTypes.remove(fieldName);
201: this .methods.remove(this .methodNames.get(fieldName));
202: this .methodNames.remove(fieldName);
203:
204: }
205:
206: private void storeField(final Method method, final int index,
207: final String fieldName) {
208: this .fieldNames.put(fieldName, new Integer(index));
209: this .fieldTypes.put(fieldName, method.getReturnType());
210: this .methodNames.put(fieldName, method);
211: this .methods.add(method);
212: }
213:
214: private String calcFieldName(String name, final int offset) {
215: name = name.substring(offset);
216: return Introspector.decapitalize(name);
217: }
218:
219: /**
220: * Using the ASM classfield extractor to pluck it out in the order they appear in the class file.
221: * @author Michael Neale
222: */
223: static class ClassFieldVisitor implements ClassVisitor {
224:
225: private Class clazz;
226: private ClassFieldInspector inspector;
227: private boolean includeFinalMethods;
228:
229: ClassFieldVisitor(final Class cls,
230: final boolean includeFinalMethods,
231: final ClassFieldInspector inspector) {
232: this .clazz = cls;
233: this .includeFinalMethods = includeFinalMethods;
234: this .inspector = inspector;
235: }
236:
237: public MethodVisitor visitMethod(final int access,
238: final String name, final String desc,
239: final String signature, final String[] exceptions) {
240: //only want public methods
241: //and have no args, and return a value
242: final int mask = this .includeFinalMethods ? Opcodes.ACC_PUBLIC
243: : Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL;
244: if ((access & mask) == Opcodes.ACC_PUBLIC) {
245: if (desc.startsWith("()") && (!name.equals("<init>"))
246: && (!name.equals("<clinit>"))) {// && ( name.startsWith("get") || name.startsWith("is") ) ) {
247: try {
248: final Method method = this .clazz.getMethod(
249: name, (Class[]) null);
250: if (method.getReturnType() != void.class) {
251: final int fieldIndex = this .inspector.methods
252: .size();
253: this .inspector.addToMapping(method,
254: fieldIndex);
255: }
256: } catch (final NoSuchMethodException e) {
257: throw new IllegalStateException(
258: "Error in getting field access method.");
259: }
260: }
261: }
262: return null;
263: }
264:
265: public void visit(final int arg0, final int arg1,
266: final String arg2, final String arg3,
267: final String[] arg4, final String arg5) {
268: }
269:
270: public void visitInnerClass(final String arg0,
271: final String arg1, final String arg2, final int arg3) {
272: }
273:
274: public void visitField(final int access, final String arg1,
275: final String arg2, final Object arg3,
276: final Attribute arg4) {
277: }
278:
279: public void visitAttribute(final Attribute arg0) {
280: }
281:
282: public void visitEnd() {
283: }
284:
285: public void visit(final int arg0, final int arg1,
286: final String arg2, final String arg3,
287: final String arg4, final String[] arg5) {
288:
289: }
290:
291: public void visitSource(final String arg0, final String arg1) {
292:
293: }
294:
295: public void visitOuterClass(final String arg0,
296: final String arg1, final String arg2) {
297:
298: }
299:
300: public AnnotationVisitor visitAnnotation(final String arg0,
301: final boolean arg1) {
302:
303: return new ClassFieldAnnotationVisitor();
304: }
305:
306: public FieldVisitor visitField(final int arg0,
307: final String arg1, final String arg2,
308: final String arg3, final Object arg4) {
309:
310: return null;
311: }
312:
313: }
314:
315: /**
316: * This is required for POJOs that have annotations.
317: * It may also come in handy if we want to allow custom annotations for marking field numbers etc.
318: */
319: static class ClassFieldAnnotationVisitor implements
320: AnnotationVisitor {
321:
322: public void visit(final String arg0, final Object arg1) {
323: }
324:
325: public void visitEnum(final String arg0, final String arg1,
326: final String arg2) {
327: }
328:
329: public AnnotationVisitor visitAnnotation(final String arg0,
330: final String arg1) {
331: return new ClassFieldAnnotationVisitor();
332: }
333:
334: public AnnotationVisitor visitArray(final String arg0) {
335: return new ClassFieldAnnotationVisitor();
336: }
337:
338: public void visitEnd() {
339:
340: }
341:
342: }
343:
344: }
|