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
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 extends Place>[] 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 extends Place>[] 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 extends Place>[] 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 extends Place> 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 extends Place> 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 extends Place> 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 extends Place> 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 extends Place> 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 extends Place> 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 {
}