Statary / google-gin

Automatically exported from code.google.com/p/google-gin
0 stars 0 forks source link

Easily generate little stub classes/subinterfaces for parameterized interfaces, for use with generators #51

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
I (and probably some others) commonly invoke a generator by creating an 
interface like:

{{{
interface BeanInfo<T>{ 
  Object getValue(String prop); 
  void setValue(String prop, Object value); 
}
}}}

And then I write a generator which will generate my implementation given 
code like:

{{{
interface CardBeanInfo extends BeanInfo<Card>{} 

BeanInfo getInfo() {
   return GWT.create(CardBeanInfo.class); 
}
}}}

There are many great uses for this pattern, all kinds of wrappers, proxies, 
and introspectors can be created.  However, the entire part about creating 
the interface and calling GWT.create can be a bit of a chore.  Wouldn't it 
be great if Gin would do that part for me?

With this in mind I create a patch to help do exactly that.  I included a 
single very basic test case that proves that the code works.  I appreciate 
your comments and I hope that this functionality could be included in Gin, 
either by merging in my patch or writing a new one that suits the project 
owners better.

== How to use it ==

Annotate a parameter, field, or Injector method with the 
@GeneratedImplementation annotation.  The generator will then assume that it 
can create a subclass or subinterface of that object and call GWT.create() 
on that class in order to create the object you are looking for.

There's an example of its use included in the patch in the form of a test 
case:

{{{

public interface Hello<T> {

  public String sayHello(T who);
}

public class MainImpl implements Main {
  private final Hello<String> helloString;
  private final Hello<Integer> helloInteger;

  @Inject
  public MainImpl(@GeneratedImplementation Hello<String> helloString,   
      @GeneratedImplementation Hello<Integer> helloInteger) {
    this.helloString = helloString;
    this.helloInteger = helloInteger;  
  }

  public Hello<String> getHelloString() {
    return helloString;
  }

  public Hello<Integer> getHelloInteger() {
    return helloInteger;
  }

}

/**
 * Generate an implementation of Hello.sayHello() based on the 
 * parameterized type.
 */
public class HelloGenerator extends Generator {

  public String generate(TreeLogger logger, GeneratorContext ctx,
      String requestedClass) throws UnableToCompleteException {

    final TypeOracle types = ctx.getTypeOracle();
    JClassType baseType = types
        .findType(requestedClass);

    if (baseType == null) {
      logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
          + requestedClass + "'", null);

      throw new UnableToCompleteException();
    }

    JPackage interfacePackage = baseType.getPackage();
    String packageName = interfacePackage == null ? "" : 
interfacePackage.getName();
    JClassType[] implementedInterfaces = 
baseType.getImplementedInterfaces();
    JClassType beanType=null;
    for(JClassType iface : implementedInterfaces) {
        JParameterizedType parameterizedType = 
iface.isParameterized();
        if(parameterizedType == null)
            continue;
        if(parameterizedType.getSimpleSourceName().equals("Hello")) 
{
            JClassType[] typeArgs = 
parameterizedType.getTypeArgs();
            if(typeArgs.length != 1) {
                logger.log(TreeLogger.ERROR, "Hello 
interface must be parameterized to the type of object to say hello to", 
null);
                throw new UnableToCompleteException();
            }
            beanType = typeArgs[0];
        }
    }

    if(beanType == null) {
        logger.log(TreeLogger.ERROR, "Class class must implement the 
Hello<T> interface", null);
        throw new UnableToCompleteException();
    }
    String implClassName = baseType.getSimpleSourceName() + 
"Impl_"+beanType.getSimpleSourceName();
    String generatedClassName = packageName + "." + implClassName;
    PrintWriter printWriter = ctx.tryCreate(logger, packageName, 
implClassName);
    if (printWriter == null) {
      // We've already created it, so nothing to do
    } else {
      ClassSourceFileComposerFactory composerFactory = new 
ClassSourceFileComposerFactory(
          packageName, implClassName);
      SourceWriter writer = composerFactory.createSourceWriter(ctx, 
printWriter);
      writer.println("public String 
sayHello("+beanType.getQualifiedSourceName()+" who) { return \"Hello, 
\"+who; }");
      writer.commit(logger);
    }

    return generatedClassName;

  }

In the module XML:
    <generate-with

class="com.google.gwt.inject.rebind.generatedimplementation.HelloGenerator">
     <when-type-assignable
        class="com.google.gwt.inject.client.generatedimplementation.Hello"/>
    </generate-with>

}}}

Original issue reported on code.google.com by dob...@gmail.com on 24 Jul 2009 at 11:48

Attachments:

GoogleCodeExporter commented 9 years ago
This looks interesting and we can take a look at it. Please follow the 
instructions
here for contributing to Gin: 
http://code.google.com/p/google-gin/wiki/ContributingToGin

Once you have uploaded the patch for a code review, I can take a closer look 
and will
let you know what I think.

Original comment by aragos on 30 Jul 2009 at 6:34

GoogleCodeExporter commented 9 years ago
We're discussing a generic extension mechanism that would make it possible 
based on the interface type rather than a binding annotation (i.e. as soon as 
you expect a UiBinder<A,B> the registered extension for UiBinder will generate 
the 'interface Binder extends UiBinder<A,B>' to be GWT.create()d); on the other 
hand, it would require an extension to be written and registered (though there 
could be one that's registered for the @GeneratedImplementation annotation too)
http://groups.google.com/group/google-gin/t/7dd7382be803e00b

Original comment by t.broyer on 21 Nov 2010 at 9:22