Eliminating GWT ActivityMapper boilerplate

前端 未结 6 1125
盖世英雄少女心
盖世英雄少女心 2021-02-05 16:26

I am using the GWT Activities and Places framework to structure my application and it is turning out nicely. One thing that annoys me though is that the ActivityMapper

6条回答
  •  广开言路
    2021-02-05 17:02

    Just for future reference for people like me landed here and still have no answer. I´ve ended up with the following solution using GIN and Generators for a PlaceFactory.

    This is what my tokens look like now. eg: #EditUser/id:15/type:Agent

    I have a AbstractPlace that every Place should extend from.

    public abstract class AbstractPlace extends Place {
        public abstract Activity getActivity();
    }
    

    An example Place:

    public class EditUserPlace extends AbstractPlace {
    
    private Long id;
    
    private User.Type type;
    
    //getters and setters
    
    @Override
    public Activity getActivity() {
        return App.getClientFactory().getEditUserPresenter().withPlace(this);
    }
    }
    

    PlaceFactory interface for Defered binding:

    public interface PlaceFactory {
        Place fromToken(String token);
        String toToken(Place place);
    }
    

    and a annotation for registering place classes

    public @interface WithPlaces {
        Class[] value() default {};
    }
    

    and the PlaceFactoryGenerator

    Setup the generator in you GWT Module

    
        
    
    
    public class PlaceFactoryGenerator extends Generator {
    
        private TreeLogger logger;
        private TypeOracle typeOracle;
        private JClassType interfaceType;
        private String packageName;
        private String implName;
        private Class[] placeTypes;
    
        @Override
        public String generate(TreeLogger logger,
                GeneratorContext generatorContext, String interfaceName)
                throws UnableToCompleteException {
    
            this.logger = logger;
            this.typeOracle = generatorContext.getTypeOracle();
            this.interfaceType = typeOracle.findType(interfaceName);
            this.packageName = interfaceType.getPackage().getName();
            this.implName = interfaceType.getName().replace(".", "_") + "Impl";
    
            // TODO Trocar annotation por scan
            WithPlaces places = interfaceType.getAnnotation(WithPlaces.class);
            assert (places != null);
    
            Class[] placeTypes = places.value();
    
            this.placeTypes = placeTypes;
    
            PrintWriter out = generatorContext.tryCreate(logger,
                    packageName, implName);
    
            if (out != null) {
                generateOnce(generatorContext, out);
            }
    
            return packageName + "." + implName;
        }
    
        private void generateOnce(GeneratorContext generatorContext, PrintWriter out) {
            TreeLogger logger = this.logger.branch(
                    TreeLogger.DEBUG,
                    String.format("Generating implementation of %s",
                            interfaceType));
    
            ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
                    packageName, implName);
            factory.addImport(interfaceType.getQualifiedSourceName());
            factory.addImplementedInterface(interfaceType.getSimpleSourceName());
    
            factory.addImport(StringBuilder.class.getCanonicalName());
            factory.addImport(Map.class.getCanonicalName());
            factory.addImport(HashMap.class.getCanonicalName());
            factory.addImport(Place.class.getCanonicalName());
    
            for (Class place : placeTypes)
                factory.addImport(place.getCanonicalName());
    
            SourceWriter sw = factory.createSourceWriter(generatorContext, out);
    
            sw.println("public Place fromToken(String token) {");
            sw.indent();
            sw.println("int barAt = token.indexOf('/');");
            sw.println("String placeName = token;");
            sw.println("Map params = new HashMap();");
            sw.println("if (barAt > 0) {");
            sw.indent();
            sw.println("placeName = token.substring(0, barAt);");
            sw.println("String[] keyValues = token.substring(barAt + 1).split(\"/\");");
            sw.println("for (String item : keyValues) {");
            sw.indent();
            sw.println("int colonAt = item.indexOf(':');");
            sw.println("if (colonAt > 0) {");
            sw.indent();
            sw.println("String key = item.substring(0, colonAt);");
            sw.println("String value = item.substring(colonAt + 1);");
            sw.println("params.put(key, value);");
            sw.outdent();
            sw.println("}");
            sw.outdent();
            sw.println("}");
            sw.outdent();
            sw.println("}\n");
    
            for (Class placeType : placeTypes) {
                String placeTypeName = placeType.getSimpleName();
    
                int replaceStrPos = placeTypeName.lastIndexOf("Place");
                String placeName = placeTypeName.substring(0, replaceStrPos);
    
                sw.println("if (placeName.equals(\"%s\")) {", placeName);
                sw.indent();
    
                sw.println("%s place = new %s();", placeTypeName, placeTypeName);
    
                generateSetExpressions(sw, placeType);
    
                sw.println("return place;");
    
                sw.outdent();
                sw.println("}\n");
    
            }
    
            sw.println("return null;");
            sw.outdent();
            sw.println("}\n");
    
            sw.println("public String toToken(Place place) {");
            sw.indent();
            sw.println("StringBuilder token = new StringBuilder();\n");
    
            for (Class placeType : placeTypes) {
                String placeTypeName = placeType.getSimpleName();
                int replaceStrPos = placeTypeName.lastIndexOf("Place");
                String placeName = placeTypeName.substring(0, replaceStrPos);
    
                sw.println("if (place instanceof %s) {", placeTypeName);
                sw.indent();
    
                sw.println("%s newPlace = (%s)place;", placeTypeName, placeTypeName);
                sw.println("token.append(\"%s\");", placeName);
                generateTokenExpressions(sw, placeType);
                sw.println("return token.toString();");
    
                sw.outdent();
                sw.println("}\n");
            }
    
            sw.println("return token.toString();");
            sw.outdent();
            sw.println("}\n");
    
    
            sw.outdent();
            sw.println("}");
    
            generatorContext.commit(logger, out);
        }
    
        private void generateTokenExpressions(SourceWriter sw,
                Class placeType) {
            for (Field field : placeType.getDeclaredFields()) {
                char[] fieldName = field.getName().toCharArray();
                fieldName[0] = Character.toUpperCase(fieldName[0]); 
                String getterName = "get"  + new String(fieldName);
                sw.println("token.append(\"/%s:\");", field.getName());
                sw.println("token.append(newPlace.%s().toString());", getterName);
            }
        }
    
        private void generateSetExpressions(SourceWriter sw, Class placeType) {
            for (Field field : placeType.getDeclaredFields()) {
                char[] fieldName = field.getName().toCharArray();
                fieldName[0] = Character.toUpperCase(fieldName[0]); 
                String setterName = "set"  + new String(fieldName);
    
                List methods = findMethods(placeType, setterName);
    
                for (Method method : methods) {
                    Class[] parameterTypes = method.getParameterTypes();
    
                    if (parameterTypes.length == 0 || parameterTypes.length > 1)
                        continue;
    
                    Class parameterType = parameterTypes[0];
                    String exp = "%s";
    
                    if (parameterType == Character.class) {
                        exp = "%s.charAt(0)";
                    } else if (parameterType == Boolean.class) {
                        exp = "Boolean.parseBoolean(%s)";
                    } else if (parameterType == Byte.class) {
                        exp = "Byte.parseInt(%s)";
                    } else if (parameterType == Short.class) {
                        exp = "Short.parseShort(%s)";
                    } else if (parameterType == Integer.class) {
                        exp = "Integer.parseInt(%s)";
                    } else if (parameterType == Long.class) {
                        exp = "Long.parseLong(%s)";
                    } else if (parameterType == Float.class) {
                        exp = "Float.parseFloat(%s)";
                    } else if (parameterType == Double.class) {
                        exp = "Double.parseDouble(%s)";
                    } else if (parameterType.getSuperclass().isAssignableFrom(Enum.class)) {
                        exp = parameterType.getCanonicalName() + ".valueOf(%s)";
                    } else if (parameterType != String.class){
                        continue;
                    }
    
                    String innerExp = String.format("params.get(\"%s\")", field.getName());
                    String wrapperExp = String.format(exp, innerExp);
                    sw.println("place.%s(%s);", setterName, wrapperExp);
                }
            }
        }
    
        private List findMethods(Class placeType, String name) {
            Method[] methods = placeType.getMethods();
            List found = new ArrayList();
    
            for (Method method : methods) {
                if (method.getName().equals(name)) {
                    found.add(method);
                }
            }
    
            return found;
        }
    }
    

    Whats my ActivityMapper look like?

    public class AppActivityMapper implements ActivityMapper {
    
        public Activity getActivity(Place place) {
            AbstractPlace abstractPlace = (AbstractPlace)place;
            return abstractPlace.getActivity();
        }
    }
    

    We need a custom PlaceHistoryMapper

    public class AppPlaceHistoryMapper implements PlaceHistoryMapper {
    
        private AppPlaceFactory placeFactory = GWT.create(AppPlaceFactory.class);
    
        public Place getPlace(String token) {
            return placeFactory.fromToken(token);
        }
    
        public String getToken(Place place) {
            return placeFactory.toToken(place);
        }
    }
    

    And at last the PlaceFactory, this will be generated for just put your place classes in the annotation and be happy!

    @WithPlaces(value = {
        HomePlace.class,
        EditUserPlace.class
    })
    public interface AppPlaceFactory extends PlaceFactory {
    
    }
    

提交回复
热议问题