/* | |
* Copyright 1999-2011 Alibaba Group. | |
* | |
* 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 com.alibaba.dubbo.common.bytecode; | |
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.HashMap; | |
import java.util.LinkedHashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.atomic.AtomicLong; | |
import java.util.regex.Matcher; | |
import com.alibaba.dubbo.common.utils.ClassHelper; | |
import com.alibaba.dubbo.common.utils.ReflectUtils; | |
/** | |
* Wrapper. | |
* | |
* @author qian.lei | |
*/ | |
public abstract class Wrapper | |
{ | |
private static AtomicLong WRAPPER_CLASS_COUNTER = new AtomicLong(0); | |
private static final Map<Class<?>, Wrapper> WRAPPER_MAP = new ConcurrentHashMap<Class<?>, Wrapper>(); //class wrapper map | |
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(){ | |
public String[] getMethodNames(){ return OBJECT_METHODS; } | |
public String[] getDeclaredMethodNames(){ return OBJECT_METHODS; } | |
public String[] getPropertyNames(){ return EMPTY_STRING_ARRAY; } | |
public Class<?> getPropertyType(String pn){ return null; } | |
public Object getPropertyValue(Object instance, String pn) throws NoSuchPropertyException{ throw new NoSuchPropertyException("Property [" + pn + "] not found."); } | |
public void setPropertyValue(Object instance, String pn, Object pv) throws NoSuchPropertyException{ throw new NoSuchPropertyException("Property [" + pn + "] not found."); } | |
public boolean hasProperty(String name){ return false; } | |
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."); | |
} | |
}; | |
/** | |
* 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; | |
Wrapper ret = WRAPPER_MAP.get(c); | |
if( ret == null ) | |
{ | |
ret = makeWrapper(c); | |
WRAPPER_MAP.put(c,ret); | |
} | |
return ret; | |
} | |
/** | |
* get property name array. | |
* | |
* @return property name array. | |
*/ | |
abstract public String[] getPropertyNames(); | |
/** | |
* get property type. | |
* | |
* @param pn property name. | |
* @return Property type or nul. | |
*/ | |
abstract public Class<?> getPropertyType(String pn); | |
/** | |
* has property. | |
* | |
* @param name property name. | |
* @return has or has not. | |
*/ | |
abstract public boolean hasProperty(String name); | |
/** | |
* get property value. | |
* | |
* @param instance instance. | |
* @param pn property name. | |
* @return value. | |
*/ | |
abstract public Object getPropertyValue(Object instance, String pn) throws NoSuchPropertyException, IllegalArgumentException; | |
/** | |
* set property value. | |
* | |
* @param instance instance. | |
* @param pn property name. | |
* @param pv property value. | |
*/ | |
abstract public 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 pn property name array. | |
* @param pv 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. | |
*/ | |
abstract public String[] getMethodNames(); | |
/** | |
* get method name array. | |
* | |
* @return method name array. | |
*/ | |
abstract public 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. | |
*/ | |
abstract public Object invokeMethod(Object instance, String mn, Class<?>[] types, Object[] args) throws NoSuchMethodException, InvocationTargetException; | |
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 = ClassHelper.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); } try{"); | |
Map<String, Class<?>> pts = new HashMap<String, Class<?>>(); // <property name, property types> | |
Map<String, Method> ms = new LinkedHashMap<String, Method>(); // <method desc, Method instance> | |
List<String> mns = new ArrayList<String>(); // method names. | |
List<String> dmns = new ArrayList<String>(); // 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()) ) | |
continue; | |
c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }"); | |
c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }"); | |
pts.put(fn, ft); | |
} | |
Method[] methods = c.getMethods(); | |
// get all public method. | |
for( Method m : methods ) | |
{ | |
if( m.getDeclaringClass() == Object.class ) //ignore Object's method. | |
continue; | |
String mn = m.getName(); | |
c3.append(" if( \"").append(mn).append("\".equals( $2 ) "); | |
boolean override = false; | |
for( Method m2 : methods ) { | |
if (m != m2 && m.getName().equals(m2.getName())) { | |
override = true; | |
break; | |
} | |
} | |
if (override) { | |
int len = m.getParameterTypes().length; | |
c3.append(" && ").append(" $3.length == ").append(len); | |
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 " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }"); | |
// deal with get/set method. | |
Matcher matcher; | |
for( Map.Entry<String,Method> entry : ms.entrySet() ) | |
{ | |
String md = entry.getKey(); | |
Method 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 " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }"); | |
c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" filed or setter method in class " + c.getName() + ".\"); }"); | |
// make class | |
long id = WRAPPER_CLASS_COUNTER.getAndIncrement(); | |
ClassGenerator cc = ClassGenerator.newInstance(cl); | |
cc.setClassName( ( Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw" ) + 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(); | |
// 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.newInstance(); | |
} | |
catch(RuntimeException e) | |
{ | |
throw e; | |
} | |
catch(Throwable e) | |
{ | |
throw new RuntimeException(e.getMessage(), e); | |
} | |
finally | |
{ | |
cc.release(); | |
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; | |
} | |
} |