Can I map between values inside enums? - java

I create enum which has two values: brand name and brand code.
I want to know the brand code by inputting the brand name.
And I also want to know the brand name by inputting the brand code.
Can this problem solved using Enum? or other code is more effective? I want to make the code as shorter as possible
I have created following code to search the code of a brand. If I want to do vice versa, I can create another Hashmap and method to convert the code into a brand. But is that the effective way to solve it?
public enum Brand{
COLA("cola", "CL8935"),
BREAD("bread", "BR2810"),
SNICKERS("snickers", "SN4423");
private static final Map<String, String> BY_BRAND = new HashMap<>();
static {
for (Brand brand : values()){
BY_BRAND.put(brand.code, brand.brand);
}
}
private final String brand;
private final String code;
public static String convertToCode(String brand){
return BY_BRAND.get(brand.toLowerCase()).toString();
}
}

Update - Adding the full enum (with imports)
import java.util.Arrays;
import java.util.function.Function;
enum Brand {
COLA("cola", "CL8935"),
BREAD("bread", "BR2810"),
SNICKERS("snickers", "SN4423");
private final String brand;
private final String code;
Brand(String brand, String code) {
this.brand = brand;
this.code = code;
}
public static Brand findBy(String value, Function<Brand, String> extractor) {
return Arrays.stream(Brand.values())
.filter(brand -> extractor.apply(brand).equalsIgnoreCase(value))
.findFirst()
.orElse("Either a default or throw exception here");
}
public String getBrand() {
return brand;
}
public String getCode() {
return code;
}
}
Original
You could use a static findBy method as an alternative to the map. This would allow you to pass in the value and method reference for the getter which will be used to compare the values stored within the enum.
The difference here would be performance (as maps would be faster), the fact that you would be returning the enum and that you most likely would want either a default enum value or to throw an exception on no matched being found. Below is an example
public static Brand findBy(String value, Function<Brand, String> extractor) {
return Arrays.stream(Brand.values())
.filter(brand -> extractor.apply(brand).equalsIgnoreCase(value))
.findFirst()
.orElse("Either a default or throw exception here");
}
And this can be called like this
public static void main(String[] args) {
Brand brand1 = Brand.findBy("cola", Brand::getBrand);
Brand brand2 = Brand.findBy("BR2810", Brand::getCode);
}

Simple static method in Brand should do:
public static String getBrand(String code) {
for(Brand b : Brand.values()){
if(b.getCode().equals(code)) return b.getBrand();
}
return null;
}
Similarly you can write a getCode(String brand)
Edit: assuming the two attributes do not have the same value, you can check do the bi-di mapping in the same method:
public static String getOtherAttribute(String value) {
for(Brand b : Brand.values()){
if(b.getCode().equals(value)) return b.getBrand();
if(b.getBrand().equals(value)) return b.getCode();
}
return null;
}
If the two attributes may have the same value you can add an argument (flag) to the method's signature to tell which attribute you want to retrieve.

Related

Java nested POJO update based on dot annotation

I have a nested POJO structure defined something like this,
public class Employee {
private String id;
private Personal personal;
private Official official;
}
public class Personal {
private String fName;
private String lName;
private String address;
}
public class Official {
private boolean active;
private Salary salary;
}
public class Salary {
private double hourly;
private double monthly;
private double yearly;
}
I get updates from a service with dot annotaion on what value changed, for ex,
id change --> id=100
address change --> personal.address=123 Main Street
hourly salary change --> official.salary.hourly=100
This POJO structure could be 3-4 level deeps. I need to look for this incoming change value and update the corresponding value in POJO. What's the best way of doing it?
If you would like to create Java objects that allows you to edit fields. You can specify your object fields with the public/default/protected access modifiers. This will enable you to get and set fields such as personal.address or official.salary.hours
This approach is typically frowned upon as the object is no longer encapsulated and any calling methods are welcome to manipulate the object. If these fields are not encapsulated with getters and setters, your object is no longer a POJO.
public provides access from any anywhere.
default provides access from any package
protected provides access from package or subclass.
public class Employee {
public String id;
public Personal personal;
public Official official;
}
public class Personal {
public String fName;
public String lName;
public String address;
}
Here's a quick approach using reflection to set fields dynamically. It surely isn't and can't be clean. If I were you, I would use a scripting engine for that (assuming it's safe to do so).
private static void setValueAt(Object target, String path, String value)
throws Exception {
String[] fields = path.split("\\.");
if (fields.length > 1) {
setValueAt(readField(target, fields[0]),
path.substring(path.indexOf('.') + 1), value);
return;
}
Field f = target.getClass()
.getDeclaredField(path);
f.setAccessible(true);
f.set(target, parse(value, f.getType())); // cast or convert value first
}
//Example code for converting strings to primitives
private static Object parse(String value, Class<?> type) {
if (String.class.equals(type)) {
return value;
} else if (double.class.equals(type) || Double.class.equals(type)) {
return Long.parseLong(value);
} else if (boolean.class.equals(type) || Boolean.class.equals(type)) {
return Boolean.valueOf(value);
}
return value;// ?
}
private static Object readField(Object from, String field) throws Exception {
Field f = from.getClass()
.getDeclaredField(field);
f.setAccessible(true);
return f.get(from);
}
Just be aware that there's a lot to improve in this code (exception handling, null checks, etc.), although it seems to achieve what you're looking for (split your input on = to call setValueAt()):
Employee e = new Employee();
e.setOfficial(new Official());
e.setPersonal(new Personal());
e.getOfficial().setSalary(new Salary());
ObjectMapper mapper = new ObjectMapper();
setValueAt(e, "id", "123");
// {"id":"123","personal":{},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}
setValueAt(e, "personal.address", "123 Main Street");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}
setValueAt(e, "official.salary.hourly", "100");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":100.0,"monthly":0.0,"yearly":0.0}}}

Java enum reverse lookup

So I have this enum that doesn't work as I expected and need some modifications:
public enum MyEnum {
CODE000("text description comes here"),
private final String value;
private static final Map<String, MyEnum> LOOKUP = Maps.uniqueIndex(
Arrays.asList(MyEnum.values()),
MyEnum::getValue
);
MyEnum(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MyEnum fromStatus(String status) {
return LOOKUP.get(status);
}
}
The way it works now is:
MyEnum.fromStatus("text description comes here") and of course I want the other way around:
MyEnum.fromStatus("CODE000") to return me "text description comes here"
Can someone suggest how I can change this ?
What you need is a literal lookup, which you get by calling valueOf:
MyEnum code000 = MyEnum.valueOf("CODE000");
And then:
String val = code000.getValue();
Please note that an exception will be raised if the string passed to valueOf doesn't resolve to an enum literal in MyEnum.
Your key function (MyEnum::getValue) is wrong as it returns the value.
It must be MyEnum::name
This will return the enum and not the text description as the value of the map is of type MyEnum. You can get the text value by calling getValue on the enum OR you can store the value in the map instead of the enum
If you want to get an enum value by enum name you can use this function :
public static String fromStatus(String status) {
MyEnum myEnum = valueOf(status);
return myEnum.getValue();
}
The answers so far are using the method valueOf. This method will return the enum constant as long as you provide a name of an enum constant. Otherwise an IllegalArgumentException will be thrown.
In your question you're using a lookup map. The Map (it looks like as it's created by Guava Maps) will return for non-enum-constant-names null. It will not throw a IllegalArgumentException in such cases. So it is a different behaviour.
In addition you say: "and of course I want the other way around"
This means you want to get the enum by status and the status by an enums name.
Therefore you would need to have two lookup methods:
status -> enum
name -> status
But you would get a compile time error if you define the two methods you mentioned:
public static MyEnum fromStatus(String status) { ... }
public static String fromStatus(String name) { ... }
The compiler could not distinguish the methods by name and parameter. But even though you wrote MyEnum.fromStatus("CODE000") actually it's the enum constant name you are using as parameter. So let's resolve the naming conflict by calling the second method fromName. The code for MyEnum could look like this:
public enum MyEnum {
CODE000("text description comes here");
private final String value;
private static final Map<String, MyEnum> LOOKUP_ENUM = Maps.uniqueIndex(Arrays.asList(MyEnum.values()), MyEnum::getValue);
private static final Map<String, String> LOOKUP_STATUS = Arrays.stream(MyEnum.values()).collect(Collectors.toMap(MyEnum::name, MyEnum::getValue));
MyEnum(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public static MyEnum fromStatus(String status) {
return LOOKUP_ENUM.get(status);
}
public static String fromName(String name) {
return LOOKUP_STATUS.get(name);
}
}
If you want to lookup the enum constants by it's names in the same manner (no exception on non-enum-constant-names), you need a third map and a third lookup method:
private static final Map<String, MyEnum> LOOKUP = Maps.uniqueIndex(Arrays.asList(MyEnum.values()), MyEnum::name);
public static MyEnum byName(String name) {
return LOOKUP.get(name);
}
This would work as follows:
System.out.println(MyEnum.fromStatus("text description comes here")); // CODE000
System.out.println(MyEnum.fromStatus("invalid")); // null - no exception
System.out.println(MyEnum.fromStatus(null)); // null - no exception
System.out.println(MyEnum.fromName("CODE000")); // "text description comes here"
System.out.println(MyEnum.fromName("invalid")); // null - no exception
System.out.println(MyEnum.fromName(null)); // null - no exception
System.out.println(MyEnum.byName("CODE000")); // CODE000
System.out.println(MyEnum.byName("invalid")); // null - no exception
System.out.println(MyEnum.byName(null)); // null - no exception
If you need the byName method I would suggest to rename the methodfromName to something like statusByName to keep them comprehensible apart.
Finally one more suggestion:
Since the lookup methods may return null we could return Optional<String> / Optional<MyEnum> as result. This would allow to immediately continue processing the result.
public static Optional<MyEnum> fromStatus(String status) { ... }
public static Optional<String> statusByName(String name) { ... }
public static Optional<MyEnum> byName(String name) { ... }

error code representation using enum in java

I have a bunch of error codes being returned to me by the server. Based on these error codes I need to write some logic for each error code. I don't want to place the plain error in my function. What is the best way to represent these error codes?
I am using an enum for now,
private enum LoginErrorCode{
EMAIL_OR_PASSWORD_INCORRECT("101"),
EMAIL_INCORRECT("102");
private final String code;
LoginErrorCode(String code){
this.code=code;
}
public String getCode(){
return code;
}
}
But I do not know how to handle it if I get an error code unknown to me. Please let me know.
Here is the solution using your Enum that I typically employ to deal with error codes as you have explained in your scenario:
import java.util.HashMap;
import java.util.Map;
public class EnumSample {
public static enum LoginErrorCode {
EMAIL_OR_PASSWORD_INCORRECT("101"), EMAIL_INCORRECT("102"), UNKNOWN_ERROR_CODE("---");
private static Map<String, LoginErrorCode> codeToEnumMap;
private final String code;
LoginErrorCode(String code) {
this.code = code;
}
public String getCode() {
return code;
}
/**
* Looks up enum based on code. If code was not registered as enum, it returns UNKNOWN_ERROR_CODE
* #param code
* #return
*/
public static LoginErrorCode fromCode(String code) {
// Keep a hashmap of mapping between code and corresponding enum as a cache. We need to initialize it only once
if (codeToEnumMap == null) {
codeToEnumMap = new HashMap<String, EnumSample.LoginErrorCode>();
for (LoginErrorCode aEnum : LoginErrorCode.values()) {
codeToEnumMap.put(aEnum.getCode(), aEnum);
}
}
LoginErrorCode enumForGivenCode = codeToEnumMap.get(code);
if (enumForGivenCode == null) {
enumForGivenCode = UNKNOWN_ERROR_CODE;
}
return enumForGivenCode;
}
}
public static void main(String[] args) {
System.out.println( LoginErrorCode.fromCode("101")); //Prints EMAIL_OR_PASSWORD_INCORRECT
System.out.println( LoginErrorCode.fromCode("102")); //Prints EMAIL_INCORRECT
System.out.println( LoginErrorCode.fromCode("999")); //Prints UNKWNOWN_ERROR_CODE
}
}
The point of an enum is that there are no invalid values; invalid values do not exist. There can't be an LoginErrorCode.EMAIL_ERROR_DOES_NOT_EXIST value. You shouldn't have to deal with a non-existent value. That is what makes an enum the best representation, because you have a known set of values to represent.
EDIT
Since you need to translate the error code strings to your enum, include a Map of the error code Strings to your enum values:
public enum LoginErrorCode
{
EMAIL_OR_PASSWORD_INCORRECT,
EMAIL_INCORRECT;
private static Map<String, LoginErrorCode> map;
// static initializer
static {
map = new HashMap<String, LoginErrorCode>();
map.put("101", EMAIL_OR_PASSWORD_INCORRECT);
map.put("102", EMAIL_INCORRECT);
}
public static LoginErrorCode fromCode(String code)
{
return map.get(code);
}
}
The fromCode method will return null on an invalid code.

return multiple value from one method

I have a class UserFunction and it have two method getAudioFunction and getPromptFunction with returning String value, my problem is that i want to return both value in one method
how can i able to do that
UserFunction.java
public class UserFunction{
Map<String,PromptBean> promptObject=new HashMap<String,PromptBean>();
Map<String,AudioBean> audioObject = new HashMap<String,AudioBean>();
XmlReaderPrompt xrpObject=new XmlReaderPrompt();
public String getAudioFunction(String audioTag,String langMode )
{
Map<String, AudioBean> audioObject=xrpObject.load_audio(langMode);
AudioBean audioBean=(AudioBean)audioObject.get(audioTag);
String av=StringEscapeUtils.escapeXml(audioBean.getAudio());
return av;
}
public String getPromptFunction(String promptTag,String langMode )
{
Map<String, PromptBean> promptObject=xrpObject.load(langMode);
PromptBean promptBean= (PromptBean)promptObject.get(promptTag);
String pv=StringEscapeUtils.escapeXml(promptBean.getPrompt());
return pv;
}
}
You need to return an object which holds both values. You could create a class for this purpose. The class can have two getter methods for retrieving the values.
It is not possible to return more than one value from a method in java. You can set multiple value into Map or List or create a custom class and can return that object.
public Map<String,String> getAudioAndPromptFunction(String audioTag,String langMode )
{
Map<String,String> map =new HashMap();
...
map.put("audioBean",StringEscapeUtils.escapeXml(audioBean.getAudio()));
map.put("promptBean",StringEscapeUtils.escapeXml(promptBean.getPrompt());
return map;
}
or you can create a custom bean class like.
public class AudioPrompt{
private String audioBean;
private String promptBean;
...
}
public AudioPrompt getAudioAndPromptFunction(String audioTag,String langMode )
{
AudioPrompt audioPrompt =new AudioPrompt();
...
audioPrompt.set(StringEscapeUtils.escapeXml(audioBean.getAudio()));
audioPrompt.set(StringEscapeUtils.escapeXml(promptBean.getPrompt());
return audioPrompt ;
}
You'll need to return an object that includes both of the values. This could be an array with two elements, a Pair<A,B> class (which holds two generic values, typically from some pan-project utility library), or a method-specific class such as:
public class UserFunctionXmlPairing {
public final String audioBeanXml;
public final String promptBeanXml;
}
Create a new class that holds your two strings and return that.
class AudioPromptPair {
private String audio;
private String prompt;
public AudioPromptPair(String audio, String prompt) {
this.audio = audio;
this.prompt = prompt;
}
// add getters and setters
}
You can wrap all the values you wish into a single object and return that:
public class Prompts {
private Map<String, Object> prompts = new HashMap<String, Object>();
public void addPrompt(String name, Object prompt) {
this.prompts.put(name, prompt);
}
public Object getPrompt(String name) {
this.prompts.get(name);
}
}
It's even easier if your AudioBean and PromptBean have a common super class or interface.
My preference would be to lose the "Bean" in your class names. AudioPrompt and TextPrompt would be preferred.

Design Issue | Enum to represent combo box options

I need to use an Enum with a combobox (values shown below).
YES (shown as YES on UI, stored in DB as Y)
NO (shown as NO on UI, stored in DB as N)
DEFAULT (shown as "" on UI, stored in DB as null)
The Enum has methods to perform the following -
toString() - to provide the custom String for UI. (showing the combo options)
OptionToDB (static) - Convert a selected option to db value (on save / update)
DBToOption (static)- Convert a DB value to selcted option (while loading the screen)
static enum EnumOption{
YES,NO,DEFAULT;
....
public static EnumOption DBToOption(String val){
if("Y".equals(val)){
return YES;
} else if("N".equals(val)){
return NO;
}else {
return DEFAULT;
}
}
....
}
It works pretty well, but the issue with above methods is that it uses if/else comparison to deduce which option / db value to be returned.
I thought of storing the dbValue as a field in enum but I was not able to reduce the if/else from DBToOption.
Can this if/else be avoided in any way using a better design??
If you store the dbValue as a field in the enum, you can remove the if/else and replace it with a for-loop, although I don't see anything wrong with those if/elses for this particular case:
static enum EnumOption {
YES("Y"),
NO("N"),
DEFAULT("");
private final String value;
private EnumOption(String value) {
this.value = value;
}
public static EnumOption DBToOption(String val) {
for (EnumOption opt : EnumOption.values()) {
if (opt.value.equals(val)) {
return opt;
}
}
return DEFAULT;
}
}
public enum EnumOption {
YES("Y"), NO("N"), DEFAULT("");
private final String value;
private final static Map<String, EnumOption> options;
static {
options = new HashMap<String, EnumOption>();
for (EnumOption opt : EnumOption.values()) {
options.put(opt.value, opt);
}
}
private EnumOption(String value) {
this.value = value;
}
public static EnumOption DBToOption(String val) {
return options.get(val) != null ? options.get(val) : DEFAULT;
}
}
And here is the test that proves it works.
public void testDBToOption() {
assertEquals(EnumOption.NO, EnumOption.DBToOption("N"));
assertEquals(EnumOption.YES, EnumOption.DBToOption("Y"));
assertEquals(EnumOption.DEFAULT, EnumOption.DBToOption(""));
assertEquals(EnumOption.DEFAULT, EnumOption.DBToOption(null));
assertEquals(EnumOption.DEFAULT, EnumOption.DBToOption("R"));
}
So you want to get rid of the remaining if/else ...Are you doing Object Calisthenics?
You could do the following, if you do not have compatibility issues:
public enum EnumOption {
Y("Y", "YES"),
N("N", "NO"),
D("D", "");
private final String dbValue;
private final String uiValue;
private EnumOption(String dbValue, String uiValue) {
this.dbValue = dbValue;
this.uiValue = uiValue;
}
public String getDbValue() {
return this.dbValue;
}
public String uiValue() {
return this.uiValue;
}
public static EnumOption getFromDb(String dbValue) {
return EnumOption.valueOf(dbValue);
}
}
Since each enum value can only occur once, this has at least the same performance as all the other implementations.
For details about the automatically generated valueOf(String) method in enum types, and James DW's solution, you can read up in Josh Bloch's Effective Java Item 30 (Use enums instead of int constants), page 154.

Categories

Resources