001: // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
002:
003: package jodd.proxetta;
004:
005: import org.objectweb.asm.AnnotationVisitor;
006: import org.objectweb.asm.ClassReader;
007: import org.objectweb.asm.ClassVisitor;
008: import org.objectweb.asm.ClassWriter;
009: import org.objectweb.asm.MethodVisitor;
010: import org.objectweb.asm.Opcodes;
011: import org.objectweb.asm.Type;
012: import static org.objectweb.asm.Opcodes.*;
013: import static jodd.proxetta.ProxettaAsmUtil.*;
014:
015: import java.util.ArrayList;
016: import java.util.List;
017:
018: /**
019: * Creates the proxy subclass.
020: */
021: public class ProxyCreator extends EmptyVisitor {
022:
023: protected ClassVisitor dest;
024: protected ProxyDefinition[] proxyDefinitionsSet;
025:
026: protected String targetClassname;
027: protected String targetPackage;
028: protected String this Reference;
029: protected String super Reference;
030: protected boolean proxyApplied;
031:
032: protected static String SUBCLASS_SUFFIX = "$JoddProxy";
033:
034: // ---------------------------------------------------------------- ctors
035:
036: protected ClassWriter cw;
037:
038: protected ProxyCreator(ClassVisitor cv,
039: ProxyDefinition... proxyDefintions) {
040: this .dest = cv;
041: this .proxyDefinitionsSet = proxyDefintions;
042: }
043:
044: public ProxyCreator(ProxyDefinition... proxyDefintions) {
045: this .cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
046: | ClassWriter.COMPUTE_FRAMES);
047: this .dest = cw;
048: this .proxyDefinitionsSet = proxyDefintions;
049: }
050:
051: public ProxyCreator accept(ClassReader cr) {
052: cr.accept(this , 0);
053: return this ;
054: }
055:
056: public byte[] toByteArray() {
057: return cw.toByteArray();
058: }
059:
060: /**
061: * Returns <code>true</code> if at least one method was found for proxyfying.
062: */
063: public boolean isProxyApplied() {
064: return proxyApplied;
065: }
066:
067: // ---------------------------------------------------------------- visitors
068:
069: /**
070: * Creates a subclass.
071: */
072: @Override
073: public void visit(int version, int access, String name,
074: String signature, String super name, String[] interfaces) {
075: int lastSlash = name.lastIndexOf('/');
076: this .targetPackage = name.substring(0, lastSlash).replace('/',
077: '.');
078: this .targetClassname = name.substring(lastSlash + 1);
079: super name = name;
080: name += SUBCLASS_SUFFIX;
081: this Reference = name;
082: super Reference = super name;
083: dest.visit(version, access, name, signature, super name, null);
084: }
085:
086: /**
087: * Creates all constructor as target.
088: * Finds a target methods on which to implement one or more proxies.
089: * Creates proxies.
090: */
091: @Override
092: public MethodVisitor visitMethod(int access, String name,
093: String desc, String signature, String[] exceptions) {
094: MethodSignatureVisitor msign = createMethodSignature(access,
095: name, desc, super Reference);
096:
097: // constructors [A1]
098: if (name.equals("<init>") == true) {
099: MethodVisitor mv = dest.visitMethod(access, name, desc,
100: msign.getSignature(), null);
101: return new ConstructorBuilder(mv, msign);
102: }
103: return new MethodBuilder(access, name, desc, super Reference);
104: }
105:
106: /**
107: * Copies all type annotations to the target.
108: */
109: @Override
110: public AnnotationVisitor visitAnnotation(String signature, boolean b) {
111: dest.visitAnnotation(signature, b); // [A3]
112: return null;
113: }
114:
115: /**
116: * Finalize the proxy class.
117: */
118: @Override
119: public void visitEnd() {
120: dest.visitEnd();
121: }
122:
123: // ---------------------------------------------------------------- ctor builder
124:
125: /**
126: * Builds simple constructors that delegates calls to super ones.
127: */
128: class ConstructorBuilder extends EmptyVisitor {
129:
130: final MethodSignatureVisitor msign;
131: final MethodVisitor mv;
132:
133: ConstructorBuilder(MethodVisitor mv,
134: MethodSignatureVisitor msign) {
135: this .mv = mv;
136: this .msign = msign;
137: }
138:
139: @Override
140: public AnnotationVisitor visitAnnotation(String sign, boolean b) {
141: mv.visitAnnotation(sign, b);
142: return null;
143: }
144:
145: @Override
146: public AnnotationVisitor visitParameterAnnotation(int i,
147: String string, boolean b) {
148: mv.visitParameterAnnotation(i, string, b);
149: return null;
150: }
151:
152: @Override
153: public AnnotationVisitor visitAnnotationDefault() {
154: mv.visitAnnotationDefault();
155: return null;
156: }
157:
158: @Override
159: public void visitEnd() {
160: mv.visitCode();
161: loadMethodArguments(mv, msign);
162: mv.visitMethodInsn(INVOKESPECIAL, super Reference, msign
163: .getMethodName(), msign.getDescription());
164: mv.visitInsn(RETURN);
165: mv.visitMaxs(0, 0);
166: mv.visitEnd();
167: }
168: }
169:
170: // ---------------------------------------------------------------- method builder
171:
172: /**
173: * Utility class that holds data for target methods that should be proxied.
174: */
175: static class TargetMethodData {
176:
177: MethodSignatureVisitor msign;
178: ProxyDefinition[] proxies; // applied proxies for the target
179:
180: String getMethodName(int proxyIndex) {
181: if (proxyIndex == proxies.length - 1) {
182: return msign.getMethodName();
183: }
184: return msign.getMethodName() + '$'
185: + proxies[proxyIndex + 1].getProxyName();
186: }
187:
188: String getReferName(int proxyIndex) {
189: return msign.getMethodName() + '$'
190: + proxies[proxyIndex].getProxyName();
191: }
192:
193: String getLastMethodName() {
194: return msign.getMethodName() + '$'
195: + proxies[0].getProxyName();
196: }
197:
198: boolean isFirstMethodInChain(int proxyIndex) {
199: return proxyIndex == proxies.length - 1;
200: }
201: }
202:
203: /**
204: * Proxy method builder
205: */
206: class MethodBuilder extends EmptyVisitor {
207:
208: final MethodSignatureVisitor msign;
209:
210: MethodBuilder(int access, String name, String desc,
211: String signature) {
212: msign = createMethodSignature(access, name, desc, signature);
213: }
214:
215: /**
216: * Reads and stores all method annotations in the method signature.
217: */
218: @Override
219: public AnnotationVisitor visitAnnotation(String sign, boolean b) {
220: AnnotationData ad = new AnnotationData(sign, b);
221: msign.annotations.add(ad);
222: return new AnnotationReader(ad);
223: }
224:
225: /**
226: * Builds proxy methods if applied to current method.
227: */
228: @Override
229: public void visitEnd() {
230:
231: if (proxyDefinitionsSet == null) {
232: return;
233: }
234:
235: // match method to all proxy definitions
236: TargetMethodData tmd = null;
237: List<ProxyDefinition> pdl = null;
238: for (ProxyDefinition pdef : proxyDefinitionsSet) {
239: if (pdef.apply(msign) == true) {
240: if (tmd == null) {
241: tmd = new TargetMethodData();
242: tmd.msign = msign;
243: pdl = new ArrayList<ProxyDefinition>(
244: proxyDefinitionsSet.length);
245: }
246: pdl.add(pdef);
247: }
248: }
249: // create proxies for matched methods
250: if (tmd != null) {
251: proxyApplied = true;
252: tmd.proxies = new ProxyDefinition[pdl.size()];
253: pdl.toArray(tmd.proxies);
254: createLastChainDelegate(tmd); // [A2]
255: for (int p = 0; p < tmd.proxies.length; p++) {
256: createProxyMethod(tmd, p);
257: }
258: }
259:
260: }
261:
262: /**
263: * Creates last method in calling chain that delegates the invocation to
264: * the target, i.e. super method that is proxied.
265: */
266: protected void createLastChainDelegate(TargetMethodData td) {
267: MethodVisitor mv = dest.visitMethod(td.msign
268: .getAccessFlags(), td.getLastMethodName(), td.msign
269: .getDescription(), td.msign.getSignature(), null);
270: mv.visitCode();
271: loadMethodArguments(mv, td.msign);
272: mv.visitMethodInsn(INVOKESPECIAL, super Reference, td.msign
273: .getMethodName(), td.msign.getDescription());
274: visitReturn(mv, td.msign);
275: mv.visitMaxs(0, 0);
276: mv.visitEnd();
277: }
278:
279: /**
280: * Write all method annotations.
281: */
282: void writeAnnotations(MethodVisitor dest,
283: List<AnnotationData> annotations) {
284: for (AnnotationData ann : annotations) {
285: AnnotationVisitor av = dest.visitAnnotation(
286: ann.signature, ann.isVisible);
287: for (String name : ann.values.keySet()) {
288: av.visit(name, ann.values.get(name));
289: }
290: }
291: }
292:
293: /**
294: * Creates proxies for a target method,
295: * If subclassing creates overridden target method.
296: * For each matched proxy definition, creates new proxy method.
297: * <p>
298: *
299: * Invocation chain:<br>
300: * name{p2} -> name$p2{p1} -> name$p1{target}.
301: * <p>
302: * Fix the arguments and replaces <code>ProxyMethod</code> method calls.
303: */
304: public void createProxyMethod(final TargetMethodData td,
305: final int p) {
306: ProxyDefinition pdef = td.proxies[p];
307:
308: final MethodVisitor mv = dest.visitMethod(td.msign
309: .getAccessFlags(), td.getMethodName(p), td.msign
310: .getDescription(), null, null);
311: if (td.isFirstMethodInChain(p)) {
312: writeAnnotations(mv, td.msign.getAnnotations()); // [A4]
313: }
314: mv.visitCode();
315:
316: pdef.getProxyClassReader().accept(new EmptyVisitor() {
317: @Override
318: public MethodVisitor visitMethod(int i, String name,
319: String string1, String string2, String[] strings) {
320: if (name.equals("invoke")) {
321: return new HistoryMethodAdapter(mv) {
322:
323: int returnValueOffset = -1;
324:
325: @Override
326: public void visitVarInsn(int opcode, int var) {
327: int newVar = var
328: + td.msign.getParamSize();
329: if (traceNext == true) {
330: if ((opcode == ASTORE)
331: || (opcode == ISTORE)
332: || (opcode == LSTORE)
333: || (opcode == FSTORE)
334: || (opcode == DSTORE)) { // [F4]
335: returnValueOffset = newVar;
336: }
337: }
338: super .visitVarInsn(opcode, newVar); // [F1]
339: }
340:
341: @Override
342: public void visitIincInsn(int var,
343: int increment) {
344: super .visitIincInsn(var
345: + td.msign.getParamSize(),
346: increment); // [F1]
347: }
348:
349: @Override
350: public void visitInsn(int opcode) {
351: if (opcode == RETURN) {
352: visitReturn(mv, td.msign,
353: returnValueOffset); // [F2] [F4]
354: return;
355: }
356: if (traceNext == true) {
357: if ((opcode == POP)
358: || (opcode == POP2)) { // [F3]
359: return;
360: }
361: }
362: super .visitInsn(opcode);
363: }
364:
365: @Override
366: public void visitMethodInsn(int opcode,
367: String owner, String name,
368: String desc) {
369: if (opcode == INVOKESTATIC) {
370: if (owner.endsWith("/ProxyTarget") == true) {
371:
372: if (isInvokeMethod(name, desc)) { // [R1]
373: loadMethodArguments(mv,
374: td.msign);
375: mv
376: .visitMethodInsn(
377: INVOKESPECIAL,
378: this Reference,
379: td
380: .getReferName(p),
381: td.msign
382: .getDescription());
383: return;
384: } else
385:
386: if (isInvokeGetResultMethod(
387: name, desc)) { // [R7]
388: loadMethodArguments(mv,
389: td.msign);
390: mv
391: .visitMethodInsn(
392: INVOKESPECIAL,
393: this Reference,
394: td
395: .getReferName(p),
396: td.msign
397: .getDescription());
398: if (td.msign
399: .getReturnOpcodeType() == 'V') {
400: mv
401: .visitInsn(ACONST_NULL); // [F4]
402: }
403: traceNext = true;
404: return;
405: } else
406:
407: if (isArgsCountMethod(name,
408: desc)) { // [R2]
409: int argsCount = td.msign
410: .getParamCount();
411: pushInt(mv, argsCount);
412: return;
413: } else
414:
415: if (isGetArgClassMethod(name,
416: desc)) { // [R3]
417: int argIndex = this
418: .getArgumentIndex();
419: mv.visitInsn(POP);
420: loadMethodArgumentClass(mv,
421: td.msign, argIndex);
422: return;
423: } else
424:
425: if (isGetArgMethod(name, desc)) { // [R4]
426: int argIndex = this
427: .getArgumentIndex();
428: mv.visitInsn(POP);
429: loadMethodArgumentAsObject(
430: mv, td.msign,
431: argIndex);
432: return;
433: } else
434:
435: if (isSetArgMethod(name, desc)) { // [R5]
436: int argIndex = this
437: .getArgumentIndex();
438: mv.visitInsn(POP);
439: storeMethodArgumentFromObject(
440: mv, td.msign,
441: argIndex);
442: return;
443: } else
444:
445: if (isCreateArgsArrayMethod(
446: name, desc)) { // [R6]
447: int argsCount = td.msign
448: .getParamCount();
449: pushInt(mv, argsCount);
450: mv.visitTypeInsn(ANEWARRAY,
451: "java/lang/Object");
452: for (int i = 0; i < argsCount; i++) {
453: mv.visitInsn(DUP);
454: pushInt(mv, i);
455: loadMethodArgumentAsObject(
456: mv, td.msign,
457: i + 1);
458: mv.visitInsn(AASTORE);
459: }
460: return;
461: } else
462:
463: if (isCreateArgsClassArrayMethod(
464: name, desc)) { // [R11]
465: int argsCount = td.msign
466: .getParamCount();
467: pushInt(mv, argsCount);
468: mv.visitTypeInsn(ANEWARRAY,
469: "java/lang/Class");
470: for (int i = 0; i < argsCount; i++) {
471: mv.visitInsn(DUP);
472: pushInt(mv, i);
473: loadMethodArgumentClass(
474: mv, td.msign,
475: i + 1);
476: mv.visitInsn(AASTORE);
477: }
478: return;
479: } else
480:
481: if (isSetReturnValMethod(name,
482: desc)) { // [R8]
483: if (returnValueOffset == -1) {
484: throw new ProxettaException(
485: "Return value is not availiable yet: 'setReturnValue' has to be invoked after 'invokeAndGetResult'.");
486: }
487: storeValue(
488: mv,
489: returnValueOffset,
490: td.msign
491: .getReturnOpcodeType());
492: return;
493: } else
494:
495: if (isGetTargetClassMethod(
496: name, desc)) { // [R9]
497: mv
498: .visitLdcInsn(Type
499: .getType('L' + super Reference + ';'));
500: return;
501: } else
502:
503: if (isGetTargetMethodNameMethod(
504: name, desc)) { // [R10]
505: mv.visitLdcInsn(td.msign
506: .getMethodName());
507: return;
508: }
509:
510: if (isGetRetrunTypeMethod(name,
511: desc)) { // [R11]
512: String returnType = td.msign
513: .getReturnType()
514: .replace('.', '/');
515: if (returnType
516: .equals("void")) {
517: mv
518: .visitInsn(Opcodes.ACONST_NULL);
519: } else {
520: mv
521: .visitLdcInsn(Type
522: .getType('L' + returnType + ';'));
523: }
524: return;
525: }
526: }
527: }
528: super .visitMethodInsn(opcode, owner,
529: name, desc);
530: }
531:
532: };
533: }
534: return null;
535: }
536: }, 0);
537: }
538:
539: // ---------------------------------------------------------------- detectors
540:
541: /**
542: * Detects <code>void invoke()</code> method.
543: */
544: protected boolean isInvokeMethod(String name, String desc) {
545: if (name.equals("invoke")) {
546: if (desc.equals("()V")) {
547: return true;
548: }
549: }
550: return false;
551: }
552:
553: protected boolean isInvokeGetResultMethod(String name,
554: String desc) {
555: if (name.equals("invokeAndGetResult")) {
556: if (desc.equals("()Ljava/lang/Object;")) {
557: return true;
558: }
559: }
560: return false;
561: }
562:
563: /**
564: * Detects <code>int argsCount()</code> method.
565: */
566: protected boolean isArgsCountMethod(String name, String desc) {
567: if (name.equals("argsCount")) {
568: if (desc.equals("()I")) {
569: return true;
570: }
571: }
572: return false;
573: }
574:
575: /**
576: * Detects <code>Class getArgType(int)</code> method.
577: */
578: protected boolean isGetArgClassMethod(String name, String desc) {
579: if (name.equals("getArgType")) {
580: if (desc.equals("(I)Ljava/lang/Class;")) {
581: return true;
582: }
583: }
584: return false;
585: }
586:
587: /**
588: * Detects <code>Object getArg(int)</code> method.
589: */
590: protected boolean isGetArgMethod(String name, String desc) {
591: if (name.equals("getArg")) {
592: if (desc.equals("(I)Ljava/lang/Object;")) {
593: return true;
594: }
595: }
596: return false;
597: }
598:
599: /**
600: * Detects <code>setArg(Object, int)</code> method.
601: */
602: protected boolean isSetArgMethod(String name, String desc) {
603: if (name.equals("setArg")) {
604: if (desc.equals("(Ljava/lang/Object;I)V")) {
605: return true;
606: }
607: }
608: return false;
609: }
610:
611: /**
612: * Detects <code>createArgsArray()</code> method.
613: */
614: protected boolean isCreateArgsArrayMethod(String name,
615: String desc) {
616: if (name.equals("createArgsArray")) {
617: if (desc.equals("()[Ljava/lang/Object;")) {
618: return true;
619: }
620: }
621: return false;
622: }
623:
624: /**
625: * Detects <code>createArgsClassArray()</code> method.
626: */
627: protected boolean isCreateArgsClassArrayMethod(String name,
628: String desc) {
629: if (name.equals("createArgsClassArray")) {
630: if (desc.equals("()[Ljava/lang/Class;")) {
631: return true;
632: }
633: }
634: return false;
635: }
636:
637: /**
638: * Detects <code>setReturnVal(name, desc)</code> method.
639: */
640: protected boolean isSetReturnValMethod(String name, String desc) {
641: if (name.equals("setReturnVal")) {
642: if (desc.equals("(Ljava/lang/Object;)V")) {
643: return true;
644: }
645: }
646: return false;
647: }
648:
649: /**
650: * Detects <code>Class getTargetClass()</code> method.
651: */
652: protected boolean isGetTargetClassMethod(String name,
653: String desc) {
654: if (name.equals("getTargetClass")) {
655: if (desc.equals("()Ljava/lang/Class;")) {
656: return true;
657: }
658: }
659: return false;
660: }
661:
662: /**
663: * Detects <code>String getTargetMethodName()</code> method.
664: */
665: protected boolean isGetTargetMethodNameMethod(String name,
666: String desc) {
667: if (name.equals("getTargetMethodName")) {
668: if (desc.equals("()Ljava/lang/String;")) {
669: return true;
670: }
671: }
672: return false;
673: }
674:
675: /**
676: * Detects <code>Class getReturnType()</code> method.
677: */
678: protected boolean isGetRetrunTypeMethod(String name, String desc) {
679: if (name.equals("getReturnType")) {
680: if (desc.equals("()Ljava/lang/Class;")) {
681: return true;
682: }
683: }
684: return false;
685: }
686:
687: }
688: }
|