/* * Copyright (C) 2011 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.errai.codegen.util; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.jboss.errai.codegen.Context; import org.jboss.errai.codegen.DefModifiers; import org.jboss.errai.codegen.Modifier; import org.jboss.errai.codegen.Statement; import org.jboss.errai.codegen.Variable; import org.jboss.errai.codegen.VariableReference; import org.jboss.errai.codegen.builder.impl.Scope; import org.jboss.errai.codegen.exception.GenerationException; import org.jboss.errai.codegen.exception.InvalidTypeException; import org.jboss.errai.codegen.exception.OutOfScopeException; import org.jboss.errai.codegen.exception.TypeNotIterableException; import org.jboss.errai.codegen.exception.UndefinedMethodException; import org.jboss.errai.codegen.literal.LiteralFactory; import org.jboss.errai.codegen.literal.LiteralValue; import org.jboss.errai.codegen.meta.MetaClass; import org.jboss.errai.codegen.meta.MetaClassFactory; import org.jboss.errai.codegen.meta.MetaClassMember; import org.jboss.errai.codegen.meta.MetaConstructor; import org.jboss.errai.codegen.meta.MetaField; import org.jboss.errai.codegen.meta.MetaMethod; import org.jboss.errai.codegen.meta.MetaParameter; import org.jboss.errai.codegen.meta.impl.java.JavaReflectionClass; import org.mvel2.DataConversion; import org.mvel2.util.NullType; /** * @author Mike Brock <cbrock@redhat.com> * @author Christian Sadilek <csadilek@redhat.com> */ public class GenUtil { private static final String PERMISSIVE_PROPERTY = "errai.codegen.permissive"; private static boolean PERMISSIVE_MODE; static { PERMISSIVE_MODE = System.getProperty(PERMISSIVE_PROPERTY) != null && Boolean.getBoolean(PERMISSIVE_PROPERTY); } public static boolean isPermissiveMode() { return PERMISSIVE_MODE; } public static void setPermissiveMode(final boolean permissiveMode) { PERMISSIVE_MODE = permissiveMode; } public static Statement[] generateCallParameters(final Context context, final Object... parameters) { return Arrays.stream(parameters).map(p -> generate(context, p)).toArray(s -> new Statement[s]); } public static Statement[] generateCallParameters(final MetaMethod method, final Context context, final Object... parameters) { if (parameters.length != method.getParameters().length && !method.isVarArgs()) { throw new UndefinedMethodException("Wrong number of parameters"); } final MetaParameter[] methParms = method.getParameters(); final Statement[] statements = new Statement[parameters.length]; int i = 0; for (Object parameter : parameters) { if (parameter instanceof Statement) { if (((Statement) parameter).getType() == null) { parameter = generate(context, parameter); } } try { statements[i] = convert(context, parameter, methParms[i++].getType()); } catch (final GenerationException t) { t.appendFailureInfo("in method call: " + method.getDeclaringClass().getFullyQualifiedName() + "." + method.getName() + "(" + Arrays.toString(methParms) + ")"); throw t; } } return statements; } public static Statement generate(final Context context, final Object o) { if (o instanceof VariableReference) { return context.getVariable(((VariableReference) o).getName()); } else if (o instanceof Variable) { final Variable v = (Variable) o; if (context.isScoped(v)) { return v.getReference(); } else { if (context.isPermissiveMode()) { return v.getReference(); } else { throw new OutOfScopeException("variable cannot be referenced from this scope: " + v.getName()); } } } else if (o instanceof Statement) { ((Statement) o).generate(context); return (Statement) o; } else { return LiteralFactory.getLiteral(context, o); } } public static void assertIsIterable(final Statement statement) { final Class<?> cls = statement.getType().asClass(); if (!cls.isArray() && !Iterable.class.isAssignableFrom(cls)) throw new TypeNotIterableException(statement.generate(Context.create())); } private static final Set<String> classAliases = new HashSet<String>() { { add(JavaReflectionClass.class.getName()); add(Class.class.getName()); } }; public static void addClassAlias(final Class cls) { classAliases.add(cls.getName()); } 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 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 (final NumberFormatException nfe) { throw new InvalidTypeException(nfe); } } public static String classesAsStrings(final MetaClass... stmt) { final StringBuilder buf = new StringBuilder(128); for (int i = 0; i < stmt.length; i++) { buf.append(stmt[i].getFullyQualifiedName()); if (i + 1 < stmt.length) { buf.append(", "); } } return buf.toString(); } public static MetaClass[] fromParameters(final MetaParameter... parms) { return Arrays.stream(parms).map(p -> p.getType()).toArray(s -> new MetaClass[s]); } public static MetaClass[] classToMeta(final Class<?>[] types) { return Arrays.stream(types).map(t -> MetaClassFactory.get(t)).toArray(s -> new MetaClass[s]); } 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; } } public static Scope scopeOf(final MetaClassMember member) { if (member.isPublic()) { return Scope.Public; } else if (member.isPrivate()) { return Scope.Private; } else if (member.isProtected()) { return Scope.Protected; } else { return Scope.Package; } } public static DefModifiers modifiersOf(final MetaClassMember member) { final DefModifiers defModifiers = new DefModifiers(); if (member.isAbstract()) { defModifiers.addModifiers(Modifier.Abstract); } else if (member.isFinal()) { defModifiers.addModifiers(Modifier.Final); } else if (member.isStatic()) { defModifiers.addModifiers(Modifier.Static); } else if (member.isSynchronized()) { defModifiers.addModifiers(Modifier.Synchronized); } else if (member.isVolatile()) { defModifiers.addModifiers(Modifier.Volatile); } else if (member.isTransient()) { defModifiers.addModifiers(Modifier.Transient); } return defModifiers; } public static boolean equals(final MetaField a, final MetaField b) { return a.getName().equals(b.getName()) && !a.getType().equals(b.getType()) && !a.getDeclaringClass().equals(b.getDeclaringClass()); } public static boolean equals(final MetaConstructor a, final MetaConstructor b) { if (a.getParameters().length != b.getParameters().length) { return false; } for (int i = 0; i < a.getParameters().length; i++) { if (!equals(a.getParameters()[i], b.getParameters()[i])) { return false; } } return a.getDeclaringClass().equals(b.getDeclaringClass()); } public static boolean equals(final MetaMethod a, final MetaMethod b) { if (!a.getName().equals(b.getName())) return false; if (a.getParameters().length != b.getParameters().length) return false; if (!a.getDeclaringClass().equals(b.getDeclaringClass())) return false; for (int i = 0; i < a.getParameters().length; i++) { if (!equals(a.getParameters()[i], b.getParameters()[i])) return false; } return true; } public static boolean equals(final MetaParameter a, final MetaParameter b) { return a.getType().isAssignableFrom(b.getType()) || b.getType().isAssignableFrom(a.getType()); } public static String getMethodString(final MetaMethod method) { return method.getName() + "(" + Arrays.toString(method.getParameters()) + ")"; } 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 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 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 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 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 void throwIfUnhandled(final String error, final Throwable t) { try { throw t; } catch (final RuntimeException e) { throw e; } catch (final Throwable e) { throw new RuntimeException("generation failure at: " + error, e); } } 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 int scoreMethods(final MetaClass[] arguments, final MetaParameter[] parmTypes, final boolean isVarArgs) { int score = 0; for (int i = 0; i != arguments.length; i++) { final MetaClass actualParamType; if (isVarArgs && !arguments[arguments.length - 1].isArray() && i >= parmTypes.length - 1) actualParamType = parmTypes[parmTypes.length - 1].getType().getComponentType(); else actualParamType = parmTypes[i].getType(); if (arguments[i] == null) { if (!actualParamType.isPrimitive()) { score += 6; } else { score = 0; break; } } else if (actualParamType.equals(arguments[i])) { score += 7; } else if (actualParamType.isPrimitive() && actualParamType.asBoxed().equals(arguments[i])) { score += 6; } else if (arguments[i].isPrimitive() && arguments[i].asUnboxed().equals(actualParamType)) { score += 6; } else if (actualParamType.isAssignableFrom(arguments[i])) { score += 5; } else if (isNumericallyCoercible(arguments[i], actualParamType)) { score += 4; } else if (actualParamType.asBoxed().isAssignableFrom(arguments[i].asBoxed()) && !Object_MetaClass.equals(arguments[i])) { score += 3 + scoreInterface(actualParamType, arguments[i]); } else if (canConvert(actualParamType, arguments[i])) { if (actualParamType.isArray() && arguments[i].isArray()) { final MetaClass outerComponentTypeActual = actualParamType.getOuterComponentType(); final MetaClass outerComponentTypeArg = arguments[i].getOuterComponentType(); if (canConvert(outerComponentTypeActual, outerComponentTypeArg)) { score += 1; } else { continue; } } else if (actualParamType.equals(char_MetaClass) && arguments[i].equals(String_MetaClass)) { score += 1; } score += 1; } else if (actualParamType.equals(Object_MetaClass) || arguments[i].equals(NullType_MetaClass)) { score += 1; } else { score = 0; break; } } return score; } public static MetaConstructor getBestConstructorCandidate(final MetaClass[] arguments, final MetaClass decl, MetaConstructor[] constructors, final boolean classTarget) { if (constructors == null || constructors.length == 0) { return null; } MetaParameter[] parmTypes; MetaConstructor bestCandidate = null; int bestScore = 0; int score; boolean retry = false; do { for (final MetaConstructor meth : constructors) { if (classTarget && (meth.isStatic())) continue; 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 MetaConstructor[] objMethods = Object_MetaClass.getConstructors(); final MetaConstructor[] nMethods = new MetaConstructor[constructors.length + objMethods.length]; System.arraycopy(constructors, 0, nMethods, 0, constructors.length); System.arraycopy(objMethods, 0, nMethods, constructors.length, objMethods.length); constructors = nMethods; retry = true; } else { break; } } while (true); return bestCandidate; } private static final MetaClass Number_MetaClass = MetaClassFactory.get(Number.class); private static final MetaClass Object_MetaClass = MetaClassFactory.get(Object.class); private static final MetaClass NullType_MetaClass = MetaClassFactory.get(NullType.class); private static final MetaClass char_MetaClass = MetaClassFactory.get(char.class); private static final MetaClass String_MetaClass = MetaClassFactory.get(String.class); 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 (final Throwable t) { return false; } } 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 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; } public static void rewriteBlameStackTrace(final Throwable innerBlame) { final StackTraceElement[] stackTrace = innerBlame.getStackTrace(); final List<StackTraceElement> innerStackTrace = new ArrayList<StackTraceElement>(10); final List<StackTraceElement> outerStackTrace = new ArrayList<StackTraceElement>(10); for (final StackTraceElement el : stackTrace) { if (el.getClassName().startsWith("org.jboss.errai.codegen.")) { innerStackTrace.add(el); } else { outerStackTrace.add(el); } } innerBlame.setStackTrace(innerStackTrace.toArray(new StackTraceElement[innerStackTrace.size()])); final RuntimeException outerBlame = new RuntimeException("External call to API"); outerBlame.setStackTrace(outerStackTrace.toArray(new StackTraceElement[outerStackTrace.size()])); innerBlame.initCause(outerBlame); } }