I'm trying to write an utility method which allows me to get the definition of a field, including all generic arguments. To do so, I retrieve the generic type of a field via Field.getGenericType() and parse the type name which has the following syntax (in EBNF):
generictype = classname [ '<' generictype { ',' ' ' generictype } '>' ]
classname = package '.' ( letter | '_' ) { letter | digit | '_' }
package = ( packagepart { '.' packagepart } ) |
packagepart = ( letter | '_' ) { letter | digit | '_' }
My first attempt to parse this was by using the regular expression
(?<argument>\w+(?:\.\w+)*(?:\<(?<arglist>\g<argument>(?:,\s\g<argument>)*)\>)?)
whose details can be inspected here. This regular expression is just what I need. Now, Java regex does not support the \g<name> construct, so I can't use this approach if I want to support generic types with an unknown depth in their arguments.
Is there any other approach that I can use? If yes, how can I achieve what I'm trying to do?
EDIT: The reason I want to achieve this is because I have a configuration and want to transfer its contents into the respective fields of an object. Some sort of deserialization, if you want to call it that way. Now, the configuration only supports primitive types, java.lang.String, java.util.List<T>, java.util.Map<K, V> and java.util.Map.Entry<K, V>. To retrieve values of those classes, the client has to provide a class as parameter which will be used to deserialize the strings that are saved in the configuration. Because of that, I have to determine which generic parameters a field of a class used and also which Classes they correspond to.
If you really need to parse (highstakes' approach looks more elegant, but parsing is what the question asks for), I'd do this with recursive descent parsing, something like this:
class GenericType {
String baseName;
List<GenericType> params;
GenericType(String baseName, List<GenericType> params) {
this.baseName = baseName;
this.params = params;
}
static GenericType parse(String s) {
StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(s));
tokenizer.wordChars('.', '.'); // Make dots part of the name
try {
tokenizer.nextToken(); // Skip "BOF" token
return parse(tokenizer);
} catch (IOException e) {
throw new RuntimeException();
}
}
static GenericType parse(StreamTokenizer tokenizer) throws IOException {
String baseName = tokenizer.sval;
tokenizer.nextToken();
List<GenericType> params = new ArrayList<>();
if (tokenizer.ttype == '<') {
do {
tokenizer.nextToken(); // Skip '<' or ','
params.add(parse(tokenizer));
} while (tokenizer.ttype == ',');
tokenizer.nextToken(); // skip '>'
}
return new GenericType(baseName, params);
}
}
You can do the following:
Type type = Field.getGenericType();
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
Class<?> genericType = (Class<?>) pt.getActualTypeArguments()[0];
}
If this is not enough, just use the google reflections library : https://github.com/google/guava/wiki/ReflectionExplained
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With