/* | |
* 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.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.HashSet; | |
import java.util.Set; | |
import java.util.concurrent.atomic.AtomicLong; | |
import com.alibaba.dubbo.common.utils.ClassHelper; | |
import com.alibaba.dubbo.common.utils.ReflectUtils; | |
/** | |
* Mixin | |
* | |
* @author qian.lei | |
*/ | |
public abstract class Mixin | |
{ | |
private static AtomicLong MIXIN_CLASS_COUNTER = new AtomicLong(0); | |
private static final String PACKAGE_NAME = Mixin.class.getPackage().getName(); | |
public static interface MixinAware{ void setMixinInstance(Object instance); } | |
/** | |
* mixin interface and delegates. | |
* all class must be public. | |
* | |
* @param ics interface class array. | |
* @param dc delegate class. | |
* @return Mixin instance. | |
*/ | |
public static Mixin mixin(Class<?>[] ics, Class<?> dc) | |
{ | |
return mixin(ics, new Class[]{dc}); | |
} | |
/** | |
* mixin interface and delegates. | |
* all class must be public. | |
* | |
* @param ics interface class array. | |
* @param dc delegate class. | |
* @param cl class loader. | |
* @return Mixin instance. | |
*/ | |
public static Mixin mixin(Class<?>[] ics, Class<?> dc, ClassLoader cl) | |
{ | |
return mixin(ics, new Class[]{dc}, cl); | |
} | |
/** | |
* mixin interface and delegates. | |
* all class must be public. | |
* | |
* @param ics interface class array. | |
* @param dcs delegate class array. | |
* @return Mixin instance. | |
*/ | |
public static Mixin mixin(Class<?>[] ics, Class<?>[] dcs) | |
{ | |
return mixin(ics, dcs, ClassHelper.getClassLoader(ics[0])); | |
} | |
/** | |
* mixin interface and delegates. | |
* all class must be public. | |
* | |
* @param ics interface class array. | |
* @param dcs delegate class array. | |
* @param cl class loader. | |
* @return Mixin instance. | |
*/ | |
public static Mixin mixin(Class<?>[] ics, Class<?>[] dcs, ClassLoader cl) | |
{ | |
assertInterfaceArray(ics); | |
long id = MIXIN_CLASS_COUNTER.getAndIncrement(); | |
String pkg = null; | |
ClassGenerator ccp = null, ccm = null; | |
try | |
{ | |
ccp = ClassGenerator.newInstance(cl); | |
// impl constructor | |
StringBuilder code = new StringBuilder(); | |
for(int i=0;i<dcs.length;i++) | |
{ | |
if( !Modifier.isPublic(dcs[i].getModifiers()) ) | |
{ | |
String npkg = dcs[i].getPackage().getName(); | |
if( pkg == null ) | |
{ | |
pkg = npkg; | |
} | |
else | |
{ | |
if( !pkg.equals(npkg) ) | |
throw new IllegalArgumentException("non-public interfaces class from different packages"); | |
} | |
} | |
ccp.addField("private " + dcs[i].getName() + " d" + i + ";"); | |
code.append("d").append(i).append(" = (").append(dcs[i].getName()).append(")$1[").append(i).append("];\n"); | |
if( MixinAware.class.isAssignableFrom(dcs[i]) ) | |
code.append("d").append(i).append(".setMixinInstance(this);\n"); | |
} | |
ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{ Object[].class }, code.toString()); | |
// impl methods. | |
Set<String> worked = new HashSet<String>(); | |
for(int i=0;i<ics.length;i++) | |
{ | |
if( !Modifier.isPublic(ics[i].getModifiers()) ) | |
{ | |
String npkg = ics[i].getPackage().getName(); | |
if( pkg == null ) | |
{ | |
pkg = npkg; | |
} | |
else | |
{ | |
if( !pkg.equals(npkg) ) | |
throw new IllegalArgumentException("non-public delegate class from different packages"); | |
} | |
} | |
ccp.addInterface(ics[i]); | |
for( Method method : ics[i].getMethods() ) | |
{ | |
if( "java.lang.Object".equals(method.getDeclaringClass().getName()) ) | |
continue; | |
String desc = ReflectUtils.getDesc(method); | |
if( worked.contains(desc) ) | |
continue; | |
worked.add(desc); | |
int ix = findMethod(dcs, desc); | |
if( ix < 0 ) | |
throw new RuntimeException("Missing method [" + desc + "] implement."); | |
Class<?> rt = method.getReturnType(); | |
String mn = method.getName(); | |
if( Void.TYPE.equals(rt) ) | |
ccp.addMethod(mn, method.getModifiers(), rt, method.getParameterTypes(), method.getExceptionTypes(), | |
"d" + ix + "." + mn + "($$);"); | |
else | |
ccp.addMethod(mn, method.getModifiers(), rt, method.getParameterTypes(), method.getExceptionTypes(), | |
"return ($r)d" + ix + "." + mn + "($$);"); | |
} | |
} | |
if( pkg == null ) | |
pkg = PACKAGE_NAME; | |
// create MixinInstance class. | |
String micn = pkg + ".mixin" + id; | |
ccp.setClassName(micn); | |
ccp.toClass(); | |
// create Mixin class. | |
String fcn = Mixin.class.getName() + id; | |
ccm = ClassGenerator.newInstance(cl); | |
ccm.setClassName(fcn); | |
ccm.addDefaultConstructor(); | |
ccm.setSuperClass(Mixin.class.getName()); | |
ccm.addMethod("public Object newInstance(Object[] delegates){ return new " + micn + "($1); }"); | |
Class<?> mixin = ccm.toClass(); | |
return (Mixin)mixin.newInstance(); | |
} | |
catch(RuntimeException e) | |
{ | |
throw e; | |
} | |
catch(Exception e) | |
{ | |
throw new RuntimeException(e.getMessage(), e); | |
} | |
finally | |
{ | |
// release ClassGenerator | |
if( ccp != null ) | |
ccp.release(); | |
if( ccm != null ) | |
ccm.release(); | |
} | |
} | |
/** | |
* new Mixin instance. | |
* | |
* @param ds delegates instance. | |
* @return instance. | |
*/ | |
abstract public Object newInstance(Object[] ds); | |
protected Mixin(){} | |
private static int findMethod(Class<?>[] dcs, String desc) | |
{ | |
Class<?> cl; | |
Method[] methods; | |
for(int i=0;i<dcs.length;i++) | |
{ | |
cl = dcs[i]; | |
methods = cl.getMethods(); | |
for( Method method : methods ) | |
{ | |
if( desc.equals(ReflectUtils.getDesc(method)) ) | |
return i; | |
} | |
} | |
return -1; | |
} | |
private static void assertInterfaceArray(Class<?>[] ics) | |
{ | |
for(int i=0;i<ics.length;i++) | |
if( !ics[i].isInterface() ) | |
throw new RuntimeException("Class " + ics[i].getName() + " is not a interface."); | |
} | |
} |