I have an class with "T extends StorageClass". You can extend this class and put another class instend of the "T" for example "GroupStorage extends Storage. In the "Storage" class is an method called "get". Now i want that method to convert json to the "T" class, in my case to the "Group" class. Maybe you will understand when you look to the code below.
public abstract class Storage<T extends StorageClass> {
// This should return whatever T is.
public T get(String groupName) {
T t = null;
File file = new File(this.groupFolderPath, groupName + ".json");
if (file.exists()) {
try {
FileReader reader = new FileReader(file);
// 'T.class' is not possible
t = Storage.GSON.fromJson(reader, T.class);
} catch (Exception e) {
e.printStackTrace();
Bukkit.getLogger().warning("Failed to read " + groupName + ".json!");
}
} else {
Bukkit.getLogger().warning("The group " + groupName + " does not exists!");
}
return t;
}
}
You would have to pass down the type of the class to your method as :
public T get(String groupName, Class<T> type)
and then use it as :
t = Storage.GSON.fromJson(reader, type);
Related
I have output functions that output data from different objects to their corresponding files. Most of these functions follow the same pattern, the only differences are the objects being used and by extension, the object's inherent methods being used.
I pass in 'obj' to all the write functions, and in each individual write function we call different 'obj.get...' functions to get different objects to grab data from to output.
My output functions are called like so:
for (Object obj : objects) {
writer.writeSubOject1(obj, dir, "subObject1.csv", true);
writer.writeSubOject2(obj, dir, subObject2.csv, true);
....
}
Code for the write functions:
public class Writer{
public void writeSubOject1(Object obj, File dir, String filename, Boolean append) {
ArrayList<SubObject1> subObject1 = obj.getSubObject1();
try {
log.info("writing " + filename + "...");
ArrayList<String[]> so1Data = SubObject1.getData(subObject1);
final File out = CreateFileObject(dir, filename);
writeCsv(so1Data, out, append);
} catch (Exception e) {
log.info("Error in writeSubOject1");
log.error(e);
e.printStackTrace();
}
}
public void writeSubOject2(Object obj, File dir, String filename, Boolean append) {
ArrayList<SubObject2> subObject2 = obj.getSubObject2();
try {
log.info("writing " + filename + "...");
ArrayList<String[]> so2Data = SubObject2.getData(subObject2);
final File out = CreateFileObject(dir, filename);
writeCsv(so2Data, out, append);
} catch (Exception e) {
log.info("Error in writeSubOject2");
log.error(e);
e.printStackTrace();
}
}
}
You can see that the only differences between the two methods is the obj.getSubObjectX() call, and the getData() method which has a unique implementation in SubObject1 and 2.
Is there a better way to do this to get rid of duplicate code?
make a private method
writeSubOject(ArrayList<String[]> soData, File dir, String filename, Boolean append) {
try {
log.info("writing " + filename + "...");
final File out = CreateFileObject(dir, filename);
writeCsv(soData, out, append);
} catch (Exception e) {
log.info("Error in writeSubOject");
log.error(e);
e.printStackTrace();
}
}
then in your public methods
public void writeSubOject1(Object obj, File dir, String filename, Boolean append) {
ArrayList<SubObject1> subObject1 = obj.getSubObject1();
ArrayList<String[]> so1Data = SubObject1.getData(subObject1);
writeSubOject(so1Data,dir,filename,append);
}
public void writeSubOject2(Object obj, File dir, String filename, Boolean append) {
ArrayList<SubObject2> subObject2 = obj.getSubObject2();
ArrayList<String[]> so2Data = SubObject2.getData(subObject2);
writeSubOject(so2Data,dir,filename,append);
}
Your question is not well put, but I will try to answer as best as I can with the provided information.
First, I would write an abstract class.
public abstract class WritableObject {
List<WritableObject> children = new ArrayList<>();
public abstract List<String> getDataAsStringList();
List<WritableObject> getChildren() {
return this.children;
}
public void addChild(WritableObject wo) {
children.add(wo);
}
}
Now we can implement this interface in as many other classes as needed. Let's assume you have two for now.
public class WritableObjectOne extends WritableObject {
#Override
public List<String> getDataAsStringList() {
return Arrays.asList(""); // here the object returns it's String representation
}
}
public class WritableObjectTwo extends WritableObject {
#Override
public List<String> getDataAsStringList() {
return Arrays.asList(""); // here the object returns it's String representation
}
}
Now, the best part is that you can combine however you want. You can have children of any WritableObject and can have children for the child and so on.
Now, in your writer, you can just have one method:
public class Writer {
public void writeSubOject(WritableObject obj, File dir, String filename, Boolean append) {
List<WritableObject> children = obj.getChildren();
try {
log.info("writing " + filename + "...");
List<String> data = new ArrayList<>();
for (WritableObject child:children) {
data.addAll(child.getDataAsStringList());
}
final File out = CreateFileObject(dir, filename);
writeCsv(data, out, append);
} catch (Exception e) {
log.info("Error in writeSubOject1");
log.error(e);
e.printStackTrace();
}
}
}
Again, maybe this is not exactly how you want it (I find it weird how a list of strings is returned), but it should at least help you get to the right solution.
I'm trying to integrate my application with Bukkit - A Minecraft Server API.
Basically, I'm trying to figure out how could I get functionality like this to work:
Reflections reflections = new Reflections("com.mycompany");
Set<Class<? extends BlockEvent>> classes = reflections.getSubTypesOf(BlockEvent.class);
for (Class<? extends BlockEvent> clazz : classes) {
getServer().getPluginManager().registerEvents(new BlockListener<clazz>(), this);
}
I want to register a listener for all event types that extend BlockEvent. Events are registered by passing an implemention of Listener into the registerEvents(Listener, Plugin) method exposed by the Bukkit API's PluginManager. Obviously clazz is not a type and cannot be used as such.
Here is the generic class:
public class BlockListener<T extends BlockEvent> implements Listener {
#EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
void onBlock(T event) {
System.out.println("Block Event");
Bukkit.getPluginManager().callEvent(new BlockChangeEvent(event.getBlock()));
}
}
Update
public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(#NotNull Listener listener, #NotNull final Plugin plugin) {
Validate.notNull(plugin, "Plugin can not be null");
Validate.notNull(listener, "Listener can not be null");
boolean useTimings = server.getPluginManager().useTimings();
Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<Class<? extends Event>, Set<RegisteredListener>>();
Set<Method> methods;
try {
Method[] publicMethods = listener.getClass().getMethods();
Method[] privateMethods = listener.getClass().getDeclaredMethods();
methods = new HashSet<Method>(publicMethods.length + privateMethods.length, 1.0f);
for (Method method : publicMethods) {
methods.add(method);
}
for (Method method : privateMethods) {
methods.add(method);
}
} catch (NoClassDefFoundError e) {
plugin.getLogger().severe("Plugin " + plugin.getDescription().getFullName() + " has failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist.");
return ret;
}
for (final Method method : methods) {
final EventHandler eh = method.getAnnotation(EventHandler.class);
if (eh == null) continue;
// Do not register bridge or synthetic methods to avoid event duplication
// Fixes SPIGOT-893
if (method.isBridge() || method.isSynthetic()) {
continue;
}
final Class<?> checkClass;
if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
plugin.getLogger().severe(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass());
continue;
}
final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class);
method.setAccessible(true);
Set<RegisteredListener> eventSet = ret.get(eventClass);
if (eventSet == null) {
eventSet = new HashSet<RegisteredListener>();
ret.put(eventClass, eventSet);
}
for (Class<?> clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
// This loop checks for extending deprecated events
if (clazz.getAnnotation(Deprecated.class) != null) {
Warning warning = clazz.getAnnotation(Warning.class);
WarningState warningState = server.getWarningState();
if (!warningState.printFor(warning)) {
break;
}
plugin.getLogger().log(
Level.WARNING,
String.format(
"\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated. \"%s\"; please notify the authors %s.",
plugin.getDescription().getFullName(),
clazz.getName(),
method.toGenericString(),
(warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected",
Arrays.toString(plugin.getDescription().getAuthors().toArray())),
warningState == WarningState.ON ? new AuthorNagException(null) : null);
break;
}
}
final CustomTimingsHandler timings = new CustomTimingsHandler("Plugin: " + plugin.getDescription().getFullName() + " Event: " + listener.getClass().getName() + "::" + method.getName() + "(" + eventClass.getSimpleName() + ")", pluginParentTimer); // Spigot
EventExecutor executor = new EventExecutor() {
#Override
public void execute(#NotNull Listener listener, #NotNull Event event) throws EventException {
try {
if (!eventClass.isAssignableFrom(event.getClass())) {
return;
}
// Spigot start
boolean isAsync = event.isAsynchronous();
if (!isAsync) timings.startTiming();
method.invoke(listener, event);
if (!isAsync) timings.stopTiming();
// Spigot end
} catch (InvocationTargetException ex) {
throw new EventException(ex.getCause());
} catch (Throwable t) {
throw new EventException(t);
}
}
};
if (false) { // Spigot - RL handles useTimings check now
eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
} else {
eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
}
}
return ret;
}
Specifically
method.getParameterTypes()[0]
Does not work with generics.
You can create a generic method which returns you the BlockEventListenerObject, something like this:
private static <T extends BlockEvent> BlockEventListener<T> getBlockEventListener(Class<T> clazz) {
return new BlockEventListener<T>();
}
Then you can call it from your loop,
getServer().getPluginManager().registerEvents(getBlockEventListener(clazz), this);
„…Basically how could I get functionality like this to work…“
Here is one way…
...
public void use( final Set< Class < ? extends BlockEvent > > classes, PluginManager pluginMgr ) throws ReflectiveOperationException {
for( Class < ? extends BlockEvent > clazz : ( classes ) ){
pluginMgr.registerEvents( new BlockListener< >( clazz.newInstance( ) ), this );
}
}
...
I've implemented some stand-in classes to make the experiment testable. I used the experimental classes like this…
...
final Set< Class < ? extends BlockEvent > > classes = new HashSet< >( );
classes.add( BlockParty.class );
final PluginManager pluginMgr = new PluginManager( );
final Deduper experimental = new DeduperAnswer( );
experimental.use( classes, pluginMgr );
...
Click the green Start button at the top of the page and observe this output…
BlockChangeEvent [ block: Block#404b9385 ]
Havana Block Party!
EXPERIMENT SUCCESSFUL
I am using mockito-junit to test a piece of my code. As progressing I found out that there was an interface implemented in the main file which I was testing, when the test was running I found that the line where interface method is called get's covered but the real method doesn't get's covered.
This the code for the main file:
public class ExtractCurrencyDataTask {
private static final Logger LOGGER = LoggerFactory.getLogger(ExtractCurrencyDataTask.class);
#Autowired
private ExtractCurrencyService extractCurrencyService;
public void writeCurrencyListToFile(List<Currency> currencyList) {
if (currencyList != null && !currencyList.isEmpty()) {
String dir = "A path";
String fileName = "A filename";
String writeToFile = dir + "/" + fileName + ".writing";
String renameFile = dir + "/" + fileName + ".dat";
BufferedWriter writer = null;
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(writeToFile);
writer = new BufferedWriter(fileWriter);
extractCurrencyService.extractCurrencyList(currencyList, writer);
} catch (Exception e) {
throw new RuntimeException("Error writing Currency codes", e);
} finally {
if (writer != null) {
try {
writer.close();
fileWriter.close();
} catch (IOException e) {
LOGGER.info("Exception occured while closing the file writer", e);
}
moveFile(writeToFile, renameFile);
}
}
}
}
private void moveFile(String writeToFile, String renameFile) {
try {
FileUtils.moveFile(FileUtils.getFile(writeToFile), FileUtils.getFile(renameFile));
} catch (IOException e) {
LOGGER.info("Exception occured while moving file from writing to dat", e);
}
}
Here extractCurrencyService is the interface which I have mentioned.
The interface:
public interface ExtractCurrencyService {
public void extractCurrencyList(List<Currency> currency, Writer writer);
}
This the method definition which is done another file which implements same interface Filename:ExtractCurrencyServiceImpl.java
public class ExtractCurrencyServiceImpl implements ExtractCurrencyService {
private static final String SEP = "|";
private static final String NEWLINE = "\n";
#Override
public void extractCurrencyList(List<Currency> currencyList, Writer writer) {
if (currencyList != null) {
currencyList.forEach(currency -> {
String code = currency.getCode();
String name = currency.getName() == null ? "" : currency.getName();
Long noOfDecimals = currency.getNumberOfDecimals();
RoundingMethodValue roundingMethod = currency.getRoundingMethod();
boolean isDealCurrency = currency.isDealCurrency();
String description = currency.getDescription() == null ? "" : currency.getDescription();
try {
writer.write(createCurrencyDataLine(code,
name,
noOfDecimals,
roundingMethod,
isDealCurrency,
description));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
private String createCurrencyDataLine(String code,
String name,
Long noOfDecimals,
RoundingMethodValue roundingMethod,
boolean isdealCurrency,
String description) {
return code + SEP + name + SEP + noOfDecimals.toString() + SEP + roundingMethod.toString() + SEP
+ isdealCurrency + SEP + description + NEWLINE;
}
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
}
This is the test file:
#RunWith(MockitoJUnitRunner.class)
public class ExtractCurrencyDataTaskTest {
#Mock
private Currency mockCurrency;
#Mock
private ExtractCurrencyService mockExtractCurrencyService;
#Mock
private BufferedWriter mockBufferWriter;
#Mock
private Bean mockBean;
#InjectMocks
private ExtractCurrencyDataTask extractCurrencyDataTask;
#Test
public void writeCurrencyListToFileTest() {
List<Currency> currencyList = new ArrayList();
when(mockCurrency.getCode()).thenReturn("USD");
when(mockCurrency.getNumberOfDecimals()).thenReturn((long) 2);
when(mockCurrency.getRoundingMethod()).thenReturn(enum value);
when(mockCurrency.isDealCurrency()).thenReturn(true);
when(mockCurrency.getName()).thenReturn("US Dollars");
when(mockCurrency.getDescription()).thenReturn("Currency Description");
currencyList.add(mockCurrency);
extractCurrencyDataTask.writeCurrencyListToFile(currencyList);
}
}
This the configuration for Autowired bean
#Bean
public ExtractCurrencyService extractCurrencyService() {
return new ExtractCurrencyServiceImpl();
}
As you can see the real output of this process is a file will be created in a path mentioned with some data. Here in this test I am mocking the data and passing it to main file. Main file is the created file in respective path but there is no data in the file.
The data writing part is done by the interface method. This is the part where I need help.
Thanks in advance....
You are injecting a mock of ExtractCurrencyService into your tested class. So the test is running with this mock instance instead of ExtractCurrencyServiceImpl. The current behaviour is that your ExtractCurrencyDataTasktested class is calling to extractCurrencyService#extractCurrencyList, but this extractCurrencyService is a mock, not your real implementation, so the call is done but it does nothing.
If you want to unit test ExtractCurrencyDataTask then thats ok, but maybe you should assert the call to extractCurrencyService#extractCurrencyList is done in the way you expect.
If you want to unit test ExtractCurrencyServiceImpl then create a unit test for this class.
If you want to test the interaction between these two classes then create an integration test where ExtractCurrencyDataTask has injected a real instance of ExtractCurrencyServiceImpl, not a mock.
I have int, float, boolean and string from Properties file. Everything has loaded in Properties. Currently, I am parsing values as I know expected value for particular key.
Boolean.parseBoolean("false");
Integer.parseInt("3")
What is better way of setting these constants values, If I don't know what could be primitive value datatype for a key.
public class Messages {
Properties appProperties = null;
FileInputStream file = null;
public void initialization() throws Exception {
appProperties = new Properties();
try {
loadPropertiesFile();
} catch (Exception e) {
throw new Exception(e.getMessage(), e);
}
}
public void loadPropertiesFile() throws IOException {
String path = "./cfg/message.properties";
file = new FileInputStream(path);
appProperties.load(file);
file.close();
}
}
Properties File.
messassge.properties
SSO_URL = https://example.com/connect/token
SSO_API_USERNAME = test
SSO_API_PASSWORD = Uo88YmMpKUp
SSO_API_SCOPE = intraday_api
SSO_IS_PROXY_ENABLED = false
SSO_MAX_RETRY_COUNT = 3
SSO_FLOAT_VALUE = 3.0
Constant.java
public class Constants {
public static String SSO_URL = null;
public static String SSO_API_USERNAME = null;
public static String SSO_API_PASSWORD = null;
public static String SSO_API_SCOPE = null;
public static boolean SSO_IS_PROXY_ENABLED = false;
public static int SSO_MAX_RETRY_COUNT = 0;
public static float SSO_FLOAT_VALUE = 0;
}
If you have a class of configuration values, like your Constants class, and you want to load all values from a configuration (properties) file, you can create a little helper class and use reflection:
public class ConfigLoader {
public static void load(Class<?> configClass, String file) {
try {
Properties props = new Properties();
try (FileInputStream propStream = new FileInputStream(file)) {
props.load(propStream);
}
for (Field field : configClass.getDeclaredFields())
if (Modifier.isStatic(field.getModifiers()))
field.set(null, getValue(props, field.getName(), field.getType()));
} catch (Exception e) {
throw new RuntimeException("Error loading configuration: " + e, e);
}
}
private static Object getValue(Properties props, String name, Class<?> type) {
String value = props.getProperty(name);
if (value == null)
throw new IllegalArgumentException("Missing configuration value: " + name);
if (type == String.class)
return value;
if (type == boolean.class)
return Boolean.parseBoolean(value);
if (type == int.class)
return Integer.parseInt(value);
if (type == float.class)
return Float.parseFloat(value);
throw new IllegalArgumentException("Unknown configuration value type: " + type.getName());
}
}
Then you call it like this:
ConfigLoader.load(Constants.class, "/path/to/constants.properties");
You can extend the code to handle more types. You can also change it to ignore missing properties, instead of failing like it does now, such that assignments in the field declaration will remain unchanged, i.e. be the default.
If you know the type of constant, you can use Apache Commons Collections.
For example, you can use some utilities method based on type of your constant.
booelan SSO_IS_PROXY_ENABLED = MapUtils.getBooleanValue(appProperties, "SSO_IS_PROXY_ENABLED", false);
String SSO_URL = MapUtils.getString(appProperties, "SSO_URL", "https://example.com/connect/token");
You can even use default values to avoid errors.
Dambros is right, every thing you store inside a Properties file is as a String value.
You can track your different primitive data types after retrieving properties value as below like ref. -
Java Properties File: How to Read config.properties Values in Java?
package crunchify.com.tutorial;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Properties;
/**
* #author Crunchify.com
*
*/
public class CrunchifyGetPropertyValues {
String result = "";
InputStream inputStream;
public String getPropValues() throws IOException {
try {
Properties prop = new Properties();
String propFileName = "config.properties";
inputStream = getClass().getClassLoader().getResourceAsStream(propFileName);
if (inputStream != null) {
prop.load(inputStream);
} else {
throw new FileNotFoundException("property file '" + propFileName + "' not found in the classpath");
}
Date time = new Date(System.currentTimeMillis());
// get the property value and print it out
String user = prop.getProperty("user");
String company1 = prop.getProperty("company1");
String company2 = prop.getProperty("company2");
String company3 = prop.getProperty("company3");
result = "Company List = " + company1 + ", " + company2 + ", " + company3;
System.out.println(result + "\nProgram Ran on " + time + " by user=" + user);
} catch (Exception e) {
System.out.println("Exception: " + e);
} finally {
inputStream.close();
}
return result;
}
}
and later convert to primitive -
How to convert String to primitive type value?
I suggest you to track your data types value by putting the key values inside String type switch statement and later retrieve the related data type value by using key name cases.
String type switch case is possible after Java 7.
Not entirely sure whether I exactly understand the problem but a possibility could be to include the type of the property value in the (String) value. So for example the properties you showed would become something like:
SSO_URL = URL:https://example.com/connect/token
SSO_API_USERNAME = STRING:test
SSO_API_PASSWORD = STRING:Uo88YmMpKUp
SSO_API_SCOPE = STRING:intraday_api
SSO_IS_PROXY_ENABLED = BOOLEAN:false
SSO_MAX_RETRY_COUNT = INTEGER:3
SSO_FLOAT_VALUE = FLOAT:3.0
During the parsing of the property values you first determine the type of the property by looking at the part before : and use the part after for the actual parsing.
private static Object getValue(Properties props, String name) {
String propertyValue = props.getProperty(name);
if (propertyValue == null) {
throw new IllegalArgumentException("Missing configuration value: " + name);
} else {
String[] parts = string.split(":");
switch(parts[0]) {
case "STRING":
return parts[1];
case "BOOLEAN":
return Boolean.parseBoolean(parts[1]);
....
default:
throw new IllegalArgumentException("Unknown configuration value type: " + parts[0]);
}
}
}
Follow the dropwizard configuration pattern where you define your constants using YAML instead of Properties and use Jackson to deserialize it into your Class. Other than type safety, dropwizard's configuration pattern goes one step further by allowing Hibernate Validator annotations to validate that the values fall into your expected ranges.
For dropwizard's example...
http://www.dropwizard.io/0.9.2/docs/getting-started.html#creating-a-configuration-class
For more information about the technology involved...
github.com/FasterXML/jackson-dataformat-yaml
hibernate.org/validator/
Spring Boot has ready to use and feature reach solution for type-safe configuration properties.
Definitely, use of the Spring just for this task is overkill but Spring has a lot of cool features and this one can attract you to right side ;)
You can define your configurable parameters as 'static' in your class of choice, and from a static init call a method that loads the parameter values from a properties file.
For example:
public class MyAppConfig {
final static String propertiesPath="/home/workspace/MyApp/src/config.properties";
static String strParam;
static boolean boolParam;
static int intParam;
static double dblParam;
static {
// Other static initialization tasks...
loadParams();
}
private static void loadParams(){
Properties prop = new Properties();
try (InputStream propStream=new FileInputStream(propertiesPath)){
// Load parameters from config file
prop.load(propStream);
// Second param is default value in case key-pair is missing
strParam=prop.getProperty("StrParam", "foo");
boolParam=Boolean.parseBoolean(prop.getProperty("boolParam", "false"));
intParam= Integer.parseInt(prop.getProperty("intParam", "1"));
dblParam=Double.parseDouble(prop.getProperty("dblParam", "0.05"));
} catch (IOException e) {
logger.severe(e.getMessage());
e.printStackTrace();
}
}
}
This might help:
props.getProperty("name", Integer.class);
Are there any way not to define all Places in the PlaceHistoryMapper?
At this moment I am using Generator in order to generate list of all places automatically, but I am not sure that this is a correct way.
public class AppPlaceHistoryMapper extends AbstractPlaceHistoryMapper<Object> {
#Override
protected PrefixAndToken getPrefixAndToken(Place place) {
if (place instanceof AbstractPlace) {
return new PrefixAndToken(((AbstractPlace) place).getName(), ((AbstractPlace) place).getTokenizer()
.getToken((AbstractPlace) place));
}
throw new RuntimeException("Invalid place: " + place);
}
/**
* This method will be overrided by the gwt-generated class, so any changes made in it will not be executed
*
* #see PlaceMapperGenerator
*/
#Override
protected PlaceTokenizer<?> getTokenizer(String prefix) {
AbstractPlace[] places = {/* List of places generated by PlaceMapperGenerator */};
for (AbstractPlace p : places) {
if (p.getName().equals(prefix)) {
return p.getTokenizer();
}
}
throw new RuntimeException("Unable to find place for provided prefix: " + prefix);
}
}
Generator:
public class PlaceMapperGenerator extends Generator {
// #formatter:off
private static final String GENERATED_METHOD_TEMPLATE =
"protected com.google.gwt.place.shared.PlaceTokenizer<?> getTokenizer(String prefix) {" +
"AbstractPlace[] places = { %s };" +
"for (AbstractPlace p : places) {" +
"if (p.getName().equals(prefix)) {" +
"return p.getTokenizer();" +
"}" +
"}" +
"throw new RuntimeException(\"Unable to find place for provided prefix: \" + prefix);" +
"}"
; // #formatter:on
#Override
public String generate(TreeLogger logger, GeneratorContext context, String typeName) {
JClassType type;
try {
type = context.getTypeOracle().getType(typeName);
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
String implTypeName = type.getSimpleSourceName() + "Impl";
String implPackageName = type.getPackage().getName();
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(implPackageName,
implTypeName);
composerFactory.setSuperclass(AppPlaceHistoryMapper.class.getName());
#SuppressWarnings("resource")
PrintWriter printWriter = context.tryCreate(logger, implPackageName, implTypeName);
if (printWriter != null) {
SourceWriter sourceWriter = composerFactory.createSourceWriter(context, printWriter);
sourceWriter.print(GENERATED_METHOD_TEMPLATE, getPlaces(context));
sourceWriter.commit(logger);
printWriter.close();
}
return composerFactory.getCreatedClassName();
}
private static String getPlaces(GeneratorContext context) {
JPackage[] packages = context.getTypeOracle().getPackages();
List<String> places = new ArrayList<String>();
for (JPackage p : packages) {
if (p.getName().startsWith(AbstractPlace.class.getPackage().getName())) {
JClassType[] types = p.getTypes();
for (JClassType type : types) {
if (type.getSuperclass() != null
&& type.getSuperclass().getQualifiedSourceName().equals(AbstractPlace.class.getName())) {
places.add("new " + type.getQualifiedSourceName() + "()");
}
}
}
}
return places.toString().replaceAll("^\\[|\\]$", "");
}
}
I'm afraid that the only way to figure out what Places and Tokenizers are in your application, without maintaining a list with them, is with a generator like you are doing.
Anyway instead of maintaining a generator I would use the #WithTokenizers annotation and let GWT generate your PlaceHistoryMapper take a look to the GWT MVP dev-guide
#WithTokenizers({HelloPlace.Tokenizer.class, GoodbyePlace.Tokenizer.class})
public interface AppPlaceHistoryMapper extends PlaceHistoryMapper {}
What I do in my applications is to use a script to generate activities, views, places and update gin modules and mappers based on a template.