Montag, 7. Mai 2012

Verwenden eines Generators zum generischen Klassen laden mit GWT und GAE


Ein Beispiel wie man die Klasse com.google.gwt.core.ext.Generator verwenden kann, um auf Client-Seite von GWT und GAE Java-Reflection zu verwenden kann.

English version
Szenario
Ich habe ein kleine Szenario erstellt:

Ich habe eine Controller, welche die Navigation der Web-Application behandelt.

Etwa so:
@Override
public void onValueChange(ValueChangeEvent<String> event) {
    String token = event.getValue();
    IPresenter presenter = null;
    if (token != null) {
         if (token.equals("ShowEntry")) {
             presenter = new ShowEntryPresenter(new EntryView());
         } else if (token.equals("EditEntry")) {
             presenter = new EditEntryPresenter(new EditView());
         }  

         if (presenter != null) {
             presenter.display();            
         }    
    }
}

Weil unsere Web-Applikation von einem grösseren Entwicklungsteam erweitert wird, brauche ich eine lösung, damit ich nicht immer diesen Controller anpassen muss, wenn ich einen neuen Presenter publizieren will.

Am liebsten würde ich Java-Reflection verwenden, etwa so:

@Override
public void onValueChange(ValueChangeEvent<String> event) {
    String token = event.getValue();
    IPresenter presenter = null;
    if (token != null) {
        if (token.equals("ShowEntry")) {
            presenter = new ShowEntryPresenter(new EntryView());
        } else if (token.equals("EditEntry")) {
            presenter = new EditEntryPresenter(new EditView());
        } else {    
            presenter = Class.forName(token);
        }
        ....
}

Das Problem: Class.forName() funktioniert beim GWT-Client nicht.
Deswegen müssen wir einen Generator schreiben, welcher uns diese Operation ermöglicht.

} else {    
    PresenterFactory factory = (PresenterFactory) GWT.create(IReflectiveFactory.class);
    presenter = (IPresenter) factory.newInstance(
        "com.samuelschmid.dynamicpresenters.client." + token);
}

Solution
Folgendes muss gemacht werden:
1. Wir brauchen ein Markierungs-Interface
2. Wir brauchen ein Wrapper-Interface
3. Wir brauchen ein Generator-Interface
4. Wir brauchen ein Fabrik-Interface
5. Wir brauchen eine Generator Klasse
6. Wir müssen den Generator im gwt.xml anwenden.
7. Wir müssen die Applikation ohne Runtime Class Casting kompilieren

1. IDynamicPresenter.java
Ein Interface um die dynamisch ladbaren Klassen zu markieren.

package com.samuelschmid.dynamicpresenters.client

public interface IDynamicPresenter extends IPresenter {
    
}

2. Wrapper interface IFactoryWrapper.java

package com.samuelschmid.dynamicpresenters.client

public interface IFactoryWrapper {
    
}

3. Generator interface IReflectiveFactory.java


package com.samuelschmid.dynamicpresenters.client

public interface IReflectiveFactory implements IFactoryWrapper{

}

4. Factory interface IPresenterFactory.java

package com.samuelschmid.dynamicpresenters.client

public interface IPresenterFactory {
 IDynamicPresenter newInstance(String className);
}

5. Generator class PresenterGenerator.java

package com.samuelschmid.dynamicpresenters.server;

import java.io.PrintWriter;
import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
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.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

public class PresenterGenerator extends Generator {

    public String generate(TreeLogger logger, GeneratorContext context, String typeName) 
        throws UnableToCompleteException {

      
        TypeOracle typeOracle = context.getTypeOracle();
        JClassType clazz = typeOracle.findType(typeName);

        if (clazz == null) {
            throw new UnableToCompleteException();
        }

        try {
            
            JClassType reflectableType = typeOracle.getType(
               "com.samuelschmid.dynamicpresenters.client.IDynamicPresenter");

            SourceWriter sourceWriter = getSourceWriter(clazz, context, logger);

            if (sourceWriter != null) {
                sourceWriter.println("public "+
                     reflectableType.getQualifiedSourceName()+ 
                     " newInstance(String className) {");

                JClassType[] types = typeOracle.getTypes();

                int count = 0;
                for (int i = 0; i < types.length; i++) {
                    if (types[i].isInterface() == null   
                        && types[i].isAssignableTo(reflectableType)) {

                        if (count == 0) {
                            sourceWriter.println("   if(\""
                            + types[i].getQualifiedSourceName()
                            + "\".equals(className)) {"
                            + " return new "
                            + types[i].getQualifiedSourceName() + "();"
                            + "}");

                        } else {

                            sourceWriter.println("   else if(\""
                            + types[i].getQualifiedSourceName()
                            + "\".equals(className)) {"
                            + " return new "
                            + types[i].getQualifiedSourceName() + "();"
                            + "}");

                        }

                        count++;

                    }

                }

                sourceWriter.println("return null;");
                sourceWriter.println("}");
                sourceWriter.commit(logger);
                logger.log(TreeLogger.INFO, "Done Generating source for "
                + clazz.getName(), null);

                return clazz.getQualifiedSourceName() + "Wrapper";

            }

        } catch (NotFoundException e) {

            e.printStackTrace();

        }

        return null;

    }

    public SourceWriter getSourceWriter(JClassType classType, 
        GeneratorContext context, TreeLogger logger) {

        String packageName = classType.getPackage().getName();
        String simpleName = classType.getSimpleSourceName() + "Wrapper";
        ClassSourceFileComposerFactory composer =
           new ClassSourceFileComposerFactory(packageName, simpleName);

        composer.addImplementedInterface(
           "com.samuelschmid.dynamicpresenters.client.IPresenterFactory");
        
        PrintWriter printWriter = context.tryCreate(logger, packageName,simpleName);

        if (printWriter == null) {
            return null;
        } else {
            SourceWriter sw = composer.createSourceWriter(context, printWriter);
            return sw;
        }

    }

}


6. *.gwt.xml
Definier den Generator im module.gwt.xml

...
<generate-with 
  class="com.samuelschmid.dynamicpresenters.server.PresenterGenerator" >
<when-type-assignable 
  class="com.samuelschmid.dynamicpresenters.client.IFactoryWrapper" />
</generate-with>
...

7. Kein Runtime ClassCasting
Zu guter letzt muss noch das Runtime Class Casting deaktiviert werden.
Dazu muss -XdisableCastChecking als Compiler-Argument verwendet werden.

Fazit
Ich habe die Lösung anhand folgendes Blog-Eintrags umgesetzt.
 http://programmatica.blogspot.com/2007/10/classfornamefullyqualifiedclassnamenewi.html

Der Fakt, dass dieser Eintrag schon 5 Jahre alt ist, hat mich dazu bewogen eine Neuauflage zu schrieben, welche auch mit allen gängigen Browsern funktioniert.

Keine Kommentare:

Kommentar veröffentlichen