| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.dubbo.common.bytecode; |
| |
| import org.apache.dubbo.common.utils.ClassUtils; |
| import org.apache.dubbo.common.utils.ConcurrentHashMapUtils; |
| import org.apache.dubbo.common.utils.ReflectUtils; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.regex.Matcher; |
| import java.util.stream.Collectors; |
| import javassist.ClassPool; |
| import javassist.CtMethod; |
| |
| /** |
| * Wrapper. |
| */ |
| public abstract class Wrapper { |
| // class wrapper map |
| private static final ConcurrentMap<Class<?>, Wrapper> WRAPPER_MAP = new ConcurrentHashMap<>(); |
| private static final String[] EMPTY_STRING_ARRAY = new String[0]; |
| private static final String[] OBJECT_METHODS = new String[] {"getClass", "hashCode", "toString", "equals"}; |
| private static final Wrapper OBJECT_WRAPPER = new Wrapper() { |
| @Override |
| public String[] getMethodNames() { |
| return OBJECT_METHODS; |
| } |
| |
| @Override |
| public String[] getDeclaredMethodNames() { |
| return OBJECT_METHODS; |
| } |
| |
| @Override |
| public String[] getPropertyNames() { |
| return EMPTY_STRING_ARRAY; |
| } |
| |
| @Override |
| public Class<?> getPropertyType(String pn) { |
| return null; |
| } |
| |
| @Override |
| public Object getPropertyValue(Object instance, String pn) throws NoSuchPropertyException { |
| throw new NoSuchPropertyException("Property [" + pn + "] not found."); |
| } |
| |
| @Override |
| public void setPropertyValue(Object instance, String pn, Object pv) throws NoSuchPropertyException { |
| throw new NoSuchPropertyException("Property [" + pn + "] not found."); |
| } |
| |
| @Override |
| public boolean hasProperty(String name) { |
| return false; |
| } |
| |
| @Override |
| public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) |
| throws NoSuchMethodException { |
| if ("getClass".equals(mn)) { |
| return instance.getClass(); |
| } |
| if ("hashCode".equals(mn)) { |
| return instance.hashCode(); |
| } |
| if ("toString".equals(mn)) { |
| return instance.toString(); |
| } |
| if ("equals".equals(mn)) { |
| if (args.length == 1) { |
| return instance.equals(args[0]); |
| } |
| throw new IllegalArgumentException("Invoke method [" + mn + "] argument number error."); |
| } |
| throw new NoSuchMethodException("Method [" + mn + "] not found."); |
| } |
| }; |
| private static AtomicLong WRAPPER_CLASS_COUNTER = new AtomicLong(0); |
| |
| /** |
| * get wrapper. |
| * |
| * @param c Class instance. |
| * @return Wrapper instance(not null). |
| */ |
| public static Wrapper getWrapper(Class<?> c) { |
| while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class. |
| { |
| c = c.getSuperclass(); |
| } |
| |
| if (c == Object.class) { |
| return OBJECT_WRAPPER; |
| } |
| |
| return ConcurrentHashMapUtils.computeIfAbsent(WRAPPER_MAP, c, Wrapper::makeWrapper); |
| } |
| |
| private static Wrapper makeWrapper(Class<?> c) { |
| if (c.isPrimitive()) { |
| throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c); |
| } |
| |
| String name = c.getName(); |
| ClassLoader cl = ClassUtils.getClassLoader(c); |
| |
| StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ "); |
| StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ "); |
| StringBuilder c3 = |
| new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " |
| + InvocationTargetException.class.getName() + "{ "); |
| |
| c1.append(name) |
| .append(" w; try{ w = ((") |
| .append(name) |
| .append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); |
| c2.append(name) |
| .append(" w; try{ w = ((") |
| .append(name) |
| .append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); |
| c3.append(name) |
| .append(" w; try{ w = ((") |
| .append(name) |
| .append(")$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }"); |
| |
| Map<String, Class<?>> pts = new HashMap<>(); // <property name, property types> |
| Map<String, Method> ms = new LinkedHashMap<>(); // <method desc, Method instance> |
| List<String> mns = new ArrayList<>(); // method names. |
| List<String> dmns = new ArrayList<>(); // declaring method names. |
| |
| // get all public field. |
| for (Field f : c.getFields()) { |
| String fn = f.getName(); |
| Class<?> ft = f.getType(); |
| if (Modifier.isStatic(f.getModifiers()) |
| || Modifier.isTransient(f.getModifiers()) |
| || Modifier.isFinal(f.getModifiers())) { |
| continue; |
| } |
| |
| c1.append(" if( $2.equals(\"") |
| .append(fn) |
| .append("\") ){ ((") |
| .append(f.getDeclaringClass().getName()) |
| .append(")w).") |
| .append(fn) |
| .append('=') |
| .append(arg(ft, "$3")) |
| .append("; return; }"); |
| c2.append(" if( $2.equals(\"") |
| .append(fn) |
| .append("\") ){ return ($w)((") |
| .append(f.getDeclaringClass().getName()) |
| .append(")w).") |
| .append(fn) |
| .append("; }"); |
| pts.put(fn, ft); |
| } |
| |
| final ClassPool classPool = ClassGenerator.getClassPool(cl); |
| |
| List<String> allMethod = new ArrayList<>(); |
| try { |
| final CtMethod[] ctMethods = classPool.get(c.getName()).getMethods(); |
| for (CtMethod method : ctMethods) { |
| allMethod.add(ReflectUtils.getDesc(method)); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| |
| Method[] methods = Arrays.stream(c.getMethods()) |
| .filter(method -> allMethod.contains(ReflectUtils.getDesc(method))) |
| .collect(Collectors.toList()) |
| .toArray(new Method[] {}); |
| // get all public method. |
| boolean hasMethod = ClassUtils.hasMethods(methods); |
| if (hasMethod) { |
| Map<String, Integer> sameNameMethodCount = new HashMap<>((int) (methods.length / 0.75f) + 1); |
| for (Method m : methods) { |
| sameNameMethodCount.compute(m.getName(), (key, oldValue) -> oldValue == null ? 1 : oldValue + 1); |
| } |
| |
| c3.append(" try{"); |
| for (Method m : methods) { |
| // ignore Object's method. |
| if (m.getDeclaringClass() == Object.class) { |
| continue; |
| } |
| |
| String mn = m.getName(); |
| c3.append(" if( \"").append(mn).append("\".equals( $2 ) "); |
| int len = m.getParameterTypes().length; |
| c3.append(" && ").append(" $3.length == ").append(len); |
| |
| boolean overload = sameNameMethodCount.get(m.getName()) > 1; |
| if (overload) { |
| if (len > 0) { |
| for (int l = 0; l < len; l++) { |
| c3.append(" && ") |
| .append(" $3[") |
| .append(l) |
| .append("].getName().equals(\"") |
| .append(m.getParameterTypes()[l].getName()) |
| .append("\")"); |
| } |
| } |
| } |
| |
| c3.append(" ) { "); |
| |
| if (m.getReturnType() == Void.TYPE) { |
| c3.append(" w.") |
| .append(mn) |
| .append('(') |
| .append(args(m.getParameterTypes(), "$4")) |
| .append(");") |
| .append(" return null;"); |
| } else { |
| c3.append(" return ($w)w.") |
| .append(mn) |
| .append('(') |
| .append(args(m.getParameterTypes(), "$4")) |
| .append(");"); |
| } |
| |
| c3.append(" }"); |
| |
| mns.add(mn); |
| if (m.getDeclaringClass() == c) { |
| dmns.add(mn); |
| } |
| ms.put(ReflectUtils.getDesc(m), m); |
| } |
| c3.append(" } catch(Throwable e) { "); |
| c3.append(" throw new java.lang.reflect.InvocationTargetException(e); "); |
| c3.append(" }"); |
| } |
| |
| c3.append(" throw new ") |
| .append(NoSuchMethodException.class.getName()) |
| .append("(\"Not found method \\\"\"+$2+\"\\\" in class ") |
| .append(c.getName()) |
| .append(".\"); }"); |
| |
| // deal with get/set method. |
| Matcher matcher; |
| for (Map.Entry<String, Method> entry : ms.entrySet()) { |
| String md = entry.getKey(); |
| Method method = entry.getValue(); |
| if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { |
| String pn = propertyName(matcher.group(1)); |
| c2.append(" if( $2.equals(\"") |
| .append(pn) |
| .append("\") ){ return ($w)w.") |
| .append(method.getName()) |
| .append("(); }"); |
| pts.put(pn, method.getReturnType()); |
| } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) { |
| String pn = propertyName(matcher.group(1)); |
| c2.append(" if( $2.equals(\"") |
| .append(pn) |
| .append("\") ){ return ($w)w.") |
| .append(method.getName()) |
| .append("(); }"); |
| pts.put(pn, method.getReturnType()); |
| } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { |
| Class<?> pt = method.getParameterTypes()[0]; |
| String pn = propertyName(matcher.group(1)); |
| c1.append(" if( $2.equals(\"") |
| .append(pn) |
| .append("\") ){ w.") |
| .append(method.getName()) |
| .append('(') |
| .append(arg(pt, "$3")) |
| .append("); return; }"); |
| pts.put(pn, pt); |
| } |
| } |
| c1.append(" throw new ") |
| .append(NoSuchPropertyException.class.getName()) |
| .append("(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class ") |
| .append(c.getName()) |
| .append(".\"); }"); |
| c2.append(" throw new ") |
| .append(NoSuchPropertyException.class.getName()) |
| .append("(\"Not found property \\\"\"+$2+\"\\\" field or getter method in class ") |
| .append(c.getName()) |
| .append(".\"); }"); |
| |
| // make class |
| long id = WRAPPER_CLASS_COUNTER.getAndIncrement(); |
| ClassGenerator cc = ClassGenerator.newInstance(cl); |
| cc.setClassName(c.getName() + "DubboWrap" + id); |
| cc.setSuperClass(Wrapper.class); |
| |
| cc.addDefaultConstructor(); |
| cc.addField("public static String[] pns;"); // property name array. |
| cc.addField("public static " + Map.class.getName() + " pts;"); // property type map. |
| cc.addField("public static String[] mns;"); // all method name array. |
| cc.addField("public static String[] dmns;"); // declared method name array. |
| for (int i = 0, len = ms.size(); i < len; i++) { |
| cc.addField("public static Class[] mts" + i + ";"); |
| } |
| |
| cc.addMethod("public String[] getPropertyNames(){ return pns; }"); |
| cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }"); |
| cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }"); |
| cc.addMethod("public String[] getMethodNames(){ return mns; }"); |
| cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }"); |
| cc.addMethod(c1.toString()); |
| cc.addMethod(c2.toString()); |
| cc.addMethod(c3.toString()); |
| |
| try { |
| Class<?> wc = cc.toClass(c); |
| // setup static field. |
| wc.getField("pts").set(null, pts); |
| wc.getField("pns").set(null, pts.keySet().toArray(new String[0])); |
| wc.getField("mns").set(null, mns.toArray(new String[0])); |
| wc.getField("dmns").set(null, dmns.toArray(new String[0])); |
| int ix = 0; |
| for (Method m : ms.values()) { |
| wc.getField("mts" + ix++).set(null, m.getParameterTypes()); |
| } |
| return (Wrapper) wc.getDeclaredConstructor().newInstance(); |
| } catch (RuntimeException e) { |
| throw e; |
| } catch (Throwable e) { |
| throw new RuntimeException(e.getMessage(), e); |
| } finally { |
| cc.release(); |
| pts.clear(); |
| ms.clear(); |
| mns.clear(); |
| dmns.clear(); |
| } |
| } |
| |
| private static String arg(Class<?> cl, String name) { |
| if (cl.isPrimitive()) { |
| if (cl == Boolean.TYPE) { |
| return "((Boolean)" + name + ").booleanValue()"; |
| } |
| if (cl == Byte.TYPE) { |
| return "((Byte)" + name + ").byteValue()"; |
| } |
| if (cl == Character.TYPE) { |
| return "((Character)" + name + ").charValue()"; |
| } |
| if (cl == Double.TYPE) { |
| return "((Number)" + name + ").doubleValue()"; |
| } |
| if (cl == Float.TYPE) { |
| return "((Number)" + name + ").floatValue()"; |
| } |
| if (cl == Integer.TYPE) { |
| return "((Number)" + name + ").intValue()"; |
| } |
| if (cl == Long.TYPE) { |
| return "((Number)" + name + ").longValue()"; |
| } |
| if (cl == Short.TYPE) { |
| return "((Number)" + name + ").shortValue()"; |
| } |
| throw new RuntimeException("Unknown primitive type: " + cl.getName()); |
| } |
| return "(" + ReflectUtils.getName(cl) + ")" + name; |
| } |
| |
| private static String args(Class<?>[] cs, String name) { |
| int len = cs.length; |
| if (len == 0) { |
| return ""; |
| } |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < len; i++) { |
| if (i > 0) { |
| sb.append(','); |
| } |
| sb.append(arg(cs[i], name + "[" + i + "]")); |
| } |
| return sb.toString(); |
| } |
| |
| private static String propertyName(String pn) { |
| return pn.length() == 1 || Character.isLowerCase(pn.charAt(1)) |
| ? Character.toLowerCase(pn.charAt(0)) + pn.substring(1) |
| : pn; |
| } |
| |
| /** |
| * get property name array. |
| * |
| * @return property name array. |
| */ |
| public abstract String[] getPropertyNames(); |
| |
| /** |
| * get property type. |
| * |
| * @param pn property name. |
| * @return Property type or nul. |
| */ |
| public abstract Class<?> getPropertyType(String pn); |
| |
| /** |
| * has property. |
| * |
| * @param name property name. |
| * @return has or has not. |
| */ |
| public abstract boolean hasProperty(String name); |
| |
| /** |
| * get property value. |
| * |
| * @param instance instance. |
| * @param pn property name. |
| * @return value. |
| */ |
| public abstract Object getPropertyValue(Object instance, String pn) |
| throws NoSuchPropertyException, IllegalArgumentException; |
| |
| /** |
| * set property value. |
| * |
| * @param instance instance. |
| * @param pn property name. |
| * @param pv property value. |
| */ |
| public abstract void setPropertyValue(Object instance, String pn, Object pv) |
| throws NoSuchPropertyException, IllegalArgumentException; |
| |
| /** |
| * get property value. |
| * |
| * @param instance instance. |
| * @param pns property name array. |
| * @return value array. |
| */ |
| public Object[] getPropertyValues(Object instance, String[] pns) |
| throws NoSuchPropertyException, IllegalArgumentException { |
| Object[] ret = new Object[pns.length]; |
| for (int i = 0; i < ret.length; i++) { |
| ret[i] = getPropertyValue(instance, pns[i]); |
| } |
| return ret; |
| } |
| |
| /** |
| * set property value. |
| * |
| * @param instance instance. |
| * @param pns property name array. |
| * @param pvs property value array. |
| */ |
| public void setPropertyValues(Object instance, String[] pns, Object[] pvs) |
| throws NoSuchPropertyException, IllegalArgumentException { |
| if (pns.length != pvs.length) { |
| throw new IllegalArgumentException("pns.length != pvs.length"); |
| } |
| |
| for (int i = 0; i < pns.length; i++) { |
| setPropertyValue(instance, pns[i], pvs[i]); |
| } |
| } |
| |
| /** |
| * get method name array. |
| * |
| * @return method name array. |
| */ |
| public abstract String[] getMethodNames(); |
| |
| /** |
| * get method name array. |
| * |
| * @return method name array. |
| */ |
| public abstract String[] getDeclaredMethodNames(); |
| |
| /** |
| * has method. |
| * |
| * @param name method name. |
| * @return has or has not. |
| */ |
| public boolean hasMethod(String name) { |
| for (String mn : getMethodNames()) { |
| if (mn.equals(name)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * invoke method. |
| * |
| * @param instance instance. |
| * @param mn method name. |
| * @param types |
| * @param args argument array. |
| * @return return value. |
| */ |
| public abstract Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) |
| throws NoSuchMethodException, InvocationTargetException; |
| } |