package gw.internal.gosu.ir.compiler.bytecode.expression;
import gw.internal.gosu.parser.GosuClassProxyFactory;
import gw.internal.gosu.parser.TypeLord;
import gw.lang.parser.ISource;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IPropertyInfo;
import gw.lang.reflect.IRelativeTypeInfo;
import gw.lang.reflect.IType;
import gw.lang.reflect.ITypeInfo;
import gw.lang.reflect.TypeInfoUtil;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.ClassType;
import gw.lang.reflect.gs.GosuClassTypeLoader;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuEnhancement;
import gw.lang.reflect.gs.StringSourceFileHandle;
import gw.lang.reflect.java.JavaTypes;
import gw.lang.reflect.module.IModule;
import java.util.concurrent.Callable;
/**
*/
public class StructuralTypeProxyGenerator {
public static Class makeProxy( String iface, Class<?> rootClass, final String name ) {
final IType type = TypeLord.getPureGenericType( TypeSystem.get( rootClass ) );
final IType ifaceType = TypeLord.getPureGenericType( TypeSystem.getByFullName( iface ) );
final IModule module = ifaceType.getTypeLoader().getModule();
GosuClassTypeLoader loader = GosuClassTypeLoader.getDefaultClassLoader( module );
IGosuClass gsProxy = loader.makeNewClass(
new LazyStringSourceFileHandle( getNamespace( ifaceType ), name, new Callable<StringBuilder>() {
public StringBuilder call() {
TypeSystem.pushModule( module );
try {
return generateProxy( ifaceType, type, name );
}
finally {
TypeSystem.popModule( module );
}
}
} ) );
return gsProxy.getBackingClass();
}
private static class LazyStringSourceFileHandle extends StringSourceFileHandle {
private Callable<StringBuilder> _sourceGen;
private String _namespace;
public LazyStringSourceFileHandle( String nspace, String fqn, Callable<StringBuilder> sourceGen ) {
super( fqn, null, false, ClassType.Class );
_namespace = nspace;
_sourceGen = sourceGen;
}
public String getTypeNamespace() {
return _namespace;
}
@Override
public ISource getSource() {
if( getRawSource() == null ) {
try {
setRawSource( _sourceGen.call().toString() );
}
catch( Exception e ) {
throw new RuntimeException( e );
}
}
return super.getSource();
}
}
private static StringBuilder generateProxy( IType ifaceType, IType type, String name ) {
return new StringBuilder()
.append( "package " ).append( getNamespace( ifaceType ) ).append( "\n" )
.append( "\n" )
.append( "class " ).append( name ).append( " implements " ).append( ifaceType.getName() ).append( " {\n" )
.append( " var _root: " ).append( type.getName() ).append( "\n" )
.append( " \n" )
.append( " construct( root: " ).append( type.getName() ).append( " ) {\n" )
.append( " _root = root\n" )
.append( " }\n" )
.append( " \n" )
.append( implementIface( ifaceType, type ) )
.append( "}" );
}
private static String getNamespace( IType ifaceType ) {
String nspace = TypeLord.getOuterMostEnclosingClass( ifaceType ).getNamespace();
if( nspace.startsWith( "java." ) || nspace.startsWith( "javax." ) )
{
nspace = "not" + nspace;
}
return nspace;
}
private static String implementIface( IType ifaceType, IType rootType ) {
StringBuilder sb = new StringBuilder();
ITypeInfo ti = ifaceType.getTypeInfo();
// Interface properties
for( Object o : ti.getProperties() ) {
IPropertyInfo pi = (IPropertyInfo)o;
genInterfacePropertyDecl( sb, pi, rootType );
}
// Interface methods
for( Object o : ti.getMethods() ) {
IMethodInfo mi = (IMethodInfo)o;
genInterfaceMethodDecl( sb, mi, rootType );
}
return sb.toString();
}
// Note we need to include type variables for generic methods so that the bytecode signatures, which include type vars as params, will be compatible.
// All other type variable references, including the method's type vars, are erased to their bounding types.
// e.g.,
// structure FooBar<T extends CharSequence> {
// function foo<E>( e: E ) : T
// }
// class MyClass<T extends CharSequence> { // structurally implemetns FooBar<T>
// function foo<E>( e: E ) : T {
// return x
// }
// }
//
// We generate the followin proxy for MyClass, notice we preserve the E type var for the method:
//
// class MyClass_structuralproxy_Foobar implements FooBar {
// function foo<E>( e: Object ) : CharSequence {
// return _root.foo( e )
// }
// }
private static void genInterfaceMethodDecl( StringBuilder sb, IMethodInfo mi, IType rootType ) {
if( mi.getOwnersType() instanceof IGosuEnhancement ) {
return;
}
if( mi.getDisplayName().startsWith( "@" ) ) { // property
return;
}
if( GosuClassProxyFactory.isObjectMethod( mi ) ) {
return;
}
if( mi.getOwnersType() == JavaTypes.IGOSU_OBJECT().getAdapterClass() ) {
return;
}
sb.append( " function " ).append( mi.getDisplayName() ).append( TypeInfoUtil.getTypeVarList( mi ) ).append( "(" );
IParameterInfo[] params = GosuClassProxyFactory.getGenericParameters( mi );
for( int i = 0; i < params.length; i++ ) {
IParameterInfo pi = params[i];
sb.append( ' ' ).append( "p" ).append( i ).append( ": " ).append( TypeLord.replaceTypeVariableTypeParametersWithBoundingTypes( pi.getFeatureType() ).getName() );
sb.append( i < params.length - 1 ? ',' : ' ' );
}
IType returnType = TypeLord.replaceTypeVariableTypeParametersWithBoundingTypes( mi.getReturnType() );
sb.append( ") : " ).append( returnType.getName() ).append( " {\n" )
.append( returnType == JavaTypes.pVOID()
? " "
: " return " )
//## todo: maybe we need to explicitly parameterize if the method is generic for some cases?
.append( "_root." ).append( mi.getDisplayName() ).append( "(" );
for( int i = 0; i < params.length; i++ ) {
IParameterInfo pi = params[i];
sb.append( ' ' ).append( "p" ).append( i ).append( maybeCastParamType( mi, TypeLord.replaceTypeVariableTypeParametersWithBoundingTypes( pi.getFeatureType() ), rootType, i ) )
.append( i < params.length - 1 ? ',' : ' ' );
}
sb.append( ")" ).append( maybeCastReturnType( mi, returnType, rootType ) )
.append( " }\n" );
}
private static String maybeCastReturnType( IMethodInfo mi, IType returnType, IType rootType ) {
//## todo:
return returnType != JavaTypes.pVOID()
? " as " + returnType.getName()
: "";
}
private static String maybeCastParamType( IMethodInfo ifaceMethod, IType paramType, IType rootType, int iParam ) {
//## todo: find the parameter type in the corresponding method in rootType, and then cast to that type *only* if necessary
return "";
}
private static String maybeCastPropertyAssignment( IPropertyInfo pi, IType rootType ) {
//## todo: find the corresponding property type in rootType, and then cast to that type *only* if necessary
return " as " + TypeLord.replaceTypeVariableTypeParametersWithBoundingTypes( pi.getFeatureType() ).getName() + "\n";
}
private static void genInterfacePropertyDecl( StringBuilder sb, IPropertyInfo pi, IType rootType ) {
if( pi.isStatic() ) {
return;
}
if( !pi.isReadable() ) {
return;
}
if( pi.getOwnersType() instanceof IGosuEnhancement ) {
return;
}
if( pi.getOwnersType() == JavaTypes.IGOSU_OBJECT().getAdapterClass() ) {
return;
}
IType ifacePropertyType = TypeLord.getPureGenericType( TypeLord.getDefaultParameterizedType( pi.getFeatureType() ) );
if( pi.getDescription() != null ) {
sb.append( "\n/** " ).append( pi.getDescription() ).append( " */\n" );
}
ITypeInfo rootTypeInfo = rootType.getTypeInfo();
// Have to handle private for inner class case e.g., a privagte field on the inner class implements a property on a structure
boolean bPrivate = rootTypeInfo instanceof IRelativeTypeInfo && ((IRelativeTypeInfo) rootTypeInfo).getProperty( rootType, pi.getName() ).isPrivate();
sb.append( " property get " ).append( pi.getName() ).append( "() : " ).append( ifacePropertyType.getName() ).append( " {\n" );
if( bPrivate ) {
sb.append( " return _root[\"" ).append( pi.getName() ).append( "\"] as " ).append( ifacePropertyType.getName() ).append( "\n" );
}
else {
sb.append( " return _root." ).append( pi.getName() ).append( " as " ).append( ifacePropertyType.getName() ).append( "\n" );
}
sb.append( " }\n" );
if( pi.isWritable( pi.getOwnersType() ) ) {
sb.append( " property set " ).append( pi.getName() ).append( "( value: " ).append( ifacePropertyType.getName() ).append( " ) {\n" );
if( bPrivate ) {
sb.append( " _root[\"" ).append( pi.getName() ).append( "\"] = value" ).append( maybeCastPropertyAssignment( pi, rootType ) );
}
else {
sb.append( " _root." ).append( pi.getName() ).append( " = value" ).append( maybeCastPropertyAssignment( pi, rootType ) );
}
sb.append( " }\n" );
}
}
}