0001: // Copyright 2006, 2007 The Apache Software Foundation
0002: //
0003: // Licensed under the Apache License, Version 2.0 (the "License");
0004: // you may not use this file except in compliance with the License.
0005: // You may obtain a copy of the License at
0006: //
0007: // http://www.apache.org/licenses/LICENSE-2.0
0008: //
0009: // Unless required by applicable law or agreed to in writing, software
0010: // distributed under the License is distributed on an "AS IS" BASIS,
0011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0012: // See the License for the specific language governing permissions and
0013: // limitations under the License.
0014:
0015: package org.apache.tapestry.internal.services;
0016:
0017: import static java.lang.String.format;
0018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
0019: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
0020: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
0021: import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
0022: import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
0023:
0024: import java.lang.annotation.Annotation;
0025: import java.lang.reflect.Modifier;
0026: import java.util.Collections;
0027: import java.util.Formatter;
0028: import java.util.List;
0029: import java.util.Map;
0030: import java.util.Set;
0031:
0032: import javassist.CannotCompileException;
0033: import javassist.ClassPool;
0034: import javassist.CtBehavior;
0035: import javassist.CtClass;
0036: import javassist.CtConstructor;
0037: import javassist.CtField;
0038: import javassist.CtMember;
0039: import javassist.CtMethod;
0040: import javassist.CtNewConstructor;
0041: import javassist.CtNewMethod;
0042: import javassist.NotFoundException;
0043: import javassist.expr.ExprEditor;
0044: import javassist.expr.FieldAccess;
0045:
0046: import org.apache.commons.logging.Log;
0047: import org.apache.tapestry.ComponentResources;
0048: import org.apache.tapestry.internal.InternalComponentResources;
0049: import org.apache.tapestry.internal.util.MultiKey;
0050: import org.apache.tapestry.ioc.internal.util.CollectionFactory;
0051: import org.apache.tapestry.ioc.internal.util.IdAllocator;
0052: import org.apache.tapestry.ioc.internal.util.InternalUtils;
0053: import org.apache.tapestry.model.ComponentModel;
0054: import org.apache.tapestry.runtime.Component;
0055: import org.apache.tapestry.services.FieldFilter;
0056: import org.apache.tapestry.services.MethodFilter;
0057: import org.apache.tapestry.services.MethodSignature;
0058: import org.apache.tapestry.services.TransformUtils;
0059:
0060: /**
0061: * Implementation of the {@link org.apache.tapestry.internal.services.InternalClassTransformation}
0062: * interface.
0063: */
0064: public final class InternalClassTransformationImpl implements
0065: InternalClassTransformation {
0066: private boolean _frozen;
0067:
0068: private final CtClass _ctClass;
0069:
0070: private final Log _log;
0071:
0072: private final InternalClassTransformation _parentTransformation;
0073:
0074: private ClassPool _classPool;
0075:
0076: private final IdAllocator _idAllocator;
0077:
0078: /** Map, keyed on InjectKey, of field name. */
0079: private final Map<MultiKey, String> _injectionCache = newMap();
0080:
0081: /** Map from a field to the annotation objects for that field. */
0082: private Map<String, List<Annotation>> _fieldAnnotations = newMap();
0083:
0084: /** Used to identify fields that have been "claimed" by other annotations. */
0085: private Map<String, Object> _claimedFields = newMap();
0086:
0087: private Set<String> _addedFieldNames = newSet();
0088:
0089: private Set<CtBehavior> _addedMethods = newSet();
0090:
0091: // Cache of class annotations
0092:
0093: private List<Annotation> _classAnnotations;
0094:
0095: // Cache of method annotations
0096:
0097: private Map<CtMethod, List<Annotation>> _methodAnnotations = newMap();
0098:
0099: private Map<CtMethod, MethodSignature> _methodSignatures = newMap();
0100:
0101: // Key is field name, value is expression used to replace read access
0102:
0103: private Map<String, String> _fieldReadTransforms;
0104:
0105: // Key is field name, value is expression used to replace read access
0106: private Map<String, String> _fieldWriteTransforms;
0107:
0108: private Set<String> _removedFieldNames;
0109:
0110: /** Contains the assembled Javassist code for the class' default constructor. */
0111: private StringBuilder _constructor = new StringBuilder();
0112:
0113: private final List<ConstructorArg> _constructorArgs;
0114:
0115: private final ComponentModel _componentModel;
0116:
0117: private final String _resourcesFieldName;
0118:
0119: private final StringBuilder _description = new StringBuilder();
0120:
0121: private Formatter _formatter = new Formatter(_description);
0122:
0123: private ClassLoader _loader;
0124:
0125: /**
0126: * This is a constructor for the root class, the class that directly contains the ComponentClass
0127: * annotation.
0128: */
0129: public InternalClassTransformationImpl(CtClass ctClass,
0130: ClassLoader loader, Log log, ComponentModel componentModel) {
0131: _ctClass = ctClass;
0132: _classPool = _ctClass.getClassPool();
0133: _loader = loader;
0134: _parentTransformation = null;
0135: _componentModel = componentModel;
0136:
0137: _idAllocator = new IdAllocator();
0138:
0139: _log = log;
0140:
0141: preloadMemberNames();
0142:
0143: _constructorArgs = newList();
0144: _constructor.append("{\n");
0145:
0146: addImplementedInterface(Component.class);
0147:
0148: _resourcesFieldName = addInjectedFieldUncached(
0149: InternalComponentResources.class, "resources", null);
0150:
0151: MethodSignature sig = new MethodSignature(Modifier.PUBLIC
0152: | Modifier.FINAL, ComponentResources.class.getName(),
0153: "getComponentResources", null, null);
0154:
0155: addMethod(sig, "return " + _resourcesFieldName + ";");
0156: }
0157:
0158: public InternalClassTransformationImpl(CtClass ctClass,
0159: InternalClassTransformation parentTransformation,
0160: ClassLoader loader, Log log, ComponentModel componentModel) {
0161: _ctClass = ctClass;
0162: _classPool = _ctClass.getClassPool();
0163: _loader = loader;
0164: _log = log;
0165: _parentTransformation = parentTransformation;
0166: _componentModel = componentModel;
0167:
0168: _resourcesFieldName = parentTransformation
0169: .getResourcesFieldName();
0170:
0171: _idAllocator = parentTransformation.getIdAllocator();
0172:
0173: preloadMemberNames();
0174:
0175: verifyFields();
0176:
0177: _constructorArgs = parentTransformation.getConstructorArgs();
0178:
0179: int count = _constructorArgs.size();
0180:
0181: // Build the call to the super-constructor.
0182:
0183: _constructor.append("{ super(");
0184:
0185: for (int i = 1; i <= count; i++) {
0186: if (i > 1)
0187: _constructor.append(", ");
0188:
0189: // $0 is implicitly self, so the 0-index ConstructorArg will be Javassisst
0190: // pseudeo-variable $1, and so forth.
0191:
0192: _constructor.append("$" + i);
0193: }
0194:
0195: _constructor.append(");\n");
0196:
0197: // The "}" will be added later, inside
0198: }
0199:
0200: private void freeze() {
0201: _frozen = true;
0202:
0203: // Free up stuff we don't need after freezing.
0204: // Everything else should be final.
0205:
0206: _fieldAnnotations = null;
0207: _claimedFields = null;
0208: _addedFieldNames = null;
0209: _addedMethods = null;
0210: _classAnnotations = null;
0211: _methodAnnotations = null;
0212: _methodSignatures = null;
0213: _fieldReadTransforms = null;
0214: _fieldWriteTransforms = null;
0215: _removedFieldNames = null;
0216: _constructor = null;
0217: _formatter = null;
0218: _loader = null;
0219: // _ctClass = null; -- needed by toString()
0220: _classPool = null;
0221: }
0222:
0223: public String getResourcesFieldName() {
0224: return _resourcesFieldName;
0225: }
0226:
0227: /** Loads the names of all declared fields and methods into the idAllocator. */
0228:
0229: private void preloadMemberNames() {
0230: addMemberNames(_ctClass.getDeclaredFields());
0231: addMemberNames(_ctClass.getDeclaredMethods());
0232: }
0233:
0234: public void verifyFields() {
0235: List<String> names = newList();
0236:
0237: for (CtField field : _ctClass.getDeclaredFields()) {
0238: String name = field.getName();
0239:
0240: if (_addedFieldNames.contains(name))
0241: continue;
0242:
0243: int modifiers = field.getModifiers();
0244:
0245: // Fields must be either static or private.
0246:
0247: if (Modifier.isStatic(modifiers)
0248: || Modifier.isPrivate(modifiers))
0249: continue;
0250:
0251: names.add(name);
0252: }
0253:
0254: if (!names.isEmpty()) {
0255: Collections.sort(names);
0256:
0257: _log.error(ServicesMessages.nonPrivateFields(
0258: getClassName(), names));
0259: }
0260: }
0261:
0262: private void addMemberNames(CtMember[] members) {
0263: for (CtMember member : members) {
0264: _idAllocator.allocateId(member.getName());
0265: }
0266: }
0267:
0268: public <T extends Annotation> T getFieldAnnotation(
0269: String fieldName, Class<T> annotationClass) {
0270: failIfFrozen();
0271:
0272: List<Annotation> annotations = findFieldAnnotations(fieldName);
0273:
0274: return findAnnotationInList(annotationClass, annotations);
0275: }
0276:
0277: public <T extends Annotation> T getMethodAnnotation(
0278: MethodSignature signature, Class<T> annotationClass) {
0279: failIfFrozen();
0280:
0281: CtMethod method = findMethod(signature);
0282:
0283: if (method == null)
0284: throw new IllegalArgumentException(ServicesMessages
0285: .noDeclaredMethod(_ctClass, signature));
0286:
0287: List<Annotation> annotations = findMethodAnnotations(method);
0288:
0289: return findAnnotationInList(annotationClass, annotations);
0290: }
0291:
0292: /**
0293: * Searches an array of objects (that are really annotations instances) to find one that is of
0294: * the correct type, which is returned.
0295: *
0296: * @param <T>
0297: * @param annotationClass
0298: * the annotation to search for
0299: * @param annotations
0300: * the available annotations
0301: * @return the matching annotation instance, or null if not found
0302: */
0303: private <T extends Annotation> T findAnnotationInList(
0304: Class<T> annotationClass, List<Annotation> annotations) {
0305: for (Object annotation : annotations) {
0306: if (annotationClass.isInstance(annotation))
0307: return annotationClass.cast(annotation);
0308: }
0309:
0310: return null;
0311: }
0312:
0313: public <T extends Annotation> T getAnnotation(
0314: Class<T> annotationClass) {
0315: return findAnnotationInList(annotationClass,
0316: getClassAnnotations());
0317: }
0318:
0319: private List<Annotation> findFieldAnnotations(String fieldName) {
0320: List<Annotation> annotations = _fieldAnnotations.get(fieldName);
0321:
0322: if (annotations == null) {
0323: annotations = findAnnotationsForField(fieldName);
0324: _fieldAnnotations.put(fieldName, annotations);
0325: }
0326:
0327: return annotations;
0328: }
0329:
0330: private List<Annotation> findMethodAnnotations(CtMethod method) {
0331: List<Annotation> annotations = _methodAnnotations.get(method);
0332:
0333: if (annotations == null) {
0334: annotations = extractAnnotations(method);
0335:
0336: _methodAnnotations.put(method, annotations);
0337: }
0338:
0339: return annotations;
0340: }
0341:
0342: private List<Annotation> findAnnotationsForField(String fieldName) {
0343: CtField field = findDeclaredCtField(fieldName);
0344:
0345: return extractAnnotations(field);
0346: }
0347:
0348: private List<Annotation> extractAnnotations(CtMember member) {
0349: try {
0350: List<Annotation> result = newList();
0351:
0352: addAnnotationsToList(result, member.getAnnotations());
0353:
0354: return result;
0355: } catch (ClassNotFoundException ex) {
0356: throw new RuntimeException(ex);
0357: }
0358: }
0359:
0360: private void addAnnotationsToList(List<Annotation> list,
0361: Object[] annotations) {
0362: for (Object o : annotations) {
0363: Annotation a = (Annotation) o;
0364: list.add(a);
0365: }
0366: }
0367:
0368: private CtField findDeclaredCtField(String fieldName) {
0369: try {
0370: return _ctClass.getDeclaredField(fieldName);
0371: } catch (NotFoundException ex) {
0372: throw new RuntimeException(ServicesMessages
0373: .missingDeclaredField(_ctClass, fieldName), ex);
0374: }
0375: }
0376:
0377: public String newMemberName(String suggested) {
0378: failIfFrozen();
0379:
0380: String memberName = InternalUtils.createMemberName(notBlank(
0381: suggested, "suggested"));
0382:
0383: return _idAllocator.allocateId(memberName);
0384: }
0385:
0386: public String newMemberName(String prefix, String baseName) {
0387: return newMemberName(prefix + "_"
0388: + InternalUtils.stripMemberPrefix(baseName));
0389: }
0390:
0391: public void addImplementedInterface(Class interfaceClass) {
0392: failIfFrozen();
0393:
0394: String interfaceName = interfaceClass.getName();
0395:
0396: try {
0397: CtClass ctInterface = _classPool.get(interfaceName);
0398:
0399: if (classImplementsInterface(ctInterface))
0400: return;
0401:
0402: implementDefaultMethodsForInterface(ctInterface);
0403:
0404: _ctClass.addInterface(ctInterface);
0405:
0406: } catch (NotFoundException ex) {
0407: throw new RuntimeException(ex);
0408: }
0409:
0410: }
0411:
0412: /**
0413: * Adds default implementations for the methods defined by the interface (and all of its
0414: * super-interfaces). The implementations return null (or 0, or false, as appropriate to to the
0415: * method type). There are a number of degenerate cases that are not covered properly: these are
0416: * related to base interfaces that may be implemented by base classes.
0417: *
0418: * @param ctInterface
0419: * @throws NotFoundException
0420: */
0421: private void implementDefaultMethodsForInterface(CtClass ctInterface)
0422: throws NotFoundException {
0423: // java.lang.Object is the parent interface of interfaces
0424:
0425: if (ctInterface.getName().equals(Object.class.getName()))
0426: return;
0427:
0428: for (CtMethod method : ctInterface.getDeclaredMethods()) {
0429: addDefaultImplementation(method);
0430: }
0431:
0432: for (CtClass parent : ctInterface.getInterfaces()) {
0433: implementDefaultMethodsForInterface(parent);
0434: }
0435: }
0436:
0437: private void addDefaultImplementation(CtMethod method)
0438: throws NotFoundException {
0439: // Javassist has an oddity for interfaces: methods "inherited" from java.lang.Object show
0440: // up as methods of the interface. We skip those and only consider the methods
0441: // that are abstract.
0442:
0443: if (!Modifier.isAbstract(method.getModifiers()))
0444: return;
0445:
0446: try {
0447: CtMethod newMethod = CtNewMethod.copy(method, _ctClass,
0448: null);
0449:
0450: // Methods from interfaces are always public. We definitely
0451: // need to change the modifiers of the method so that
0452: // it is not abstract.
0453:
0454: newMethod.setModifiers(Modifier.PUBLIC);
0455:
0456: // Javassist will provide a minimal implementation for us (return null, false, 0,
0457: // whatever).
0458:
0459: newMethod.setBody(null);
0460:
0461: _ctClass.addMethod(newMethod);
0462:
0463: MethodSignature sig = getMethodSignature(newMethod);
0464:
0465: addMethodToDescription("add default", sig, "<default>");
0466: } catch (CannotCompileException ex) {
0467: throw new RuntimeException(ServicesMessages
0468: .errorAddingMethod(_ctClass, method.getName(), ex),
0469: ex);
0470: }
0471:
0472: }
0473:
0474: /**
0475: * Check to see if the target class (or any of its super classes) implements the provided
0476: * interface. This is geared for simple interfaces (that don't extend other interfaces), thus if
0477: * the class (or a base class) implement interface Y that extends interface X, we may not return
0478: * true for interface X.
0479: */
0480:
0481: private boolean classImplementsInterface(CtClass ctInterface)
0482: throws NotFoundException {
0483:
0484: for (CtClass current = _ctClass; current != null; current = current
0485: .getSuperclass()) {
0486: for (CtClass anInterface : current.getInterfaces()) {
0487: if (anInterface == ctInterface)
0488: return true;
0489: }
0490: }
0491:
0492: return false;
0493: }
0494:
0495: public void claimField(String fieldName, Object tag) {
0496: notBlank(fieldName, "fieldName");
0497: notNull(tag, "tag");
0498:
0499: failIfFrozen();
0500:
0501: Object existing = _claimedFields.get(fieldName);
0502:
0503: if (existing != null) {
0504: String message = ServicesMessages.fieldAlreadyClaimed(
0505: fieldName, _ctClass, existing, tag);
0506:
0507: throw new RuntimeException(message);
0508: }
0509:
0510: // TODO: Ensure that fieldName is a known field?
0511:
0512: _claimedFields.put(fieldName, tag);
0513: }
0514:
0515: public void addMethod(MethodSignature signature, String methodBody) {
0516: failIfFrozen();
0517:
0518: CtClass returnType = findCtClass(signature.getReturnType());
0519: CtClass[] parameters = buildCtClassList(signature
0520: .getParameterTypes());
0521: CtClass[] exceptions = buildCtClassList(signature
0522: .getExceptionTypes());
0523:
0524: String action = "add";
0525:
0526: try {
0527: CtMethod existing = _ctClass.getDeclaredMethod(signature
0528: .getMethodName(), parameters);
0529:
0530: if (existing != null) {
0531: action = "replace";
0532:
0533: _ctClass.removeMethod(existing);
0534: }
0535: } catch (NotFoundException ex) {
0536: // That's ok. Kind of sloppy to rely on a thrown exception; wish getDeclaredMethod()
0537: // would return null for
0538: // that case. Alternately, we could maintain a set of the method signatures of declared
0539: // or added methods.
0540: }
0541:
0542: try {
0543:
0544: CtMethod method = new CtMethod(returnType, signature
0545: .getMethodName(), parameters, _ctClass);
0546:
0547: // TODO: Check for duplicate method add
0548:
0549: method.setModifiers(signature.getModifiers());
0550:
0551: method.setBody(methodBody);
0552: method.setExceptionTypes(exceptions);
0553:
0554: _ctClass.addMethod(method);
0555:
0556: _addedMethods.add(method);
0557: } catch (CannotCompileException ex) {
0558: throw new MethodCompileException(ServicesMessages
0559: .methodCompileError(signature, methodBody, ex),
0560: methodBody, ex);
0561: } catch (NotFoundException ex) {
0562: throw new RuntimeException(ex);
0563: }
0564:
0565: addMethodToDescription(action, signature, methodBody);
0566: }
0567:
0568: private CtClass[] buildCtClassList(String[] typeNames) {
0569: CtClass[] result = new CtClass[typeNames.length];
0570:
0571: for (int i = 0; i < typeNames.length; i++)
0572: result[i] = findCtClass(typeNames[i]);
0573:
0574: return result;
0575: }
0576:
0577: private CtClass findCtClass(String type) {
0578: try {
0579: return _classPool.get(type);
0580: } catch (NotFoundException ex) {
0581: throw new RuntimeException(ex);
0582: }
0583: }
0584:
0585: public void extendMethod(MethodSignature methodSignature,
0586: String methodBody) {
0587: failIfFrozen();
0588:
0589: CtMethod method = findMethod(methodSignature);
0590:
0591: try {
0592: method.insertAfter(methodBody);
0593: } catch (CannotCompileException ex) {
0594: throw new MethodCompileException(
0595: ServicesMessages.methodCompileError(
0596: methodSignature, methodBody, ex),
0597: methodBody, ex);
0598: }
0599:
0600: addMethodToDescription("extend", methodSignature, methodBody);
0601:
0602: _addedMethods.add(method);
0603: }
0604:
0605: private void addMethodToDescription(String operation,
0606: MethodSignature methodSignature, String methodBody) {
0607: _formatter.format("%s method: %s %s %s(", operation, Modifier
0608: .toString(methodSignature.getModifiers()),
0609: methodSignature.getReturnType(), methodSignature
0610: .getMethodName());
0611:
0612: String[] parameterTypes = methodSignature.getParameterTypes();
0613: for (int i = 0; i < parameterTypes.length; i++) {
0614: if (i > 0)
0615: _description.append(", ");
0616:
0617: _formatter.format("%s $%d", parameterTypes[i], i + 1);
0618: }
0619:
0620: _description.append(")");
0621:
0622: String[] exceptionTypes = methodSignature.getExceptionTypes();
0623: for (int i = 0; i < exceptionTypes.length; i++) {
0624: if (i == 0)
0625: _description.append("\n throws ");
0626: else
0627: _description.append(", ");
0628:
0629: _description.append(exceptionTypes[i]);
0630: }
0631:
0632: _formatter.format("\n%s\n\n", methodBody);
0633: }
0634:
0635: private CtMethod findMethod(MethodSignature methodSignature) {
0636: CtMethod method = findDeclaredMethod(methodSignature);
0637:
0638: if (method != null)
0639: return method;
0640:
0641: CtMethod result = addOverrideOfSuperclassMethod(methodSignature);
0642:
0643: if (result != null)
0644: return result;
0645:
0646: throw new IllegalArgumentException(ServicesMessages
0647: .noDeclaredMethod(_ctClass, methodSignature));
0648: }
0649:
0650: private CtMethod findDeclaredMethod(MethodSignature methodSignature) {
0651: for (CtMethod method : _ctClass.getDeclaredMethods()) {
0652: if (match(method, methodSignature))
0653: return method;
0654: }
0655:
0656: return null;
0657: }
0658:
0659: private CtMethod addOverrideOfSuperclassMethod(
0660: MethodSignature methodSignature) {
0661: try {
0662: for (CtClass current = _ctClass; current != null; current = current
0663: .getSuperclass()) {
0664: for (CtMethod method : current.getDeclaredMethods()) {
0665: if (match(method, methodSignature)) {
0666: // TODO: If the moethod is not overridable (i.e. private, or final)?
0667: // Perhaps we should limit it to just public methods.
0668:
0669: CtMethod newMethod = CtNewMethod.delegator(
0670: method, _ctClass);
0671: _ctClass.addMethod(newMethod);
0672:
0673: return newMethod;
0674: }
0675: }
0676: }
0677: } catch (NotFoundException ex) {
0678: throw new RuntimeException(ex);
0679: } catch (CannotCompileException ex) {
0680: throw new RuntimeException(ex);
0681: }
0682:
0683: // Not found in a super-class.
0684:
0685: return null;
0686: }
0687:
0688: private boolean match(CtMethod method, MethodSignature sig) {
0689: if (!sig.getMethodName().equals(method.getName()))
0690: return false;
0691:
0692: CtClass[] paramTypes;
0693:
0694: try {
0695: paramTypes = method.getParameterTypes();
0696: } catch (NotFoundException ex) {
0697: throw new RuntimeException(ex);
0698: }
0699:
0700: String[] sigTypes = sig.getParameterTypes();
0701:
0702: int count = sigTypes.length;
0703:
0704: if (paramTypes.length != count)
0705: return false;
0706:
0707: for (int i = 0; i < count; i++) {
0708: String paramType = paramTypes[i].getName();
0709:
0710: if (!paramType.equals(sigTypes[i]))
0711: return false;
0712: }
0713:
0714: // Ignore exceptions thrown and modifiers.
0715: // TODO: Validate a match on return type?
0716:
0717: return true;
0718: }
0719:
0720: public List<String> findFieldsWithAnnotation(
0721: final Class<? extends Annotation> annotationClass) {
0722: FieldFilter filter = new FieldFilter() {
0723: public boolean accept(String fieldName, String fieldType) {
0724: return getFieldAnnotation(fieldName, annotationClass) != null;
0725: }
0726: };
0727:
0728: return findFields(filter);
0729: }
0730:
0731: public List<String> findFields(FieldFilter filter) {
0732: failIfFrozen();
0733:
0734: List<String> result = newList();
0735:
0736: try {
0737: for (CtField field : _ctClass.getDeclaredFields()) {
0738: if (!isInstanceField(field))
0739: continue;
0740:
0741: String fieldName = field.getName();
0742:
0743: if (_claimedFields.containsKey(fieldName))
0744: continue;
0745:
0746: if (filter.accept(fieldName, field.getType().getName()))
0747: result.add(fieldName);
0748:
0749: }
0750: } catch (NotFoundException ex) {
0751: throw new RuntimeException(ex);
0752: }
0753:
0754: Collections.sort(result);
0755:
0756: return result;
0757: }
0758:
0759: public List<String> findFieldsOfType(final String type) {
0760: FieldFilter filter = new FieldFilter() {
0761: public boolean accept(String fieldName, String fieldType) {
0762: return type.equals(fieldType);
0763: }
0764: };
0765:
0766: return findFields(filter);
0767: }
0768:
0769: public List<MethodSignature> findMethodsWithAnnotation(
0770: Class<? extends Annotation> annotationClass) {
0771: failIfFrozen();
0772:
0773: List<MethodSignature> result = newList();
0774:
0775: for (CtMethod method : _ctClass.getDeclaredMethods()) {
0776: List<Annotation> annotations = findMethodAnnotations(method);
0777:
0778: if (findAnnotationInList(annotationClass, annotations) != null) {
0779: MethodSignature sig = getMethodSignature(method);
0780: result.add(sig);
0781: }
0782: }
0783:
0784: Collections.sort(result);
0785:
0786: return result;
0787: }
0788:
0789: public List<MethodSignature> findMethods(MethodFilter filter) {
0790: notNull(filter, "filter");
0791:
0792: List<MethodSignature> result = newList();
0793:
0794: for (CtMethod method : _ctClass.getDeclaredMethods()) {
0795: MethodSignature sig = getMethodSignature(method);
0796:
0797: if (filter.accept(sig))
0798: result.add(sig);
0799: }
0800:
0801: Collections.sort(result);
0802:
0803: return result;
0804: }
0805:
0806: private MethodSignature getMethodSignature(CtMethod method) {
0807: MethodSignature result = _methodSignatures.get(method);
0808: if (result == null) {
0809: try {
0810: String type = method.getReturnType().getName();
0811: String[] parameters = toTypeNames(method
0812: .getParameterTypes());
0813: String[] exceptions = toTypeNames(method
0814: .getExceptionTypes());
0815:
0816: result = new MethodSignature(method.getModifiers(),
0817: type, method.getName(), parameters, exceptions);
0818:
0819: _methodSignatures.put(method, result);
0820: } catch (NotFoundException ex) {
0821: throw new RuntimeException(ex);
0822: }
0823: }
0824:
0825: return result;
0826: }
0827:
0828: private String[] toTypeNames(CtClass[] types) {
0829: String[] result = new String[types.length];
0830:
0831: for (int i = 0; i < types.length; i++)
0832: result[i] = types[i].getName();
0833:
0834: return result;
0835: }
0836:
0837: public List<String> findUnclaimedFields() {
0838: failIfFrozen();
0839:
0840: List<String> names = newList();
0841:
0842: Set<String> skipped = newSet();
0843:
0844: skipped.addAll(_claimedFields.keySet());
0845: skipped.addAll(_addedFieldNames);
0846:
0847: if (_removedFieldNames != null)
0848: skipped.addAll(_removedFieldNames);
0849:
0850: for (CtField field : _ctClass.getDeclaredFields()) {
0851: if (!isInstanceField(field))
0852: continue;
0853:
0854: String name = field.getName();
0855:
0856: if (skipped.contains(name))
0857: continue;
0858:
0859: // May need to add a filter to edit out explicitly added fields.
0860:
0861: names.add(name);
0862: }
0863:
0864: Collections.sort(names);
0865:
0866: return names;
0867: }
0868:
0869: private boolean isInstanceField(CtField field) {
0870: int modifiers = field.getModifiers();
0871:
0872: return Modifier.isPrivate(modifiers)
0873: && !Modifier.isStatic(modifiers);
0874: }
0875:
0876: public String getFieldType(String fieldName) {
0877: failIfFrozen();
0878:
0879: CtClass type = getFieldCtType(fieldName);
0880:
0881: return type.getName();
0882: }
0883:
0884: public boolean isField(String fieldName) {
0885: failIfFrozen();
0886:
0887: try {
0888: CtField field = _ctClass.getDeclaredField(fieldName);
0889:
0890: return isInstanceField(field);
0891: } catch (NotFoundException ex) {
0892: return false;
0893: }
0894: }
0895:
0896: public int getFieldModifiers(String fieldName) {
0897: failIfFrozen();
0898:
0899: try {
0900: return _ctClass.getDeclaredField(fieldName).getModifiers();
0901: } catch (NotFoundException ex) {
0902: throw new RuntimeException(ex);
0903: }
0904: }
0905:
0906: private CtClass getFieldCtType(String fieldName) {
0907: try {
0908: CtField field = _ctClass.getDeclaredField(fieldName);
0909:
0910: return field.getType();
0911: } catch (NotFoundException ex) {
0912: throw new RuntimeException(ex);
0913: }
0914: }
0915:
0916: public String addField(int modifiers, String type,
0917: String suggestedName) {
0918: failIfFrozen();
0919:
0920: String fieldName = newMemberName(suggestedName);
0921:
0922: try {
0923: CtClass ctType = convertNameToCtType(type);
0924:
0925: CtField field = new CtField(ctType, fieldName, _ctClass);
0926: field.setModifiers(modifiers);
0927:
0928: _ctClass.addField(field);
0929: } catch (NotFoundException ex) {
0930: throw new RuntimeException(ex);
0931: } catch (CannotCompileException ex) {
0932: throw new RuntimeException(ex);
0933: }
0934:
0935: _formatter.format("add field: %s %s %s;\n\n", Modifier
0936: .toString(modifiers), type, fieldName);
0937:
0938: _addedFieldNames.add(fieldName);
0939:
0940: return fieldName;
0941: }
0942:
0943: public String addInjectedField(Class type, String suggestedName,
0944: Object value) {
0945: notNull(type, "type");
0946:
0947: failIfFrozen();
0948:
0949: MultiKey key = new MultiKey(type, value);
0950:
0951: String fieldName = searchForPreviousInjection(key);
0952:
0953: if (fieldName != null)
0954: return fieldName;
0955:
0956: // TODO: Probably doesn't handle arrays and primitives.
0957:
0958: fieldName = addInjectedFieldUncached(type, suggestedName, value);
0959:
0960: // Remember the injection in-case this class, or a subclass, injects the value again.
0961:
0962: _injectionCache.put(key, fieldName);
0963:
0964: return fieldName;
0965: }
0966:
0967: /**
0968: * This is split out from {@link #addInjectedField(Class, String, Object)} to handle a special
0969: * case for the InternalComponentResources, which is null when "injected" (during the class
0970: * transformation) and is only determined when a component is actually instantiated.
0971: */
0972: private String addInjectedFieldUncached(Class type,
0973: String suggestedName, Object value) {
0974: CtClass ctType;
0975:
0976: try {
0977: ctType = _classPool.get(type.getName());
0978: } catch (NotFoundException ex) {
0979: throw new RuntimeException(ex);
0980: }
0981:
0982: String fieldName = addField(
0983: Modifier.PROTECTED | Modifier.FINAL, type.getName(),
0984: suggestedName);
0985:
0986: addInjectToConstructor(fieldName, ctType, value);
0987:
0988: return fieldName;
0989: }
0990:
0991: public String searchForPreviousInjection(MultiKey key) {
0992: String result = _injectionCache.get(key);
0993:
0994: if (result != null)
0995: return result;
0996:
0997: if (_parentTransformation != null)
0998: return _parentTransformation
0999: .searchForPreviousInjection(key);
1000:
1001: return null;
1002: }
1003:
1004: /**
1005: * Adds a parameter to the constructor for the class; the parameter is used to initialize the
1006: * value for a field.
1007: *
1008: * @param fieldName
1009: * name of field to inject
1010: * @param fieldType
1011: * Javassist type of the field (and corresponding parameter)
1012: * @param value
1013: * the value to be injected (which will in unusual cases be null)
1014: */
1015: private void addInjectToConstructor(String fieldName,
1016: CtClass fieldType, Object value) {
1017: _constructorArgs.add(new ConstructorArg(fieldType, value));
1018:
1019: extendConstructor(format(" %s = $%d;", fieldName,
1020: _constructorArgs.size()));
1021: }
1022:
1023: public void injectField(String fieldName, Object value) {
1024: notNull(fieldName, "fieldName");
1025:
1026: failIfFrozen();
1027:
1028: CtClass type = getFieldCtType(fieldName);
1029:
1030: addInjectToConstructor(fieldName, type, value);
1031:
1032: makeReadOnly(fieldName);
1033: }
1034:
1035: private CtClass convertNameToCtType(String type)
1036: throws NotFoundException {
1037: return _classPool.get(type);
1038: }
1039:
1040: public void finish() {
1041: failIfFrozen();
1042:
1043: performFieldTransformations();
1044:
1045: addConstructor();
1046:
1047: verifyFields();
1048:
1049: freeze();
1050: }
1051:
1052: private void addConstructor() {
1053: String initializer = _idAllocator.allocateId("initializer");
1054:
1055: try {
1056: CtConstructor defaultConstructor = _ctClass
1057: .getConstructor("()V");
1058:
1059: CtMethod initializerMethod = defaultConstructor.toMethod(
1060: initializer, _ctClass);
1061:
1062: _ctClass.addMethod(initializerMethod);
1063: } catch (Exception ex) {
1064: throw new RuntimeException(ex);
1065: }
1066:
1067: _formatter.format("convert default constructor: %s();\n\n",
1068: initializer);
1069:
1070: int count = _constructorArgs.size();
1071:
1072: CtClass[] types = new CtClass[count];
1073:
1074: for (int i = 0; i < count; i++) {
1075: ConstructorArg arg = _constructorArgs.get(i);
1076:
1077: types[i] = arg.getType();
1078: }
1079:
1080: // Add a call to the initializer; the method converted fromt the classes default
1081: // constructor.
1082:
1083: _constructor.append(" ");
1084: _constructor.append(initializer);
1085:
1086: // This finally matches the "{" added inside the constructor
1087:
1088: _constructor.append("();\n\n}");
1089:
1090: String constructorBody = _constructor.toString();
1091:
1092: try {
1093: CtConstructor cons = CtNewConstructor.make(types, null,
1094: constructorBody, _ctClass);
1095: _ctClass.addConstructor(cons);
1096: } catch (CannotCompileException ex) {
1097: throw new RuntimeException(ex);
1098: }
1099:
1100: _formatter.format("add constructor: %s(", _ctClass.getName());
1101:
1102: for (int i = 0; i < count; i++) {
1103: if (i > 0)
1104: _description.append(", ");
1105:
1106: _formatter.format("%s $%d", types[i].getName(), i + 1);
1107: }
1108:
1109: _formatter.format(")\n%s\n\n", constructorBody);
1110: }
1111:
1112: public Instantiator createInstantiator(Class componentClass) {
1113: String className = _ctClass.getName();
1114:
1115: if (!className.equals(componentClass.getName()))
1116: throw new IllegalArgumentException(ServicesMessages
1117: .incorrectClassForInstantiator(className,
1118: componentClass));
1119:
1120: Object[] parameters = new Object[_constructorArgs.size()];
1121:
1122: // Skip the first constructor argument, it's always a placeholder
1123: // for the InternalComponentResources instance that's provided
1124: // later.
1125:
1126: for (int i = 1; i < _constructorArgs.size(); i++) {
1127: parameters[i] = _constructorArgs.get(i).getValue();
1128: }
1129:
1130: return new ReflectiveInstantiator(_componentModel,
1131: componentClass, parameters);
1132: }
1133:
1134: private void failIfFrozen() {
1135: if (_frozen)
1136: throw new IllegalStateException(
1137: "The ClassTransformation instance (for "
1138: + _ctClass.getName()
1139: + ") has completed all transformations and may not be further modified.");
1140: }
1141:
1142: private void failIfNotFrozen() {
1143: if (!_frozen)
1144: throw new IllegalStateException(
1145: "The ClassTransformation instance (for "
1146: + _ctClass.getName()
1147: + ") has not yet completed all transformations.");
1148: }
1149:
1150: public IdAllocator getIdAllocator() {
1151: failIfNotFrozen();
1152:
1153: return _idAllocator;
1154: }
1155:
1156: public List<ConstructorArg> getConstructorArgs() {
1157: failIfNotFrozen();
1158:
1159: return CollectionFactory.newList(_constructorArgs);
1160: }
1161:
1162: public List<Annotation> getClassAnnotations() {
1163: failIfFrozen();
1164:
1165: if (_classAnnotations == null)
1166: assembleClassAnnotations();
1167:
1168: return _classAnnotations;
1169: }
1170:
1171: private void assembleClassAnnotations() {
1172: _classAnnotations = newList();
1173:
1174: try {
1175: for (CtClass current = _ctClass; current != null; current = current
1176: .getSuperclass()) {
1177: addAnnotationsToList(_classAnnotations, current
1178: .getAnnotations());
1179: }
1180: } catch (NotFoundException ex) {
1181: throw new RuntimeException(ex);
1182: } catch (ClassNotFoundException ex) {
1183: throw new RuntimeException(ex);
1184: }
1185: }
1186:
1187: @Override
1188: public String toString() {
1189: StringBuilder builder = new StringBuilder(
1190: "InternalClassTransformation[\n");
1191:
1192: try {
1193: Formatter formatter = new Formatter(builder);
1194:
1195: formatter.format("%s %s extends %s", Modifier
1196: .toString(_ctClass.getModifiers()), _ctClass
1197: .getName(), _ctClass.getSuperclass().getName());
1198:
1199: CtClass[] interfaces = _ctClass.getInterfaces();
1200:
1201: for (int i = 0; i < interfaces.length; i++) {
1202: if (i == 0)
1203: builder.append("\n implements ");
1204: else
1205: builder.append(", ");
1206:
1207: builder.append(interfaces[i].getName());
1208: }
1209:
1210: formatter.format("\n\n%s", _description.toString());
1211: } catch (NotFoundException ex) {
1212: builder.append(ex);
1213: }
1214:
1215: builder.append("]");
1216:
1217: return builder.toString();
1218: }
1219:
1220: public void makeReadOnly(String fieldName) {
1221: String methodName = newMemberName("write", fieldName);
1222:
1223: String fieldType = getFieldType(fieldName);
1224:
1225: MethodSignature sig = new MethodSignature(Modifier.PRIVATE,
1226: "void", methodName, new String[] { fieldType }, null);
1227:
1228: String message = ServicesMessages.readOnlyField(_ctClass
1229: .getName(), fieldName);
1230:
1231: String body = format(
1232: "throw new java.lang.RuntimeException(\"%s\");",
1233: message);
1234:
1235: addMethod(sig, body);
1236:
1237: replaceWriteAccess(fieldName, methodName);
1238: }
1239:
1240: public void removeField(String fieldName) {
1241: _formatter.format("remove field %s;\n\n", fieldName);
1242:
1243: // TODO: We could check that there's an existing field read and field write transform ...
1244:
1245: if (_removedFieldNames == null)
1246: _removedFieldNames = newSet();
1247:
1248: _removedFieldNames.add(fieldName);
1249:
1250: }
1251:
1252: public void replaceReadAccess(String fieldName, String methodName) {
1253: // Explicitly reference $0 (aka "this") because of TAPESTRY-1511.
1254: // $0 is valid even inside a static method.
1255:
1256: String body = String.format("$_ = $0.%s();", methodName);
1257:
1258: if (_fieldReadTransforms == null)
1259: _fieldReadTransforms = newMap();
1260:
1261: // TODO: Collisions?
1262:
1263: _fieldReadTransforms.put(fieldName, body);
1264:
1265: _formatter.format("replace read %s: %s();\n\n", fieldName,
1266: methodName);
1267: }
1268:
1269: public void replaceWriteAccess(String fieldName, String methodName) {
1270: // Explicitly reference $0 (aka "this") because of TAPESTRY-1511.
1271: // $0 is valid even inside a static method.
1272:
1273: String body = String.format("$0.%s($1);", methodName);
1274:
1275: if (_fieldWriteTransforms == null)
1276: _fieldWriteTransforms = newMap();
1277:
1278: // TODO: Collisions?
1279:
1280: _fieldWriteTransforms.put(fieldName, body);
1281:
1282: _formatter.format("replace write %s: %s();\n\n", fieldName,
1283: methodName);
1284: }
1285:
1286: private void performFieldTransformations() {
1287: // If no field transformations have been requested, then we can save ourselves some
1288: // trouble!
1289:
1290: if (_fieldReadTransforms != null
1291: || _fieldWriteTransforms != null)
1292: replaceFieldAccess();
1293:
1294: if (_removedFieldNames != null) {
1295: for (String fieldName : _removedFieldNames) {
1296: try {
1297: CtField field = _ctClass
1298: .getDeclaredField(fieldName);
1299: _ctClass.removeField(field);
1300: } catch (NotFoundException ex) {
1301: throw new RuntimeException(ex);
1302: }
1303: }
1304: }
1305: }
1306:
1307: static final int SYNTHETIC = 0x00001000;
1308:
1309: private void replaceFieldAccess() {
1310: // Provide empty maps here, to make the code in the inner class a tad
1311: // easier.
1312:
1313: if (_fieldReadTransforms == null)
1314: _fieldReadTransforms = newMap();
1315:
1316: if (_fieldWriteTransforms == null)
1317: _fieldWriteTransforms = newMap();
1318:
1319: ExprEditor editor = new ExprEditor() {
1320: @Override
1321: public void edit(FieldAccess access)
1322: throws CannotCompileException {
1323: // Ignore any methods to were added as part of the transformation.
1324: // If we reference the field there, we really mean the field.
1325:
1326: if (_addedMethods.contains(access.where()))
1327: return;
1328:
1329: Map<String, String> transformMap = access.isReader() ? _fieldReadTransforms
1330: : _fieldWriteTransforms;
1331:
1332: String body = transformMap.get(access.getFieldName());
1333: if (body == null)
1334: return;
1335:
1336: access.replace(body);
1337: }
1338: };
1339:
1340: try {
1341: _ctClass.instrument(editor);
1342: } catch (CannotCompileException ex) {
1343: throw new RuntimeException(ex);
1344: }
1345: }
1346:
1347: public Class toClass(String type) {
1348: failIfFrozen();
1349:
1350: // No reason why this can't be allowed to work after freezing.
1351:
1352: String finalType = TransformUtils.getWrapperTypeName(type);
1353:
1354: try {
1355: return Class.forName(finalType, true, _loader);
1356: } catch (ClassNotFoundException ex) {
1357: throw new RuntimeException(ex);
1358: }
1359: }
1360:
1361: public String getClassName() {
1362: return _ctClass.getName();
1363: }
1364:
1365: public Log getLog() {
1366: return _log;
1367: }
1368:
1369: public void extendConstructor(String statement) {
1370: notNull(statement, "statement");
1371:
1372: failIfFrozen();
1373:
1374: _constructor.append(statement);
1375: _constructor.append("\n");
1376: }
1377:
1378: public String getMethodIdentifier(MethodSignature signature) {
1379: notNull(signature, "signature");
1380:
1381: CtMethod method = findMethod(signature);
1382:
1383: int lineNumber = method.getMethodInfo2().getLineNumber(0);
1384: CtClass enclosingClass = method.getDeclaringClass();
1385: String sourceFile = enclosingClass.getClassFile2()
1386: .getSourceFile();
1387:
1388: return format("%s.%s (at %s:%d)", enclosingClass.getName(),
1389: signature.getMediumDescription(), sourceFile,
1390: lineNumber);
1391: }
1392:
1393: }
|