I have seen this which is pretty nice solution if i had a string instead of integer, but in case all i have is the specific enum's class object and an integer, how to do i get the specific enum constant instance?
Relying on the ordinal value of Java enum constants is poor practice -- it's too easy to accidentally reorder them, which would then break your code. The better solution is to simply provide your own integer that you can use instead:
public enum MyThing {
FOO(1),
BAR(2),
BAZ(3);
private final int thingId;
private MyThing(int thingId) {
this.thingId = thingId;
}
public int getThingId() {
return thingId;
}
}
Then whenever you want to get the thingId from a MyThing, just call the getThingId() method:
void doSomething(MyThing thing) {
System.out.printf("Got MyThing object %s with ID %d\n",
thing.name(), thing.getThingId());
}
If you want to be able to look up a MyThing by its thingId, you can build a lookup table yourself and store it in a static final field:
private static final Map<Integer, MyThing> LOOKUP
= createLookupMap();
private static Map<Integer, MyThing> createLookupMap() {
Map<Integer, MyThing> lookupMap = new HashMap<>();
for (MyThing thing : MyThing.values()) {
lookupMap.put(thing.getThingId(), thing);
}
return Collections.unmodifiableMap(lookupMap);
}
public static MyThing getThingById(int thingId) {
MyThing result = LOOKUP.get(thingId);
if (result == null) {
throw new IllegalArgumentException(
"This is not a valid thingId: " + thingId);
}
return result;
}
If you end up having a lot of enum classes and you want to do a similar thing with each of them, you can define an interface for that:
public interface Identifiable {
int getId();
}
And then make your enum implement that interface:
public enum MyThing implements Identifiable {
...
#Override
public int getId() {
return thingId;
}
}
And then you could build a reusable mechanism for looking up an Identifiable object based on its ID.
seem to have found the answer :
((Class<? extends Enum>)clazz).getEnumConstants()[index]
although for any-one looking for that, you should consider following #Daniel Pryden answer as most likely that using this in most use cases i can think of is bad practice.
Related
I have a question regarding to the andriod #IntDef Annotation. I know
that in its basic usage, it should replace the enum. But what if
I have a parameterized enum with multiple hardwired values for example
public enum MyEnum {
YES(true, 1),
NO(false, 0);
private boolean boolState;
private boolean intState;
MyEnum(boolean boolState, int intState) {
this.boolState = boolState;
this.intState = intState;
}
public boolean getBoolState() {
return boolState;
}
public int getIntState() {
return intState;
}
}
How would this be replaced by an Enumerated Annotation in Android?
Is it even suggestive to do something like that in this case? I searched
everywhere, but I haven't found any answer for that.
Thank you in advance!
I don't think you would be able to find anything because:
IntDef is a way of replacing an integer enum where there's a parameter
that should only accept explicit int values.
you can read more about it here. Enumerated annotations are for simple types, you could use it for strings also StringDef. Use enum when you need its features. Don't avoid it strictly. For your case I think creating class instead of enum would look like this:
public class MyEnum {
public static final MyEnum YES = new MyEnum(true, 1);
public static final MyEnum NO = new MyEnum(false, 0);
private boolean boolState;
private int intState;
MyEnum(boolean boolState, int intState) {
this.boolState = boolState;
this.intState = intState;
}
public boolean getBoolState() {
return boolState;
}
public int getIntState() {
return intState;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyEnum myEnum = (MyEnum) o;
return boolState == myEnum.boolState && intState == myEnum.intState;
}
}
and you could use constants in your code. But if using enums you will have type checking (you'll be able to accept only listed values) and method overloading (every enum constant can have its own implementation of a method). If you want to use less space and that is the only reason why you want to avoid using enum I would suggest you that it's not worth it.
I follow a rule with enums in Android development:
if it has no params, use an intdef/stringdef,
if it has params, use an enum
If there is a way around using an enum, I'll certainly consider it where it doesn't undermine the code.
A lot was made from the video Colt Mcanlis posted: https://www.youtube.com/watch?v=Hzs6OBcvNQE&feature=youtu.be
however it had some fairly shaky numbers in it as pointed out by Jake Wharton: https://plus.google.com/+JakeWharton/posts/bTtjuFia5wm
The main drawback of enums is that they use more memory than constants would, but if that enum aids in better code, I say use it rather than micro-optimise. Just don't go overboard using them and be aware of their footprint.
I'm coming late, but anyways, since intdef ins an annotation, you can create an annotation using a custom class and then use it in the same way. given the fact an annotation needs primitives, you'll have to pass an interface as the annotation class type, and use subclasses as the value array.
example:
public interface GenericContainer<T, X> {
public T getValueOne();
public X getValueTwo();
}
then an implementation for true/1
public class TrueContainer implements GenericContainer<Boolean, Integer> {
#Override
public Boolean getValueOne() {
return true;
}
#Override
public Integer getValueTwo() {
return 1;
}
}
and other for false/0
public class FalseContainer implements GenericContainer<Boolean, Integer> {
#Override
public Boolean getValueOne() {
return false;
}
#Override
public Integer getValueTwo() {
return 0;
}
}
finally, use them:
#Retention(RetentionPolicy.SOURCE)
#GenericDef({TrueContainer.class, FalseContainer.class})
public #interface genericTest{}
boolean test = isTest(new FalseContainer());
First of all, sorry for the bad title. I don't know how to describe the problem in a few words (maybe not even in many)...
I am refactoring some settings in our system to be more abstract. The current solution has multiple tables in the DB, one for each settings area. In order to add a new setting, you'll need to extend the schema, the hibernate class, all transfer object classes, getters/setters, etc. I felt that this is violating OCP (open-closed principle), thus the refactoring.
I've spent some time coming up with ideas on how to implement such an abstraction. My favourite idea so far is the following:
1 enum for each settings area
1 enum value for each setting
Each setting is a SettingsDefinition<T> class using a generic type
A SettingsService is using static get/set methods with generic types
So for example, a settings area could be:
public enum SettingsABC{
A(new SettingDefinition<Integer>("A", 123)),
B(new SettingDefinition<String>("B", "Hello")),
C(new SettingDefinition<Boolean>("C", false));
private SettingDefinition settingDefinition;
SettingsABC(SettingDefinition settingDefinition) {
this.settingDefinition = settingDefinition;
}
public SettingDefinition getDefinition() {
return settingDefinition;
}
}
Where the SettingDefinition is the following:
public class SettingDefinition<T> {
private String name;
private T defaultValue;
public SettingDefinition(String name, T defaultValue) {
this.name = name;
this.defaultValue = defaultValue;
}
public String getName() {
return name;
}
public T getDefaultValue() {
return defaultValue;
}
}
And the service to get/set the values would be:
public class SettingsService {
public static <T> T getSetting(SettingDefinition setting) {
// hit db to read
// return value
}
public static <T> void setSetting(SettingDefinition setting, T value) {
// hit db to write
}
}
And the consumer would look something like this:
String value = SettingsService.getSetting(SettingsABC.B.getDefinition());
SettingsService.setSetting(SettingsABC.A.getDefinition(), 123);
My problem is that I cannot enforce a compiler type check between the generic type of the SettingDefinition inside SettingsABC and the generic type of get/set methods of the service. So in essence, I can do this:
Integer value = SettingsService.getSetting(SettingsABC.B.getDefinition());
Where B's definition is of type String.
Also, I can do this:
SettingsService.setSetting(SettingsABC.A.getDefinition(), "A");
Where A's definition is an Integer.
Is there any way to use generics to force these two different generic types match?
You can convert the enum to the class:
public final class SettingsABC<T> {
public static final SettingsABC<Integer> A =
new SettingsABC<>(new SettingDefinition<>("A", 123));
public static final SettingsABC<String> B =
new SettingsABC<>(new SettingDefinition<>("B", "Hello"));
public static final SettingsABC<Boolean> C =
new SettingsABC<>(new SettingDefinition<>("C", false));
private final SettingDefinition<T> settingDefinition;
// private constructor, so nobody else would instantiate it
private SettingsABC(SettingDefinition<T> settingDefinition) {
this.settingDefinition = settingDefinition;
}
public SettingDefinition<T> getDefinition() {
return settingDefinition;
}
}
This way individual constants will be typed. Now you can use the type arguments for SettingService as well:
public static <T> T getSetting(SettingDefinition<T> setting) {
...
}
public static <T> void setSetting(SettingDefinition<T> setting, T value) {
...
}
Although it's not an enum anymore, it can be used mostly in the same way. If you need other methods which are usually available in enum, you can mimic them like this:
public String name() {
return settingDefinition.getName();
}
#Override
public String toString() {
return settingDefinition.getName();
}
// and so on
What I mean by type is something that would allow me to do the following.
public class AnyObject{
List<this.type> list;
}
I know the following dosen't work.
public class AnyObject{
List<this.getClass()> list;
}
So how would I create a lets say a list, for example sake, of type of whatever this is?
--------------- UPDATE ---------------
I apologize I don't think I was clear. I seem to be getting that there is no way to escape type erasure, but if there is still away to solve my problem I will explain it better. Disclosure, this is more of an Objectify question. Sorry I have come to see that now.
Here we go, clear as I can ...
For every entity I plan to persist, in GAE datastore using Objectiy, I would like to have a method to generate an Objectify Key<?> using the id and parent field. Lets call this method generateKey(). here is how it looks.
public Key<MyEntity> generateKey() {
Key<MyEntity> key = Key.create(this.parent, MyEntity.class, this.id);
return key;
}
The problem is I have to write this exact code, more or less, for every entity I create. Actually, there is other repeated code, but my point can be made with this piece of repeated code alone.
So I tried this. I created a class called MyProjectEntity and have all my entitys extend it. Then implemented a generateKey() method using generics.
public abstract class MyProjectEntity<T, Y> {
#Id Long id;
#Parent Key<T> parentKey;
public Key<Y> generateKey() {
Key<Y> key = Key.create(this.parentKey, this.getClass(), this.id);
return key;
}
}
Then I extended all my entity classes with this new class I created called MyProjectEntity. Like such ...
#Entity
public class MyEntity extends MyProjectEntity<MyEntityParent> {...}
Sounds good, now all my entity will have a generateKey() method, well this didn't quite work. Objectify yelled at me and said IllegalArgumentException, can not declare Key of type T.
Then I tried Key<Object>, Objectify was still unpleased, Objectify said Object is not a registered entity. Should I register Object!?!? and that kinda loses the whole point to a typed key that Objectify offers.
Is there a good solution. Thanks!
-- UPDATE 2 --
Since someone pointed out Key.create(myEntity) I should point my full use ...
/**********************************************************************************************************************
* Constructors END & Identification and Relationship Methods BEGIN
**********************************************************************************************************************/
#ApiSerializationProperty(name = "id")
public String getWebSafeKey() {
String webSafeKey = getKey().getString();
return webSafeKey;
}
public void setWebSafeKey(String webSafeKey) throws BadRequestException {
try {
Key<MyEntity> key = Key.create(webSafeKey);
setKey(key);
} catch (IllegalArgumentException illegalArgumentException) {
throw new BadRequestException(ErrorMessage.INVALID_ID);
}
}
#ApiSerializationProperty(name = "parentId")
public String getParentWebSafeKey() {
String webSafeKey = parent.getString();
return webSafeKey;
}
public void setParentWebSafeKey(String parentWebSafeKey) throws BadRequestException {
if (id == null) {
try {
parent = Key.create(parentWebSafeKey);
} catch (IllegalArgumentException illegalArgumentException) {
throw new BadRequestException(ErrorMessage.invalidParentId("Property"));
}
} else {
/* Do nothing. Only set parent here if setWebSafeKey is never called, such as during a create. */
}
}
#ApiSerializationProperty(ignored = AnnotationBoolean.TRUE)
public Key<MyEntity> getParentKey() {
return parent;
}
public void setParentKey(Key<MyEntity> parentKey) {
this.parent = parentKey;
}
#ApiSerializationProperty(ignored = AnnotationBoolean.TRUE)
public Key<MyEntity> getKey() {
Key<MyEntity> key = Key.create(parent, MyEntity.class, id);
return key;
}
public void setKey(Key<MyEntity> key) {
id = key.getId();
parent = key.getParent();
}
public boolean webSafeKeyEquals(String webSafeKey) {
boolean equals;
if (id !=null & parent !=null) {
equals = getWebSafeKey().equals(webSafeKey);
} else {
equals = false;
}
return equals;
}
/**********************************************************************************************************************
* Identification Methods END & Other Getters and Setters BEGIN
**********************************************************************************************************************/
All this has to be inserted for every entity I create with MyEntity replaced for the actual entity name. It's not just typing. This code doesn't belong in the entity class, but rather in some abstract parent. If I could have only code unique to a particular entity in the class, my model would be cleaner, and easier to expand. Thanks again.
This would not make sense. Consider: you would never know what the type of list is. Suppose that list is used in some method of some class, it could always be that this is an instance of a subclass. So the parameter of List in the type of list can never be assumed in any code. If it can never be known, then what is the point of it? You would just use List<?>.
Generics is a purely compile-time thing. Therefore, it is meaningless to depend on the runtime class of something.
I suggest that you have
public class AnyObject<T> {
List<T> list;
}
and any class Foo which wants to have list be a List<Foo>, for example, should just implement or inherit from AnyObject<Foo>.
This does not make sense List<this.getClass()> list; as the type parameters are compile time thing in java. This information is erased at runtime.
Without being familiar with Objectify, just generics, the thing I see is that Key.create is supposed to itself take a generic argument <T> for the type of returned Key. So you would be supposed to do the following when you call the method in the superclass:
Key<Y> key = Key.<Y>create(this.parentKey, this.getClass(), this.id);
You may only simply have to do that to fix the error (and should be doing it anyway). Otherwise Key.create will try to instantiate a new Key<Y> and although it is more or less valid to not declare a type argument when a method asks for one, apparently Key.create may not like that.
I think you should also take another look at your Ts and Ys because it appears you are mixing them. Right now you are handing Key.create a Key<T> as a parameter but wanting to return a Key<Y>. Also if you declare your class as having <T, Y> it should be illegal to extend it with only <MyEntityParent>.
Looking at your code I think what you are trying to do is create Key of the same class as the method you are calling it from. IE class generateKey in MyEntity should return a Key<MyEntity>. I think the proper way to do this would be like so (which is valid):
public abstract class MyProjectEntity<T, K> {
Long id;
Key<K> parentKey;
public Key<K> generateKey() {
return Key.<K>create(parentKey, this.getClass(), id);
}
}
public class MyEntity extends MyEntityParent<MyEntityParent, MyEntity> {
/*
* K is now MyEntity and parentKey is a Key<MyEntity>
* generateKey now does the following:
*
* public Key<MyEntity> generateKey() {
* return Key.<MyEntity>create(parentKey, MyEntity.class, id);
* }
*
*/
}
It just seems like your example that doesn't work is giving the error because you aren't declaring the types properly. But it is hard to tell because it is unclear what your T and Y are supposed to be. You only show one of the types being declared and at least in your generateKey method you are handing Key.create a Key<T> but wanting to return a Key<Y>.
Or perhaps you should take a look at Registering Entities in the Objectify API. IE it seems you might be supposed to do something like this and that is a possible reason you are getting the error:
static {
ObjectifyService.register(MyEntityParent.class);
}
But anyway in the world of Java generics you really ought to be able to do something like this without any gymnastics unless something else is going on. The nature of erasure is that you can't find out the type at runtime but the type is essentially "known" because all instances of T are replaced with the argument type.
public abstract class MyProjectEntity<T> {
Key<T> parentKey;
}
becomes
public class MyEntity extends MyProjectEntity<MyEntityParent> {
Key<MyEntityParent> parentKey;
}
You can't find out whether or not parentKey is of Type <MyEntityParent> but it is of that type. You can obviously see this with something like a java.util.List where if you do the following:
List<Double> doubleList = new ArrayList<Double>(0);
doubleList.add("a string");
You will get the following if you ignore the compiler errors and try to run the program anyway:
Uncompilable source code - Erroneous sym type: java.util.ArrayList.add
java.lang.RuntimeException: Uncompilable source code - Erroneous sym type: java.util.ArrayList.add
Because the list does "only hold" instances of Double. This situation could be compared to an anonymous class where that instance of an ArrayList's add method now officially takes a Double as an argument. It is uncompilable because I just tried to do this:
public void add(Double element) {
// add the element to the array
}
list.add("a string");
Which is obviously illegal. This ArrayList's underlying array is still an Object[] but the methods will be changed to reflect the type and safely make sure the array only holds Double elements at runtime.
So I would recommend taking a look at the things I mentioned because it appears that there's more than one problem unless you've omitted relevant code.
I think I understand your problem and here is how you could do it. The trick is to pass the subclass as a generic parameter of the parent class:
class Parent<T> {
T doStuff() {
T res = null;
// res = ..... this.getClass() is ok...
return res;
}
}
public class SelfGerenic extends Parent<SelfGerenic> {
}
public class OtherSubClass extends Parent<OtherSubClass> {
}
If I got you right, you're looking for something like this:
public class Test {
private int id;
public Key<Test> getKey() {
return createKey(id, this.getClass());
}
public static <T> Key<T> createKey(int id, Class<? extends T> clazz) {
return new Key<T>(clazz, id);
}
private static class Key<T> {
private final Class<? extends T> clazz;
private final int id;
private Key(Class<? extends T> clazz, int id) {
this.clazz = clazz;
this.id = id;
}
private int getId() {
return id;
}
private Class<? extends T> getClazz() {
return clazz;
}
}
public int getId() {
return id;
}
}
It is not possible to replace Test here: public Key<Test> getKey() {!
This is because getKey() always returns Key. It can not return Test.
So basically no, there is no way to change this behaviour. Also there is no way to get the generic type of the "current" class. This is some kind of limit of the java generics :P
You could remove the generics here, so you do not have to implement getKey() every time.
public class Test {
private int id;
public Key getKey() {
return createKey(id, this.getClass());
}
public static Key createKey(int id, Class clazz) {
return new Key(clazz, id);
}
private static class Key {
private final Class clazz;
private final int id;
private Key(Class clazz, int id) {
this.clazz = clazz;
this.id = id;
}
private int getId() {
return id;
}
private Class getClazz() {
return clazz;
}
}
public int getId() {
return id;
}
}
I want to get a specific enum based on its field value.
Enum:
public enum CrimeCategory {
ASBO ("Anti Social Behaviour"),
BURG ("Burglary"),
CRIMDAM ("Criminal Damage And Arson"),
DRUGS ("Drugs"),
OTHTHEFT ("Other Theft"),
PUPDISOR ("Public Disorder And Weapons"),
ROBBERY ("Robbery"),
SHOPLIF ("Shoplifting"),
VEHICLE ("Vehicle Crime"),
VIOLENT ("Violent Crime"),
OTHER ("Other Crime");
private String category;
private CrimeCategory (String category) {
this.category = category;
}
public String returnString() {
return category;
}
}
Getting a new Enum:
aStringRecivedFromJson = "Anti Social Behaviour"
CrimeCategory crimeCategoryEnum;
crimeCategoryEnum = CrimeCategory.valueOf(aStringRecivedFromJson);
I have been trying to work out a way for the above bring back a an enum so it can be passed stored in a HashMap with other Crime information.
Expected Result: ASBO
For reference, here is an alternative solution with a HashMap:
enum CrimeCategory {
ASBO("Anti Social Behaviour"),
BURG("Burglary"),
CRIMDAM("Criminal Damage And Arson"),
DRUGS("Drugs"),
OTHTHEFT("Other Theft"),
PUPDISOR("Public Disorder And Weapons"),
ROBBERY("Robbery"),
SHOPLIF("Shoplifting"),
VEHICLE("Vehicle Crime"),
VIOLENT("Violent Crime"),
OTHER("Other Crime");
private static final Map<String, CrimeCategory> map = new HashMap<>(values().length, 1);
static {
for (CrimeCategory c : values()) map.put(c.category, c);
}
private final String category;
private CrimeCategory(String category) {
this.category = category;
}
public static CrimeCategory of(String name) {
CrimeCategory result = map.get(name);
if (result == null) {
throw new IllegalArgumentException("Invalid category name: " + name);
}
return result;
}
}
Add a static method to the CrimeCategory enum:
public static CrimeCategory valueOf(String name) {
for (CrimeCategory category : values()) {
if (category.category.equals(name)) {
return category;
}
}
throw new IllegalArgumentException(name);
}
Static factory methods that return an enum constant based on the value of an instance field take on one of the two forms described in the other answers: a solution based on iterating the enum values, or a solution based on a HashMap.
For enums with a small number of constants, the iterative solution should be as performant as the HashMap solution (which requires calculation of the hash code, matching it to a bucket, and assuming that there will be no hash collisions).
For larger enums, the map-based solution will be more performant (but requires storage space in memory). However, if the factory method is invoked infrequently then the overall performance improvement by using a map could still be immeasurably small.
The overall decision to use an iterative lookup or a map-based lookup for the static factory method will ultimately depend on your requirements and the environment. It is never wrong to start with an iterative lookup and then change to a map-based implementation if profiling shows an actual performance problem.
Lastly, since Java 8, the Streams API enables a pipeline-based solution for mapping (that should have performance similar to the iterative solution). For example, say that you want to create an interface that you could use on any enum class to express your intent that it should be matchable by one of its instance fields. Let's call this interface Matchable. This interface defines a method which returns the instance field on which you want to match (eg. getField()). This interface can also define a static factory method to return a constant from any implementing enum class:
interface Matchable {
Object getField();
public static <E extends Enum<E> & Matchable> E forToken(Class<E> cls, Object token) {
return Stream.of(cls.getEnumConstants())
.filter(e -> e.getField().equals(token))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown token '" +
token + "' for enum " + cls.getName()));
}
}
Now, any enum class that you define that implements Matchable can use the Matchable.forToken() static factory method to find the enum constant whose instance field value matches the supplied parameter.
The generic type declaration E extends Enum<E> & Matchable assures that the type token passed to the method as a parameter will be for an enum class that implements Matchable (otherwise the code won't compile).
Assylias answer is great. Though I would return an Optional from the factory method to let the client deal with the situation when enum could not be found (of course throwing IllegalArgumentException might be better if you use this enum internally and you think that invoking this method with wrong argument will never happen - this is your choice).
And also I would wrap the Map into unmodifiable wrapper to not modify it somewhere by accident inside your enum (the Map is private but someone could modify it later when adding new functionalities - it will at least force to think about it) :
enum CrimeCategory {
ASBO("Anti Social Behaviour"),
BURG("Burglary"),
CRIMDAM("Criminal Damage And Arson"),
DRUGS("Drugs"),
OTHTHEFT("Other Theft"),
PUPDISOR("Public Disorder And Weapons"),
ROBBERY("Robbery"),
SHOPLIF("Shoplifting"),
VEHICLE("Vehicle Crime"),
VIOLENT("Violent Crime"),
OTHER("Other Crime");
private static final Map<String, CrimeCategory> MAP;
static {
Map<String, CrimeCategory> crimeCategoryMap = Arrays.stream(values())
.collect(toMap(cg -> cg.category, e -> e));
MAP = Collections.unmodifiableMap(crimeCategoryMap);
}
private final String category;
private CrimeCategory(String category) {
this.category = category;
}
public static Optional<CrimeCategory> of(final String name) {
return Optional.ofNullable(MAP.get(name));
}
}
I have;
public enum Detailed {
PASSED, INPROCESS, ERROR1, ERROR2, ERROR3;
}
and need to convert it to the following;
public enum Simple {
DONE, RUNNING, ERROR;
}
So first PASSED->DONE and INPROCESS->RUNNING, but all errors should be: ERROR. Obviously it is possible to write cases for all values, but there may be a better solution?
Personally I would just create a Map<Detailed, Simple> and do it explicitly - or even use a switch statement, potentially.
Another alternative would be to pass the mapping into the constructor - you could only do it one way round, of course:
public enum Detailed {
PASSED(Simple.DONE),
INPROCESS(Simple.RUNNING),
ERROR1(Simple.ERROR),
ERROR2(Simple.ERROR),
ERROR3(Simple.ERROR);
private final Simple simple;
private Detailed(Simple simple) {
this.simple = simple;
}
public Simple toSimple() {
return simple;
}
}
(I find this simpler than Ted's approach of using polymorphism, as we're not really trying to provide different behaviour - just a different simple mapping.)
While you could potentially do something cunning with the ordinal value, it would be much less obvious, and take more code - I don't think there'd be any benefit.
One way is to define a method asSimple() in your Detailed enum:
public enum Detailed {
PASSED {
#Override
Simple asSimple() {
return DONE;
}
},
INPROCESS {
#Override
Simple asSimple() {
return RUNNING;
}
},
ERROR1,
ERROR2,
ERROR3;
public Simple asSimple() {
return Simple.ERROR; // default mapping
}
}
You can then simply call the method when you want to do the mapping:
Detailed code = . . .
Simple simpleCode = code.asSimple();
It has the advantage of putting the knowledge of the mapping with the Detailed enum (where perhaps it belongs). It has the disadvantage of having knowledge of Simple mixed in with the code for Detailed. This may or may not be a bad thing, depending on your system architecture.
Use EnumMap
I decouple my external xml interface from my internal domain model by implementing a transformation service. This includes mapping enums from jaxb generated code to domain model enums.
Using a static EnumMap encapsulates the concern of transformation within the class responsible for transformation. Its cohesive.
#Service
public class XmlTransformer {
private static final Map<demo.xml.Sense, Constraint.Sense> xmlSenseToSense;
static {
xmlSenseToSense = new EnumMap<demo.xml.Sense, Constraint.Sense> (
demo.xml.Sense.class);
xmlSenseToSense.put(demo.xml.planningInterval.Sense.EQUALS,
Constraint.Sense.EQUALS);
xmlSenseToSense.put(demo.xml.planningInterval.Sense.GREATER_THAN_OR_EQUALS,
Constraint.Sense.GREATER_THAN_OR_EQUALS);
xmlSenseToSense.put(demo.xml.planningInterval.Sense.LESS_THAN_OR_EQUALS,
Constraint.Sense.LESS_THAN_OR_EQUALS);
}
...
}
Guava's Enums.getIfPresent() on Enum.name()
Our case was a particular specialization of this one. We do have two Enum: one we use in the application and another one we use in the core library. The core library is used by a handful of applications, by different teams. Each application views a subset of the whole functionality. The whole functionality is configured with the enums in order to switch on and off, throttle up or down, select strategies, etc.
So we ended up with:
one enum for the library, containing all the possible configurations, visible from the applications and also some library-specific
one enum for each application, containing the literals corresponding to what the application can see/touch in the library, and some application-specific
Then as we pass data down to the library, we adapt all data and also those configurations. We own all enums, so we can choose to call the same configuration with the same literal in different enums.
Enum LibraryConfig {
FUNCTION_ONE,
FUNCTION_TWO,
FUNCTION_THREE,
FUNCTION_FOUR;
}
Enum Aplication1Config {
FUNCTION_ONE,
FUNCTION_TWO,
FUNCTION_THREE,
APPL1_FUNCTION_ONE,
APPL1_FUNCTION_TWO;
}
Enum Aplication2Config {
FUNCTION_ONE,
FUNCTION_TWO,
FUNCTION_FOUR;
APPL2_FUNCTION_ONE;
}
When we need to convert from one type to another (app --> lib or lib --> app) we use the getIfPresent() method from com.google.common.base.Enums in this way:
Aplication1Config config1App1 = FUNCTION_TWO;
LibraryConfig configLib = Enums.getIfPresent(LibraryConfig.class, config1App1.name()).orNull();
We check configLib for null value to see if there was successful conversion. This last step we use because of the APPX_FUNCTION_YYY, which are application-specific, and for the conversion on the direction lib --> app, not to pass configuration values library-specific (FUNCTION_FOUR in the example).
maven's dependency management:
Just in case anyone needs it:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
Home grown version:
You can make your own conversion using the Enum methods, but you have to take care of the exception to detect when the conversion did not succeed:
try {
Aplication1Config config1App1 = FUNCTION_TWO;
LibraryConfig configLib = LibraryConfig.valueOf(config1App1.name());
} catch (IllegalArgumentException iae) {
// if the conversion did not succeed
}
Ted's answer is very Javaly, but the expression
passed == PASSED ? DONE : ERROR
would do the job, too.
To me that sounds more like a conceptual problem than a programming problem. Why don't you just remove the "Simple" enum type and use the other one instead in all places in the program?
Just to make that more clear with another example: Would you really try to define an enum type for the work days in a week (Monday to Friday) and another enum for all days of a week (Monday to Sunday)?
Here is the simple enum mapper with test:
-- IMPLEMENTATION
-- ENUMS
public enum FirstEnum {
A(0), B(1);
private final int value;
private FirstEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public enum SecondEnum {
C(0), D(1);
private final int valueId;
private SecondEnum(int valueId) {
this.valueId = valueId;
}
public int getValueId() {
return valueId;
}
}
--MAPPER
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.Validate;
import com.google.common.collect.Sets;
public class EnumPropertyMapping {
private final Map<?, ?> firstMap;
private final Map<?, ?> secondMap;
private final Class<?> firstType;
private final Class<?> secondType;
private EnumPropertyMapping(
Map<?, ?> firstMap, Map<?, ?> secondMap, Class<?> firstType, Class<?> secondType) {
this.firstMap = firstMap;
this.secondMap = secondMap;
this.firstType = firstType;
this.secondType = secondType;
}
public static Builder builder() {
return new Builder();
}
#SuppressWarnings("unchecked")
public <R> R getCorrespondingEnum(Object mappedEnum) {
Validate.notNull(mappedEnum, "Enum must not be NULL");
Validate.isInstanceOf(Enum.class, mappedEnum, "Parameter must be an Enum");
if (firstType.equals(mappedEnum.getClass())) {
return (R) firstMap.get(mappedEnum);
}
if (secondType.equals(mappedEnum.getClass())) {
return (R) secondMap.get(mappedEnum);
}
throw new IllegalArgumentException("Didn't found mapping for enum value: " + mappedEnum);
}
public static class Builder {
private final Map<Object, Object> firstEnumMap = new HashMap<>();
private final Map<Object, Object> secondEnumMap = new HashMap<>();
private Class<?> firstEnumType;
private Class<?> secondEnumType;
public <T extends Enum<T>> Builder addFirst(Class<T> enumType, String propertyName) {
firstEnumType = enumType;
initMap(firstEnumMap, enumType.getEnumConstants(), propertyName);
return this;
}
public <T extends Enum<T>> Builder addSecond(Class<T> enumType, String propertyName) {
secondEnumType = enumType;
initMap(secondEnumMap, enumType.getEnumConstants(), propertyName);
return this;
}
private void initMap(Map<Object, Object> enumMap, Object[] enumConstants, String propertyName) {
try {
for (Object constant : enumConstants) {
enumMap.put(PropertyUtils.getProperty(constant, propertyName), constant);
}
} catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
}
public EnumPropertyMapping mapEnums() {
Validate.isTrue(firstEnumMap.size() == secondEnumMap.size());
Validate.isTrue(Sets.difference(firstEnumMap.keySet(), secondEnumMap.keySet()).isEmpty());
Map<Object, Object> mapA = new HashMap<>();
Map<Object, Object> mapB = new HashMap<>();
for (Map.Entry<Object, Object> obj : firstEnumMap.entrySet()) {
Object secondMapVal = secondEnumMap.get(obj.getKey());
mapA.put(obj.getValue(), secondMapVal);
mapB.put(secondMapVal, obj.getValue());
}
return new EnumPropertyMapping(mapA, mapB, firstEnumType, secondEnumType);
}
}
}
-- TEST
import org.junit.Test;
import com.bondarenko.common.utils.lang.enums.FirstEnum;
import com.bondarenko.common.utils.lang.enums.SecondEnum;
import static junit.framework.TestCase.assertEquals;
public class EnumPropertyMappingTest {
#Test
public void testGetMappedEnum() {
EnumPropertyMapping mapping = EnumPropertyMapping.builder()
.addSecond(SecondEnum.class, "valueId")
.addFirst(FirstEnum.class, "value")
.mapEnums();
assertEquals(SecondEnum.D, mapping.getCorrespondingEnum(FirstEnum.B));
assertEquals(FirstEnum.A, mapping.getCorrespondingEnum(SecondEnum.C));
}
}