Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create new instance from class name in gwt

Tags:

gwt

I have a class int the following name com.test.TestClass

At one point in my code I have to get instance of this class by only having the class name string. I have tried using GWT.create() But it is working only in dev mode. Can any one tell me how to get instance in gwt from class name.

like image 373
DonX Avatar asked Jun 14 '10 03:06

DonX


People also ask

Can you create an instance of a class?

When you create an object, you are creating an instance of a class, therefore "instantiating" a class. The new operator requires a single, postfix argument: a call to a constructor. The name of the constructor provides the name of the class to instantiate. The constructor initializes the new object.

What does GWT create do?

GWT. create is used by the GWT compiler for deferred binding. Deferred binding is a feature of the GWT compiler that works by generating many versions of code at compile time, only one of which needs to be loaded by a particular client during bootstrapping at runtime. You should only use the GWT.


2 Answers

Since reflection is not possible on the client side, the only solution you have to mimic reflection is using deferred binding.

Use deferred binding to discover all classes you wish to instantiate with the class name during compile time. You may use a marker interface on all such classes to aid TypeOracle to identify these. You dynamically generate a factory class, which takes in the simple name of the class and returns a newly instantiated object of that class. The approach is very straight forward and you will find a good explanation of deferred binding in google's tutorials to boot.

Edit:- Some skeletal code to get you started. (Stripped down version of my production code, check for compiler errors in the generated file! and debug the flow)

First> Add the following blurb into your *.gwt.xml, to so that the compiler invokes our com.package.ReflectionGenerator, which will generate a simple factory class to mimic reflection on the client side.

  <generate-with class="com.package.ReflectionGenerator">
      <when-type-assignable class="com.package.client.Reflection" />
  </generate-with>

Next> Define an interface for our factory class

public interface Reflection {
    public <T, V extends T> T instantiate( Class<V> clazz );
}

Last> Implement ReflectionGenerator

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

public class ReflectionGenerator extends Generator
{    
    @Override
    public String generate( TreeLogger logger, GeneratorContext context, String typeName ) throws UnableToCompleteException
    {
        TypeOracle oracle = context.getTypeOracle( );

        JClassType instantiableType = oracle.findType( MarkerInterface.class.getName( ) );

        List<JClassType> clazzes = new ArrayList<JClassType>( );

        PropertyOracle propertyOracle = context.getPropertyOracle( );

        for ( JClassType classType : oracle.getTypes( ) )
        {
            if ( !classType.equals( instantiableType ) && classType.isAssignableTo( instantiableType ) )
                clazzes.add( classType );
        }

        final String genPackageName = "com.package.client";
        final String genClassName = "ReflectionImpl";

        ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory( genPackageName, genClassName );
        composer.addImplementedInterface( Reflection.class.getCanonicalName( ) );

        composer.addImport( "com.package.client.*" );

        PrintWriter printWriter = context.tryCreate( logger, genPackageName, genClassName );

        if ( printWriter != null )
        {
            SourceWriter sourceWriter = composer.createSourceWriter( context, printWriter );
            sourceWriter.println( "ReflectionImpl( ) {" );
            sourceWriter.println( "}" );

            printFactoryMethod( clazzes, sourceWriter );

            sourceWriter.commit( logger );
        }
        return composer.getCreatedClassName( );
    }

    private void printFactoryMethod( List<JClassType> clazzes, SourceWriter sourceWriter )
    {
        sourceWriter.println( );

        sourceWriter.println( "public <T, V extends T> T instantiate( Class<V> clazz ) {" );

        for ( JClassType classType : clazzes )
        {
            if ( classType.isAbstract( ) )
                continue;

            sourceWriter.println( );
            sourceWriter.indent( );
            sourceWriter.println( "if (clazz.getName().endsWith(\"." + classType.getName( ) + "\")) {" );
            sourceWriter.indent( );
            sourceWriter.println( "return (T) new " + classType.getQualifiedSourceName( ) + "( );" );
            sourceWriter.outdent( );
            sourceWriter.println( "}" );
            sourceWriter.outdent( );
            sourceWriter.println( );
        }
        sourceWriter.indent();
        sourceWriter.println("return (T) null;");
        sourceWriter.outdent();
        sourceWriter.println();
        sourceWriter.println("}");
        sourceWriter.outdent( );
        sourceWriter.println( );
    }
}

This should generate the factory class ReflectionGenerator in your workspace, check the generated file and tweak the source writer code to generate the code you desire.

Usage GWT.create( Reflection.class ).instantiate( YourClass.class );

I have used a marker interface 'MarkerInterface' in the generator to restrict the number of classes supported by the factory, hence as a result all the participating classes must implement 'MarkerInterface'

like image 74
Ashwin Prabhu Avatar answered Sep 23 '22 04:09

Ashwin Prabhu


Here is well tested, commented and slightly refactored version of Ashwin Prabhu's code:

https://bitbucket.org/espinosa/z025-gwt-maven-alternative-setup/src/d35a3fb7e627b5598fb763f480e3f76932cf4232/src/main/java/my/code/z025/util/ClassFromStringFactoryGenerator.java?at=master

Example of usage:

String targetEntryPointClass = "my.code.client.Sample3";
ClassFromStringFactory classFromStringFactory = GWT.create(ClassFromStringFactory.class);
Object targetEntryPointInstance = classFromStringFactory.instantiate(targetEntryPointClass);
if (targetEntryPointInstance == null) {
      // throw some exception
}
if (targetEntryPointInstance instanceof EntryPoint) {
      ((EntryPoint) targetEntryPointInstance).onModuleLoad();
} else {
      // throw some exception
}

See full source code: https://bitbucket.org/espinosa/z025-gwt-maven-alternative-setup/src/d35a3fb7e627b5598fb763f480e3f76932cf4232/src/main/java/my/code/z025/client/Dispatcher.java?at=master

In my project, I use GWT's own EntryPoint as marker interface. This enables me to run arbitrary EntryPoint just via URL: http://localhost:8080/my.code.client.Sample3; the Dispatcher EntryPoint instantiates my.code.client.Sample3 via my ClassFromStringFactory. Only Dispatcher Entry Point is configured in GWT module descriptor and the deferred binding, everything else is dynamic.

For curious, here is what GWT (Code Server in DevMode or Compiler for production mode) generates, content of my ClassFromStringFactoryImpl:

package my.code.client.reflection;

public class ClassFromStringFactoryImpl implements ClassFromStringFactory {
  public ClassFromStringFactoryImpl( ) {}

  public Object instantiate(String className) {
    if (className == null) {
      return null
    }
    else if (className.equals("my.code.client.Sample1")) {
      return new my.code.client.Sample1( );
    }
    else if (className.equals("my.code.client.Sample2")) {
      return new my.code.client.Sample2( );
    }
    ..and so on, 3 same lines per every supported type
    return null;
  }
}

In temporary file like: C:\Users\espinosa\AppData\Local\Temp\my.code.client.reflection.ClassFromStringFactoryImpl4245548251877324156.java. Note: this file is generated only in case of failure, not on successful compilation

As you can see, it is no real introspection. Deferred binding does not do any special magic. Similar Java code can be generated by a Velocity template as a part of Maven build or special tools like XText, APT-Jelly. Using GWT's Generator is just a convenience.

It is important to limit number of "supported" classes, otherwise the generated ClassFromStringFactoryImpl would be too huge, impractically huge or even exceeding limits on Java class. Some sort of filtering is necessary, marking interface is just one option, others are marking annotation (see GWT's JClassType#getAnnotation(Class)) or only selected packages. In any case, ensure that number of supported classes by this "reflection" does not exceeds magnitude of hundreds.

Many thanks Ashwin Prabhu for pointing me to the right direction.

like image 20
Espinosa Avatar answered Sep 20 '22 04:09

Espinosa