package com.meituan.robust.autopatch; import com.meituan.robust.Constants; import com.meituan.robust.utils.JavaUtils; import javassist.CtClass; import javassist.CtMethod; import javassist.NotFoundException; import javassist.bytecode.AccessFlag; import static com.meituan.robust.autopatch.Config.classPool; /** * Created by mivanzhang on 17/2/9. * * create patch control classes,which dispatch patch methods */ public class PatchesControlFactory { private static PatchesControlFactory patchesControlFactory = new PatchesControlFactory(); private PatchesControlFactory() { } private CtClass createControlClass(CtClass modifiedClass) throws Exception { CtClass patchClass = classPool.get(NameManger.getInstance().getPatchName(modifiedClass.getName())); patchClass.defrost(); CtClass controlClass = classPool.getAndRename(Constants.PATCH_TEMPLATE_FULL_NAME, NameManger.getInstance().getPatchControlName(modifiedClass.getSimpleName())); StringBuilder getRealParameterMethodBody = new StringBuilder(); getRealParameterMethodBody.append("public Object getRealParameter(Object parameter) {"); getRealParameterMethodBody.append("if(parameter instanceof " + modifiedClass.getName() + "){"); getRealParameterMethodBody. append("return new " + patchClass.getName() + "(parameter);"); getRealParameterMethodBody.append("}"); getRealParameterMethodBody.append("return parameter;}"); controlClass.addMethod(CtMethod.make(getRealParameterMethodBody.toString(), controlClass)); controlClass.getDeclaredMethod("accessDispatch").insertBefore(getAccessDispatchMethodBody(patchClass, modifiedClass.getName())); controlClass.getDeclaredMethod("isSupport").insertBefore(getIsSupportMethodBody(patchClass, modifiedClass.getName())); controlClass.defrost(); return controlClass; } private static String getAccessDispatchMethodBody(CtClass patchClass, String modifiedClassName) throws NotFoundException { StringBuilder accessDispatchMethodBody = new StringBuilder(); if(Config.catchReflectException){ accessDispatchMethodBody.append("try{"); } if (Constants.isLogging) { accessDispatchMethodBody.append(" android.util.Log.d(\"robust\",\"arrivied in AccessDispatch \"+methodName+\" paramArrayOfObject \" +paramArrayOfObject);"); } //create patch instance accessDispatchMethodBody.append(patchClass.getName() + " patch= null;\n"); accessDispatchMethodBody.append(" String isStatic=$1.split(\":\")[2];"); accessDispatchMethodBody.append(" if (isStatic.equals(\"false\")) {\n"); accessDispatchMethodBody.append(" if (keyToValueRelation.get(paramArrayOfObject[paramArrayOfObject.length - 1]) == null) {\n"); if (Constants.isLogging) { accessDispatchMethodBody.append(" android.util.Log.d(\"robust\",\"keyToValueRelation not contain\" );"); } accessDispatchMethodBody.append("patch=new " + patchClass.getName() + "(paramArrayOfObject[paramArrayOfObject.length - 1]);\n"); accessDispatchMethodBody.append(" keyToValueRelation.put(paramArrayOfObject[paramArrayOfObject.length - 1], patch);\n"); accessDispatchMethodBody.append("}else{"); accessDispatchMethodBody.append("patch=(" + patchClass.getName() + ") keyToValueRelation.get(paramArrayOfObject[paramArrayOfObject.length - 1]);\n"); accessDispatchMethodBody.append("}"); accessDispatchMethodBody.append("}\n"); accessDispatchMethodBody.append("else{"); if (Constants.isLogging) { accessDispatchMethodBody.append(" android.util.Log.d(\"robust\",\"static method forward \" );"); } accessDispatchMethodBody.append("patch=new " + patchClass.getName() + "(null);\n"); accessDispatchMethodBody.append("}"); accessDispatchMethodBody.append("String methodNo=$1.split(\":\")[3];\n"); if (Constants.isLogging) { accessDispatchMethodBody.append(" android.util.Log.d(\"robust\",\"assemble method number is \" + methodNo);"); } // patchClass.declaredMethods.each { for (CtMethod method : patchClass.getDeclaredMethods()) { CtClass[] parametertypes = method.getParameterTypes(); String methodSignure = JavaUtils.getJavaMethodSignure(method).replaceAll(patchClass.getName(), modifiedClassName); String methodLongName = modifiedClassName + "." + methodSignure; Integer methodNumber = Config.methodMap.get(methodLongName); //just Forward methods with methodNumber if (methodNumber != null) { accessDispatchMethodBody.append(" if((\"" + methodNumber + "\").equals(methodNo)){\n"); if (Constants.isLogging) { accessDispatchMethodBody.append(" android.util.Log.d(\"robust\",\"invoke method is " + method.getLongName() + " \" );"); } String methodName = method.getName(); if (AccessFlag.isPrivate(method.getModifiers())) { methodName = Constants.ROBUST_PUBLIC_SUFFIX + method.getName(); } if (method.getReturnType().getName().equals("void")) { accessDispatchMethodBody.append("(patch." + methodName + "("); } else { switch (method.getReturnType().getName()) { case "boolean": accessDispatchMethodBody.append("return Boolean.valueOf(patch." + methodName + "("); break; case "byte": accessDispatchMethodBody.append("return Byte.valueOf(patch." + methodName + "("); break; case "char": accessDispatchMethodBody.append("return Character.valueOf(patch." + methodName + "("); break; case "double": accessDispatchMethodBody.append("return Double.valueOf(patch." + methodName + "("); break; case "float": accessDispatchMethodBody.append("return Float.valueOf(patch." + methodName + "("); break; case "int": accessDispatchMethodBody.append("return Integer.valueOf(patch." + methodName + "("); break; case "long": accessDispatchMethodBody.append("return Long.valueOf(patch." + methodName + "("); break; case "short": accessDispatchMethodBody.append("return Short.valueOf(patch." + methodName + "("); break; default: accessDispatchMethodBody.append("return (patch." + methodName + "("); break; } } for (int index = 0; index < parametertypes.length; index++) { accessDispatchMethodBody.append("((" + JavaUtils.getWrapperClass(parametertypes[index].getName()) + ") (paramArrayOfObject[" + index + "])"); accessDispatchMethodBody.append(")" + JavaUtils.wrapperToPrime(parametertypes[index].getName())); if (index != parametertypes.length - 1) { accessDispatchMethodBody.append(","); } } accessDispatchMethodBody.append("));}\n"); } } if(Config.catchReflectException){ accessDispatchMethodBody.append(" } catch (Throwable e) {"); accessDispatchMethodBody.append(" e.printStackTrace();}"); } return accessDispatchMethodBody.toString(); } private static String getIsSupportMethodBody(CtClass patchClass, String modifiedClassName) throws NotFoundException { StringBuilder isSupportBuilder = new StringBuilder(); StringBuilder methodsIdBuilder = new StringBuilder(); if (Constants.isLogging) { isSupportBuilder.append(" android.util.Log.d(\"robust\",\"arrivied in isSupport \"+methodName+\" paramArrayOfObject \" +paramArrayOfObject);"); } isSupportBuilder.append("String methodNo=$1.split(\":\")[3];\n"); if (Constants.isLogging) { isSupportBuilder.append(" android.util.Log.d(\"robust\",\"in isSupport assemble method number is \" + methodNo);"); } for (CtMethod method : patchClass.getDeclaredMethods()) { String methodSignure = JavaUtils.getJavaMethodSignure(method).replaceAll(patchClass.getName(), modifiedClassName); String methodLongName = modifiedClassName + "." + methodSignure; Integer methodNumber = Config.methodMap.get(methodLongName); //just Forward methods with methodNumber if (methodNumber != null) { methodsIdBuilder.append(methodNumber + ":"); } } if (Constants.isLogging) { isSupportBuilder.append(" android.util.Log.d(\"robust\",\"arrivied in isSupport \"+methodName+\" paramArrayOfObject \" +paramArrayOfObject+\" isSupport result is \"+\"" + methodsIdBuilder.toString() + "\".contains(methodNo));"); } isSupportBuilder.append("return \"" + methodsIdBuilder.toString() + "\".contains(methodNo);"); return isSupportBuilder.toString(); } public static CtClass createPatchesControl(CtClass modifiedClass) throws Exception { return patchesControlFactory.createControlClass(modifiedClass); } }