Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing generic type description

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.

like image 255
mezzodrinker Avatar asked Apr 10 '26 16:04

mezzodrinker


2 Answers

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);
  }
}
like image 98
Stefan Haustein Avatar answered Apr 13 '26 05:04

Stefan Haustein


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


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!