6

I am trying to do reverse lookup on few enum classes implementing same Field interface by iterating through list of Classes using Guava's Maps.uniqueIndex:

  Field valueOfSearchName = null;
  for (final Class<? extends Enum<?>> clazz : ImmutableList.of(
      EntityField.class,
      AddressField.class,
      PersonFunctionType.class)) {
    valueOfSearchName = Fields.valueOfSearchName(clazz, term.field()); // error
    if (valueOfSearchName != null) {
      // do something...
      break;
    }
  }

I don't want to repeat same code (for making index and doing lookup) in all enum classes, so I use helper static class Fields containing Fields.valueOfSearchName method:

  public static <E extends Enum<E> & Field> Field valueOfSearchName(
      final Class<E> clazz, final String searchName) {
    // TODO: cache the index
    final ImmutableMap<String, E> index = Maps.uniqueIndex(
        EnumSet.allOf(clazz), GET_SEARCH_NAME_FUNCTION);
    return index.get(searchName);
  }

Unfortunately, Eclipse shows an error:

Bound mismatch: 
The generic method valueOfSearchName(Class<E>, String) of type Fields is not 
applicable for the arguments (Class<capture#1-of ? extends Enum<?>>, String).
The inferred type capture#1-of ? extends Enum<?> is not a valid substitute
for the bounded parameter <E extends Enum<E> & Field>

The problem is Class<? extends Enum<?>> clazz in for-each loop (not matching Field), but I don't know how to deal with this case (obviously I cannot add & Field to clazz).

Community
  • 1
  • 1
Grzegorz Rożniecki
  • 27,415
  • 11
  • 90
  • 112
  • This link may be helpful http://stackoverflow.com/questions/7032941/why-is-this-type-not-a-valid-substitute-for-the-type-parameter – kosa Dec 28 '11 at 15:36
  • 1
    Eclipse is famous for disagreeing with common sense. Check this post: http://stackoverflow.com/questions/2220763/java-specific-enums-and-generic-enum-parameters – Nikola Yovchev Dec 28 '11 at 15:41
  • @baba I can't replace `?` wildcard with named type parameter `T` in for-each loop, where the problem occurs... However [inspired by John Skeet answer from this question](http://stackoverflow.com/questions/5262096/how-do-i-get-the-value-of-an-enum-if-i-dont-know-the-class-at-compile-time) I changed loop to `for (@SuppressWarnings("rawtypes") final Class clazz : ...` and it did the trick... Is it the right way to do this? – Grzegorz Rożniecki Dec 28 '11 at 16:09
  • Well, I suppose it is one of the ways. At least to me your code is difficult to read @ first glance as it is right now. The other way, however, would be to go fully reflectory and get rid of your helper class, which to me seems like a lot of boilerplate for a simple task. – Nikola Yovchev Dec 28 '11 at 16:17
  • @baba `valueOfSearchName` made its way into new `FieldEnumWrapper` class which ensures that enum implements `Field` interface and does appropriate magic for all those classes, see my own answer ;) – Grzegorz Rożniecki Jan 03 '12 at 10:42

3 Answers3

4

Consider Class<? extends List<?>. Class<? extends List<?> has two wildcards whereas <E extends List<E>> Class<E> only has generic parameter. The former will admit Class<ArrayList<String>>. So without doing something extra special for enums, the types are not compatible.

How to fix? An extra layer of indirection!

public final class MetaEnum<E extends Enum<E>> {
    private final E clazz;
    public static <E extends Enum<E>> MetaEnum<E> of(E clazz) {
        return clazz;
    }
    private MetaEnum(E clazz) {
        this.clazz = clazz;
    }
    public E clazz() {
        return clazz;
    }
    // ...
}

for (final MetaEnum<?> meta : ImmutableList.of(
    MetaEnum.of(EntityField       .class),
    MetaEnum.of(AddressField      .class),
    MetaEnum.of(PersonFunctionType.class)
)) {
    Field valueOfSearchName = Fields.valueOfSearchName(
        meta.clazz(), term.field()
    );
    ...

(Usual Stack Overflow dislaimer: Not so much as attempted to compile.)

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
3

Inspired by Tom Hawtin's answer I created wrapper class holding Classes, but only those with signature <E extends Enum<E> & Field>:

public final static class FieldEnumWrapper<E extends Enum<E> & Field> {
  private final Class<E> clazz;
  private final ImmutableMap<String, E> index;

  public static <E extends Enum<E> & Field> 
      FieldEnumWrapper<E> of(final Class<E> clazz) {
    return new FieldEnumWrapper<E>(clazz);
  }

  private FieldEnumWrapper(final Class<E> clazz) {
    this.clazz = clazz;
    this.index = Maps.uniqueIndex(
        EnumSet.allOf(clazz), new Function<E, String>() {
          @Override
          public String apply(final E input) {
            return input.searchName();
          }
        });
  }

  public Class<E> clazz() {
    return clazz;
  }

  public Field valueOfSearchName(final String searchName) {
    return index.get(searchName);
  }
}

Now:

for (final FieldEnumWrapper<?> fieldEnum : ImmutableList.of(
    FieldEnumWrapper.of(EntityField.class),
    FieldEnumWrapper.of(AddressField.class),
    FieldEnumWrapper.of(PersonFunctionType.class))) {
  valueOfSearchName = fieldEnum.valueOfSearchName("POD_I_OS_PARTNER");
  // ...

is type-safe and inappropriate usage of FieldEnumWrapper's static factory:

FieldEnumWrapper.of(NotEnumAndFieldClass.class)

generates compile error.

Moreover, valueOfSearchName is now method of FieldEnumWrapper what make more sense that helper class.

Community
  • 1
  • 1
Grzegorz Rożniecki
  • 27,415
  • 11
  • 90
  • 112
0

maybe something like this:

import java.util.*;
class N {
    static int n;
}
interface HasField {
    int getField();
}
enum Color implements HasField {
    r, g, b;
    public int getField() {
        return field;
    }
    private int field = N.n++;
}
enum Day implements HasField {
    m, t, w, th, f, sa, su;
    public int getField() {
        return field;
    }
    private int field = N.n++;
}
    class Helper {
    Helper(Set<HasField> set) {
        for (HasField hasField : set)
            if (hasField instanceof Enum) {
                Enum<?> e = (Enum<?>) hasField;
                for (Object o : e.getDeclaringClass().getEnumConstants()) {
                    map.put(((HasField) o).getField(), (Enum<?>) o);
                }
            } else
                throw new RuntimeException(hasField + " is not an enum!");
    }
    final Map<Integer, Enum<?>> map = new TreeMap<Integer, Enum<?>>();
}
public class Main {
    public static void main(String[] args) {
        Set<HasField> set = new LinkedHashSet<HasField>();
        set.add(Color.r);
        set.add(Day.m);
        Helper helper = new Helper(set);
        for (int i = 0; i < N.n; i++)
            System.out.println(i + " " + helper.map.get(i));
    }
}
Ray Tayek
  • 9,841
  • 8
  • 50
  • 90