@Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (context != null ? context.hashCode() : 0); result = 31 * result + (className != null ? className.hashCode() : 0); result = 31 * result + (superClass != null ? superClass.hashCode() : 0); result = 31 * result + (interfaces != null ? interfaces.hashCode() : 0); result = 31 * result + (scope != null ? scope.hashCode() : 0); result = 31 * result + (isArray ? 1 : 0); result = 31 * result + dimensions; result = 31 * result + (isInterface ? 1 : 0); result = 31 * result + (isAbstract ? 1 : 0); result = 31 * result + (isFinal ? 1 : 0); result = 31 * result + (isStatic ? 1 : 0); result = 31 * result + (isInner ? 1 : 0); result = 31 * result + (methods != null ? methods.hashCode() : 0); result = 31 * result + (fields != null ? fields.hashCode() : 0); result = 31 * result + (constructors != null ? constructors.hashCode() : 0); result = 31 * result + (typeVariables != null ? typeVariables.hashCode() : 0); result = 31 * result + (reifiedFormOf != null ? reifiedFormOf.hashCode() : 0); result = 31 * result + (_nameCache != null ? _nameCache.hashCode() : 0); result = 31 * result + (_methodsCache != null ? Arrays.hashCode(_methodsCache) : 0); result = 31 * result + (_fieldsCache != null ? Arrays.hashCode(_fieldsCache) : 0); result = 31 * result + (_constructorsCache != null ? Arrays.hashCode(_constructorsCache) : 0); result = 31 * result + (generatedCache != null ? generatedCache.hashCode() : 0); return result; }
@Override public Collection<MetaClass> provideTypesToExpose() { final Set<MetaClass> types = new HashSet<MetaClass>(); for (final MetaClass metaClass : ClassScanner.getTypesAnnotatedWith(Remote.class)) { for (final MetaMethod method : metaClass.getDeclaredMethods()) { if (!method.getReturnType().isVoid()) { types.add(method.getReturnType().getErased()); } for (final MetaParameter parameter : method.getParameters()) { final MetaClass type = parameter.getType(); types.add(type.getErased()); final MetaParameterizedType parameterizedType = type.getParameterizedType(); if (parameterizedType != null) { for (final MetaType tp : parameterizedType.getTypeParameters()) { if (tp instanceof MetaClass) { types.add(((MetaClass) tp).getErased()); } } } } } } return types; }
private static List<MetaField> getAllFields(MetaClass c) { ArrayList<MetaField> fields = new ArrayList<MetaField>(); for (; c != null; c = c.getSuperClass()) { fields.addAll(Arrays.asList(c.getDeclaredFields())); } return fields; }
public static MetaMethod findCaseInsensitiveMatch( final MetaClass retType, final MetaClass clazz, final String name, final MetaClass... parms) { MetaClass c = clazz; do { Outer: for (final MetaMethod method : c.getDeclaredMethods()) { if (name.equalsIgnoreCase(method.getName())) { if (parms.length != method.getParameters().length) continue; final MetaParameter[] mps = method.getParameters(); for (int i = 0; i < parms.length; i++) { if (!parms[i] .getFullyQualifiedName() .equals(mps[i].getType().getFullyQualifiedName())) { continue Outer; } } if (retType != null && !retType .getFullyQualifiedName() .equals(method.getReturnType().getFullyQualifiedName())) { continue; } return method; } } } while ((c = c.getSuperClass()) != null); return null; }
public static BuildMetaClass makeProxy(final MetaClass toProxy, final String privateAccessorType, final Map<String, ProxyProperty> proxyProperties, final Map<MetaMethod, Map<WeaveType, Collection<Statement>>> weavingStatements) { return makeProxy( PrivateAccessUtil.condensify(toProxy.getPackageName()) + "_" + toProxy.getName() + "_proxy", toProxy, privateAccessorType, proxyProperties, weavingStatements); }
public static int getArrayDimensions(final MetaClass type) { if (!type.isArray()) return 0; final String internalName = type.getInternalName(); for (int i = 0; i < internalName.length(); i++) { if (internalName.charAt(i) != '[') return i; } return 0; }
public static boolean canConvert(final MetaClass to, final MetaClass from) { try { final Class<?> fromClazz = from.asClass(); final Class<?> toClass = to.asClass(); return DataConversion.canConvert(toClass, fromClazz); } catch (Throwable t) { return false; } }
private static void fillInInterfacesAndSuperTypes( final Set<MetaClass> set, final MetaClass type) { for (final MetaClass iface : type.getInterfaces()) { set.add(iface); fillInInterfacesAndSuperTypes(set, iface); } if (type.getSuperClass() != null) { fillInInterfacesAndSuperTypes(set, type.getSuperClass()); } }
public static Scope scopeOf(final MetaClass clazz) { if (clazz.isPublic()) { return Scope.Public; } else if (clazz.isPrivate()) { return Scope.Private; } else if (clazz.isProtected()) { return Scope.Protected; } else { return Scope.Package; } }
/** * Ensures the provided type is a {@link DataBinder} and throws a {@link GenerationException} in * case it's not. * * @param type the type to check */ private static void assertTypeIsDataBinder(MetaClass type) { final MetaClass databinderMetaClass = MetaClassFactory.get(DataBinder.class); if (!databinderMetaClass.isAssignableFrom(type)) { throw new GenerationException( "Type of @AutoBound element must be " + DataBinder.class.getName() + " but is: " + type.getFullyQualifiedName()); } }
/** * Tries to find a reference for an injected {@link AutoBound} data binder. * * @return the data binder reference or null if not found. */ private static DataBinderRef lookupAutoBoundBinder(final InjectableInstance<?> inst) { Statement dataBinderRef = null; MetaClass dataModelType = null; InjectUtil.BeanMetric beanMetric = InjectUtil.getFilteredBeanMetric( inst.getInjectionContext(), inst.getInjector().getInjectedType(), AutoBound.class); final Collection<Object> allInjectors = beanMetric.getAllInjectors(); if (allInjectors.size() > 1) { throw new GenerationException( "Multiple @AutoBound data binders injected in " + inst.getEnclosingType()); } else if (allInjectors.size() == 1) { final Object injectorElement = allInjectors.iterator().next(); if (injectorElement instanceof MetaConstructor || injectorElement instanceof MetaMethod) { final MetaParameter mp = beanMetric.getConsolidatedMetaParameters().iterator().next(); assertTypeIsDataBinder(mp.getType()); dataModelType = (MetaClass) mp.getType().getParameterizedType().getTypeParameters()[0]; dataBinderRef = inst.getInjectionContext().getInlineBeanReference(mp); inst.ensureMemberExposed(); } else { final MetaField field = (MetaField) allInjectors.iterator().next(); assertTypeIsDataBinder(field.getType()); dataModelType = (MetaClass) field.getType().getParameterizedType().getTypeParameters()[0]; dataBinderRef = Stmt.invokeStatic( inst.getInjectionContext().getProcessingContext().getBootstrapClass(), PrivateAccessUtil.getPrivateFieldInjectorName(field), Variable.get(inst.getInjector().getInstanceVarName())); inst.getInjectionContext().addExposedField(field, PrivateAccessType.Both); } } else { final MetaClass declaringClass = inst.getInjector().getInjectedType(); for (final MetaField field : declaringClass.getFields()) { if (field.isAnnotationPresent(AutoBound.class)) { assertTypeIsDataBinder(field.getType()); dataModelType = (MetaClass) field.getType().getParameterizedType().getTypeParameters()[0]; dataBinderRef = Stmt.invokeStatic( inst.getInjectionContext().getProcessingContext().getBootstrapClass(), PrivateAccessUtil.getPrivateFieldInjectorName(field), Variable.get(inst.getInjector().getInstanceVarName())); inst.getInjectionContext().addExposedField(field, PrivateAccessType.Both); break; } } } return (dataBinderRef != null) ? new DataBinderRef(dataModelType, dataBinderRef) : null; }
private void createValidationError(Collection<MetaClass> pages, Class<?> role) { StringBuilder builder = new StringBuilder(); for (MetaClass mc : pages) { builder.append("\n ").append(mc.getFullyQualifiedName()); } throw new GenerationException( "Found more than one @Page with role = '" + role + "': " + builder + "\nExactly one @Page class must be designated with this unique role."); }
public static int scoreInterface(final MetaClass parm, final MetaClass arg) { if (parm.isInterface()) { final MetaClass[] iface = arg.getInterfaces(); if (iface != null) { for (final MetaClass c : iface) { if (c == parm) return 1; else if (parm.isAssignableFrom(c)) return scoreInterface(parm, arg.getSuperClass()); } } } return 0; }
/** * Renders the page-to-page navigation graph into the file {@code navgraph.gv} in the {@code * .errai} cache directory. */ private void renderNavigationToDotFile(BiMap<String, MetaClass> pages) { final File dotFile = new File(RebindUtils.getErraiCacheDir().getAbsolutePath(), "navgraph.gv"); PrintWriter out = null; try { out = new PrintWriter(dotFile); out.println("digraph Navigation {"); final MetaClass transitionToType = MetaClassFactory.get(TransitionTo.class); final MetaClass transitionAnchorType = MetaClassFactory.get(TransitionAnchor.class); final MetaClass transitionAnchorFactoryType = MetaClassFactory.get(TransitionAnchorFactory.class); for (Map.Entry<String, MetaClass> entry : pages.entrySet()) { String pageName = entry.getKey(); MetaClass pageClass = entry.getValue(); // entry for the node itself out.print("\"" + pageName + "\""); Page pageAnnotation = pageClass.getAnnotation(Page.class); List<Class<? extends PageRole>> roles = Arrays.asList(pageAnnotation.role()); if (roles.contains(DefaultPage.class)) { out.print(" [penwidth=3]"); } out.println(); for (MetaField field : getAllFields(pageClass)) { if (field.getType().getErased().equals(transitionToType) || field.getType().getErased().equals(transitionAnchorType) || field.getType().getErased().equals(transitionAnchorFactoryType)) { MetaType targetPageType = field.getType().getParameterizedType().getTypeParameters()[0]; String targetPageName = pages.inverse().get(targetPageType); // entry for the link between nodes out.println( "\"" + pageName + "\" -> \"" + targetPageName + "\" [label=\"" + field.getName() + "\"]"); } } } out.println("}"); } catch (FileNotFoundException e) { throw new RuntimeException(e); } finally { if (out != null) { out.close(); } } }
private Statement defaultValueStatement(MetaClass type) { if (type.isPrimitive()) { if (type.asBoxed().isAssignableTo(Number.class)) { return Stmt.castTo(type, Stmt.load(0)); } else if (type.isAssignableTo(boolean.class)) { return Stmt.load(false); } else { throw new UnsupportedOperationException( "Don't know how to make a default value for @PageState field of type " + type.getFullyQualifiedName()); } } else { return Stmt.load(null); } }
public static Statement convert(final Context context, Object input, final MetaClass targetType) { try { if (input instanceof Statement) { if (input instanceof LiteralValue<?>) { input = ((LiteralValue<?>) input).getValue(); } else { if ("null".equals(((Statement) input).generate(context))) { return (Statement) input; } assertAssignableTypes(context, ((Statement) input).getType(), targetType); return (Statement) input; } } if (input != null && MetaClassFactory.get(input.getClass()) .getOuterComponentType() .getFullyQualifiedName() .equals(MetaClass.class.getName())) { return generate(context, input); } if (Object.class.getName().equals(targetType.getFullyQualifiedName())) { return generate(context, input); } Class<?> inputClass = input == null ? Object.class : input.getClass(); if (MetaClass.class.isAssignableFrom(inputClass)) { inputClass = Class.class; } final Class<?> targetClass = targetType.asBoxed().asClass(); if (NullType.class.getName().equals(targetClass.getName())) { return generate(context, input); } if (!targetClass.isAssignableFrom(inputClass) && DataConversion.canConvert(targetClass, inputClass)) { return generate(context, DataConversion.convert(input, targetClass)); } else { return generate(context, input); } } catch (NumberFormatException nfe) { throw new InvalidTypeException(nfe); } }
@Override public MetaMethod[] getMethods() { if (_methodsCache != null) return _methodsCache; MetaMethod[] methodArray = methods.toArray(new MetaMethod[methods.size()]); MetaMethod[] outputMethods; if (superClass != null) { List<MetaMethod> methodList = new ArrayList<MetaMethod>(); for (MetaMethod m : superClass.getMethods()) { if (_getMethod(methodArray, m.getName(), GenUtil.fromParameters(m.getParameters())) == null) { methodList.add(m); } } methodList.addAll(Arrays.asList(methodArray)); outputMethods = methodList.toArray(new MetaMethod[methodList.size()]); } else { outputMethods = methodArray; } return _methodsCache = outputMethods; }
private static Statement paramFromStringStatement(MetaClass toType, Statement stringValue) { // make sure it's really a string stringValue = Stmt.castTo(String.class, stringValue); if (toType.isAssignableTo(String.class)) { return stringValue; } else if (toType.asBoxed().isAssignableTo(Number.class)) { return Stmt.invokeStatic(toType.asBoxed(), "valueOf", stringValue); } else if (toType.asBoxed().isAssignableTo(Boolean.class)) { return Stmt.invokeStatic(Boolean.class, "valueOf", stringValue); } else { throw new UnsupportedOperationException( "@PageState fields of type " + toType.getFullyQualifiedName() + " are not supported"); } }
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof BuildMetaClass)) return false; if (!super.equals(o)) return false; BuildMetaClass that = (BuildMetaClass) o; if (dimensions != that.dimensions) return false; if (isAbstract != that.isAbstract) return false; if (isArray != that.isArray) return false; if (isFinal != that.isFinal) return false; if (isInner != that.isInner) return false; if (isInterface != that.isInterface) return false; if (isStatic != that.isStatic) return false; if (!Arrays.equals(_constructorsCache, that._constructorsCache)) return false; if (!Arrays.equals(_fieldsCache, that._fieldsCache)) return false; if (!Arrays.equals(_methodsCache, that._methodsCache)) return false; if (_nameCache != null ? !_nameCache.equals(that._nameCache) : that._nameCache != null) return false; if (className != null ? !className.equals(that.className) : that.className != null) return false; if (constructors != null ? !constructors.equals(that.constructors) : that.constructors != null) return false; if (context != null ? !context.equals(that.context) : that.context != null) return false; if (fields != null ? !fields.equals(that.fields) : that.fields != null) return false; if (generatedCache != null ? !generatedCache.equals(that.generatedCache) : that.generatedCache != null) return false; if (interfaces != null ? !interfaces.equals(that.interfaces) : that.interfaces != null) return false; if (methods != null ? !methods.equals(that.methods) : that.methods != null) return false; if (reifiedFormOf != null ? !reifiedFormOf.equals(that.reifiedFormOf) : that.reifiedFormOf != null) return false; if (scope != that.scope) return false; if (superClass != null ? !superClass.equals(that.superClass) : that.superClass != null) return false; if (typeVariables != null ? !typeVariables.equals(that.typeVariables) : that.typeVariables != null) return false; return true; }
@Override public MetaClassMember getReadingMember() { if (readingMember != null) { return readingMember; } MetaMethod meth = toMap.getMethod(getterMethod, new MetaClass[0]); meth.asMethod().setAccessible(true); readingMember = meth; if (readingMember == null) { throw new RuntimeException( "no such getter method: " + toMap.getFullyQualifiedName() + "." + getterMethod); } return readingMember; }
/** * Appends the method that calls the {@code @PageHidden} method of the widget. * * @param pageImplBuilder The class builder for the implementation of PageNode we are adding the * method to. * @param pageClass The "content type" (Widget subclass) of the page. This is the type the user * annotated with {@code @Page}. */ private void appendPageHiddenMethod( AnonymousClassStructureBuilder pageImplBuilder, MetaClass pageClass) { BlockBuilder<?> method = pageImplBuilder .publicMethod( void.class, createMethodNameFromAnnotation(PageHidden.class), Parameter.of(pageClass, "widget")) .body(); checkMethodAndAddPrivateAccessors( pageImplBuilder, method, pageClass, PageHidden.class, HistoryToken.class, "state"); if (pageClass.getAnnotation(Singleton.class) == null && pageClass.getAnnotation(ApplicationScoped.class) == null && pageClass.getAnnotation(EntryPoint.class) == null) { method.append(Stmt.loadVariable("bm").invoke("destroyBean", Stmt.loadVariable("widget"))); } method.finish(); }
public static MetaMethod getBestCandidate( final MetaClass[] arguments, final String method, final MetaClass decl, MetaMethod[] methods, final boolean classTarget) { if (methods == null || methods.length == 0) { return null; } MetaParameter[] parmTypes; MetaMethod bestCandidate = null; int bestScore = 0; int score; boolean retry = false; do { for (final MetaMethod meth : methods) { if (classTarget && (meth.isStatic())) continue; if (method.equals(meth.getName())) { final boolean isVarArgs = meth.isVarArgs(); if ((parmTypes = meth.getParameters()).length != arguments.length && !isVarArgs) { continue; } else if (arguments.length == 0 && parmTypes.length == 0) { bestCandidate = meth; break; } score = scoreMethods(arguments, parmTypes, isVarArgs); if (score != 0 && score > bestScore) { bestCandidate = meth; bestScore = score; } } } if (!retry && bestCandidate == null && decl.isInterface()) { final MetaMethod[] objMethods = Object_MetaClass.getMethods(); final MetaMethod[] nMethods = new MetaMethod[methods.length + objMethods.length]; System.arraycopy(methods, 0, nMethods, 0, methods.length); System.arraycopy(objMethods, 0, nMethods, methods.length, objMethods.length); methods = nMethods; retry = true; } else { break; } } while (true); return bestCandidate; }
public static void assertAssignableTypes( final Context context, final MetaClass from, final MetaClass to) { if (!to.asBoxed().isAssignableFrom(from.asBoxed())) { if (to.isArray() && from.isArray() && GenUtil.getArrayDimensions(to) == GenUtil.getArrayDimensions(from) && to.getOuterComponentType().isAssignableFrom(from.getOuterComponentType())) { return; } if (!context.isPermissiveMode()) { if (classAliases.contains(from.getFullyQualifiedName()) && classAliases.contains(to.getFullyQualifiedName())) { // handle convertibility between MetaClass API and java Class reference. return; } throw new InvalidTypeException( to.getFullyQualifiedName() + " is not assignable from " + from.getFullyQualifiedName()); } } }
public static boolean isNumericallyCoercible(final MetaClass target, final MetaClass parm) { MetaClass boxedTarget = target.isPrimitive() ? target.asBoxed() : target; if (boxedTarget != null && Number_MetaClass.isAssignableFrom(target)) { if ((boxedTarget = parm.isPrimitive() ? parm.asBoxed() : parm) != null) { return Number_MetaClass.isAssignableFrom(boxedTarget); } } return false; }
public static boolean isPrimitiveWrapper(final MetaClass clazz) { return Integer.class.getName().equals(clazz.getFullyQualifiedName()) || Boolean.class.getName().equals(clazz.getFullyQualifiedName()) || Long.class.getName().equals(clazz.getFullyQualifiedName()) || Double.class.getName().equals(clazz.getFullyQualifiedName()) || Float.class.getName().equals(clazz.getFullyQualifiedName()) || Short.class.getName().equals(clazz.getFullyQualifiedName()) || Character.class.getName().equals(clazz.getFullyQualifiedName()) || Byte.class.getName().equals(clazz.getFullyQualifiedName()); }
public static MetaClass getPrimitiveWrapper(final MetaClass clazz) { if (clazz.isPrimitive()) { if ("int".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Integer.class); } else if ("boolean".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Boolean.class); } else if ("long".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Long.class); } else if ("double".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Double.class); } else if ("float".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Float.class); } else if ("short".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Short.class); } else if ("char".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Character.class); } else if ("byte".equals(clazz.getCanonicalName())) { return MetaClassFactory.get(Byte.class); } } return clazz; }
public static MetaClass getUnboxedFromWrapper(final MetaClass clazz) { if (Integer.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(int.class); } else if (Boolean.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(boolean.class); } else if (Long.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(long.class); } else if (Double.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(double.class); } else if (Float.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(float.class); } else if (Short.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(short.class); } else if (Character.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(char.class); } else if (Byte.class.getName().equals(clazz.getFullyQualifiedName())) { return MetaClassFactory.get(byte.class); } return clazz; }
public ProxyInjector(final IOCProcessingContext context, final MetaClass proxiedType, final QualifyingMetadata metadata) { Assert.notNull(proxiedType); Assert.notNull(metadata); this.proxiedType = proxiedType; this.varName = InjectUtil.getNewInjectorName() + "_proxy"; this.qualifyingMetadata = AbstractInjector.getMetadataWithAny(metadata); final String proxyClassName = proxiedType.getName() + "_" + varName; this.closeStatements = new ArrayList<Statement>(); this.proxyClass = ProxyMaker.makeProxy(proxyClassName, proxiedType, context.isGwtTarget() ? "jsni" : "reflection"); this.proxyClass.setStatic(true); this.proxyClass.setScope(Scope.Package); context.getBootstrapClass() .addInnerClass(new InnerClass(proxyClass)); }
BuildMetaClass make(final String proxyClassName, final MetaClass toProxy, final String privateAccessorType) { final ClassStructureBuilder builder; final boolean renderEqualsAndHash; if (!toProxy.isInterface()) { renderEqualsAndHash = true; if (toProxy.isFinal()) { throw new UnproxyableClassException(toProxy, toProxy.getFullyQualifiedName() + " is an unproxiable class because it is final"); } if (!toProxy.isDefaultInstantiable()) { throw new UnproxyableClassException(toProxy, toProxy.getFullyQualifiedName() + " must have a default " + "no-arg constructor"); } builder = ClassBuilder.define(proxyClassName, toProxy).publicScope().body(); } else { renderEqualsAndHash = false; builder = ClassBuilder.define(proxyClassName).publicScope().implementsInterface(toProxy).body(); } final String proxyVar = "$$_proxy_$$"; final Set<String> renderedMethods = new HashSet<String>(); final Map<String, MetaType> typeVariableMap = new HashMap<String, MetaType>(); final MetaParameterizedType metaParameterizedType = toProxy.getParameterizedType(); if (metaParameterizedType != null) { int i = 0; for (final MetaTypeVariable metaTypeVariable : toProxy.getTypeParameters()) { typeVariableMap.put(metaTypeVariable.getName(), metaParameterizedType.getTypeParameters()[i++]); } } builder.privateField(proxyVar, toProxy).finish(); final Set<Map.Entry<String, ProxyProperty>> entries = proxyProperties.entrySet(); for (final Map.Entry<String, ProxyProperty> entry : entries) { builder.privateField(entry.getValue().getEncodedProperty(), entry.getValue().getType()).finish(); builder.packageMethod(void.class, "$set_" + entry.getKey(), Parameter.of(entry.getValue().getType(), "o")) .append(Stmt.loadVariable(entry.getValue().getEncodedProperty()).assignValue(Refs.get("o"))) .finish(); } for (final MetaMethod method : toProxy.getMethods()) { final String methodString = GenUtil.getMethodString(method); if (renderedMethods.contains(methodString) || method.getName().equals("hashCode") || (method.getName().equals("equals") && method.getParameters().length == 1 && method.getParameters()[0].getType().getFullyQualifiedName().equals(Object.class.getName()))) continue; renderedMethods.add(methodString); if ((!method.isPublic() && !method.isProtected()) || method.isSynthetic() || method.isFinal() || method.isStatic() || method.getDeclaringClass().getFullyQualifiedName().equals(Object.class.getName())) continue; final List<Parameter> methodParms = new ArrayList<Parameter>(); final MetaParameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { methodParms.add(Parameter.of(parameters[i].getType().getErased(), "a" + i)); } final DefParameters defParameters = DefParameters.fromParameters(methodParms); final BlockBuilder methBody = builder.publicMethod(method.getReturnType(), method.getName()) .annotatedWith(OVERRIDE_ANNOTATION) .parameters(defParameters) .throws_(method.getCheckedExceptions()); methBody.appendAll(getAroundInvokeStatements(method)); methBody.appendAll(getBeforeStatements(method)); final List<Parameter> parms = defParameters.getParameters(); final Statement[] statementVars = new Statement[parms.size()]; for (int i = 0; i < parms.size(); i++) { statementVars[i] = loadVariable(parms.get(i).getName()); } if (!method.isPublic()) { PrivateAccessUtil.addPrivateAccessStubs(privateAccessorType, builder, method, new Modifier[0]); final Statement[] privateAccessStmts = new Statement[statementVars.length + 1]; privateAccessStmts[0] = Refs.get(proxyVar); System.arraycopy(statementVars, 0, privateAccessStmts, 1, statementVars.length); if (method.getReturnType().isVoid()) { methBody._(loadVariable("this").invoke(PrivateAccessUtil.getPrivateMethodName(method), privateAccessStmts)); } else { methBody._(loadVariable("this").invoke(PrivateAccessUtil.getPrivateMethodName(method), privateAccessStmts).returnValue()); } } else { if (method.getReturnType().isVoid()) { methBody._(loadVariable(proxyVar).invoke(method, statementVars)); } else { methBody._(loadVariable(proxyVar).invoke(method, statementVars).returnValue()); } } methBody.appendAll(getAfterStatements(method)); methBody.appendAll(getAroundInvokeStatements(method)); methBody.finish(); } if (renderEqualsAndHash) { // implement hashCode() builder.publicMethod(int.class, "hashCode") .annotatedWith(OVERRIDE_ANNOTATION) .body() ._( If.isNull(loadVariable(proxyVar)) ._(throw_(IllegalStateException.class, "call to hashCode() on an unclosed proxy.")) .finish() .else_() ._(Stmt.loadVariable(proxyVar).invoke("hashCode").returnValue()) .finish() ) .finish(); // implements equals() builder.publicMethod(boolean.class, "equals", Parameter.of(Object.class, "o")) .annotatedWith(OVERRIDE_ANNOTATION) .body() ._( If.isNull(loadVariable(proxyVar)) ._(throw_(IllegalStateException.class, "call to equals() on an unclosed proxy.")) .finish() .else_() ._(Stmt.loadVariable(proxyVar).invoke("equals", Refs.get("o")).returnValue()) .finish() ) .finish(); } builder.publicMethod(void.class, PROXY_BIND_METHOD).parameters(DefParameters.of(Parameter.of(toProxy, "proxy"))) ._(loadVariable(proxyVar).assignValue(loadVariable("proxy"))).finish(); return builder.getClassDefinition(); }
/** * Implementation for the same-named public methods. * * @param o The object to snapshot. * @param typeToSnapshot The type to read the snapshot attributes from. Must be a superclass of o * or an interface implemented by o, and methods not supplied by {@code methodBodyCallback} * must meed the requirements laid out in the class-level SnapshotMaker documentation. * @param typeToExtend The type of the snapshot to produce. Must be a subclass or subinterface of * typeToSnapshot, and the additional methods present in typeToExtend vs. typeToSnapshot must * be provided by the MethodMaker callback, since they can't be generated from o. * @param typesToRecurseOn Types for which this method should be called recursively. * @param methodBodyCallback A callback that can provide method bodies, preventing the standard * snapshot behaviour for those methods. This callback is optional; null is acceptable as "no * callback." * @param existingSnapshots Object instances for which a snapshot has already been completed. * Bootstrap this with an empty IdentityHashMap. * @param unfinishedSnapshots Object instances for which a partially-completed snapshot exists. If * one of these objects is returned by a method in {@code o}, this causes a * CyclicalObjectGraphException. * @return A Statement of type {@code typeToExtend} that represents the current publicly visible * state of {@code o}. */ private static Statement makeSnapshotAsSubclass( final Object o, final MetaClass typeToSnapshot, final MetaClass typeToExtend, final MethodBodyCallback methodBodyCallback, final Set<MetaClass> typesToRecurseOn, final IdentityHashMap<Object, Statement> existingSnapshots, final Set<Object> unfinishedSnapshots) { if (o == null) { return NullLiteral.INSTANCE; } if (!typeToSnapshot.isAssignableFrom(o.getClass())) { throw new IllegalArgumentException( "Given object (of type " + o.getClass().getName() + ") is not an instance of requested type to snapshot " + typeToSnapshot.getName()); } if (logger.isDebugEnabled()) { logger.debug("** Making snapshot of " + o); logger.debug(" Existing snapshots: " + existingSnapshots); } final List<MetaMethod> sortedMethods = Arrays.asList(typeToExtend.getMethods()); Collections.sort( sortedMethods, new Comparator<MetaMethod>() { @Override public int compare(MetaMethod m1, MetaMethod m2) { return m1.getName().compareTo(m2.getName()); } }); logger.debug(" Creating a new statement"); return new Statement() { String generatedCache; /** * We retain a mapping of return values to the methods that returned them, in case we need to * provide diagnostic information when an exception is thrown. */ IdentityHashMap<Object, MetaMethod> methodReturnVals = new IdentityHashMap<Object, MetaMethod>(); @Override public String generate(Context context) { if (logger.isDebugEnabled()) { logger.debug("++ Statement.generate() for " + o); } if (generatedCache != null) return generatedCache; // create a subcontext and record the types we will allow the LiteralFactory to create // automatic // snapshots for. final Context subContext = Context.create(context); subContext.addLiteralizableMetaClasses(typesToRecurseOn); final AnonymousClassStructureBuilder builder = ObjectBuilder.newInstanceOf(typeToExtend.getErased(), context).extend(); unfinishedSnapshots.add(o); for (MetaMethod method : sortedMethods) { if (method.isFinal() || method.getName().equals("toString")) continue; if (logger.isDebugEnabled()) { logger.debug(" method " + method.getName()); logger.debug(" return type " + method.getReturnType()); } if (methodBodyCallback != null) { Statement providedMethod = methodBodyCallback.generateMethodBody(method, o, builder); if (providedMethod != null) { logger.debug(" body provided by callback"); builder .publicOverridesMethod(method.getName(), Parameter.of(method.getParameters())) .append(providedMethod) .finish(); continue; } } if (method.getName().equals("equals") || method.getName().equals("hashCode") || method.getName().equals("clone") || method.getName().equals("finalize")) { // we skip these if not provided by the callback if (logger.isDebugEnabled()) { logger.debug(" skipping special-case method " + method.getName()); } continue; } if (method.getParameters().length > 0) { throw new GenerationException( "Method " + method + " in " + typeToSnapshot + " takes parameters. Such methods must be handled by the MethodBodyCallback," + " because they cannot be snapshotted."); } if (method.getReturnType().equals(void.class)) { builder.publicOverridesMethod(method.getName()).finish(); if (logger.isDebugEnabled()) { logger.debug(" finished method " + method.getName()); } continue; } try { final Object retval = typeToExtend.asClass().getMethod(method.getName()).invoke(o); methodReturnVals.put(retval, method); if (logger.isDebugEnabled()) { logger.debug(" retval=" + retval); } Statement methodBody; if (existingSnapshots.containsKey(retval)) { logger.debug(" using existing snapshot"); methodBody = existingSnapshots.get(retval); } else if (subContext.isLiteralizableClass(method.getReturnType().getErased())) { if (unfinishedSnapshots.contains(retval)) { throw new CyclicalObjectGraphException(unfinishedSnapshots); } // use Stmt.create(context) to pass the context along. if (logger.isDebugEnabled()) { logger.debug(" >> recursing for " + retval); } methodBody = Stmt.create(subContext) .nestedCall( makeSnapshotAsSubclass( retval, method.getReturnType(), method.getReturnType(), methodBodyCallback, typesToRecurseOn, existingSnapshots, unfinishedSnapshots)) .returnValue(); } else { logger.debug(" relying on literal factory"); methodBody = Stmt.load(retval).returnValue(); } if (logger.isDebugEnabled()) { logger.debug(" finished method " + method.getName()); } builder.publicOverridesMethod(method.getName()).append(methodBody).finish(); existingSnapshots.put(retval, methodBody); } catch (GenerationException e) { e.appendFailureInfo( "In attempt to snapshot return value of " + typeToExtend.getFullyQualifiedName() + "." + method.getName() + "()"); throw e; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new GenerationException("Failed to extract value for snapshot", e); } } if (logger.isDebugEnabled()) { logger.debug(" finished: " + builder); } try { generatedCache = prettyPrintJava(builder.finish().toJavaString()); } catch (NotLiteralizableException e) { MetaMethod m = methodReturnVals.get(e.getNonLiteralizableObject()); if (m != null) { e.appendFailureInfo( "This value came from method " + m.getDeclaringClass().getFullyQualifiedNameWithTypeParms() + "." + m.getName() + ", which has return type " + m.getReturnType()); } throw e; } catch (GenerationException e) { e.appendFailureInfo( "While generating a snapshot of " + o.toString() + " (actual type: " + o.getClass().getName() + "; type to extend: " + typeToExtend.getFullyQualifiedName() + ")"); throw e; } unfinishedSnapshots.remove(o); return generatedCache; } @Override public MetaClass getType() { return typeToExtend; } }; }