error code representation using enum in java - 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.

Related

Can I map between values inside enums?

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.

define error codes in java with inheritance

I want to model some error codes. The classic enum approach
public enum FileError implement FormattedError {
_10 ("some error with parameters [{0}] and [{1}]"),
_20 ("some other error");
private final String description;
private Error(String description) {
this.description = description;
}
public String getDescription(Object... parameters) {
return // logic to format message
}
...
}
it is not good for me because I have many modules, each with it's error codes and I don't want to copy and paste the boilerplate (constructors, getters, logic..) in all these enums.
So I went for a "manual" enum implemented like this
public class FileError extends BaseError {
public final static FileError _10 = new FileError (10, "some message with parameters [{0}] and [{1}]");
public final static FileError _20 = new FileError (20, "some other message");
}
where I can define my logic in BaseError and reuse it.
but it is still bad because there is no way to link the variable name to the number (_10 to 10) and people copy pasting might reuse the same number without noticing. I could add a test to check that via reflection but then how do I enforce people to use that test for their implementations.
so do you guys have a better idea about how I could achieve this ?
[edit] please keep in mind that I don't want to put error codes in properties files because I want the ide to link error codes in the code with their message.
To answer your question of how to check for reused numbers, you can do that simply by using a static set of all numbers registered so far, and check that when a new one is registered it does not yet exist:
public class BaseError {
// ...
private static Set<Integer> registeredNums = new HashSet<>();
public BaseError(int N, String msg) {
synchronized(registeredNums) {
assert(!registeredNums.contains(N)) : "Duplicated error code";
registeredNums.add(N);
}
// ...
}
}
The users will need to have assertions enabled. If you want the check to always happen, you could throw an AssertionError manually.
A combination of both your approaches may be what you are looking for:
enum ErrorCode {
_10(new FileError(10, "some message with parameters [{0}] and [{1}]")),
_20(new FileError(20, "some other message"));
private final FileError error;
private ErrorCode(FileError err) {
error = err;
}
public FileError getError() {
return error;
}
}
With this code there is an explicit link between the error code and the variable. To avoid other people using the same error code you may prevent them from creating their own FileError instances entirely by making the constructor package private. If that is not an option, you may create an additional subclass as follows:
public class UserDefinedFileError extends FileError {
public UserDefinedFileError(int code, String msg){
super(checkCode(code),msg);
}
static int checkCode(int code){
if(code <= 100){ // or check if it exists in a set of used codes
throw new IllegalArgumentException("Error codes lower than 100 are reserved.");
}
}
}
It is necessary to use some boilerplate code but you can keep it at a minimum by making the enum implement an interface and put much of the functionality statically in the interface - assuming you are using Java-7+ of course.
interface Error {
/**
* Keeps track of error ranges - for sanity check when errors are registered.
*/
static final Map<ErrorRange, Set<? extends Error>> errors = new HashMap<>();
/**
* Lookup range.
*/
static final Map<Error, ErrorRange> range = new HashMap<>();
public static <E extends Enum<E> & Error> void register(ErrorRange errorRange, Class<E> theClass) {
// Keep track of all errors - TODO - Make sure each is registered only once.
errors.put(errorRange, EnumSet.allOf(theClass));
// We need the range.
for (Error e : theClass.getEnumConstants()) {
range.put(e, errorRange);
}
}
/**
* Get a formatted string for the error with the provided parameters.
*/
static <E extends Enum<E> & Error> String format(E error, Object... parameters) {
// The error number comes from it's range + its ordinal.
int errNo = range.get(error).range + error.ordinal();
// The string comes from the formatted description.
return errNo + "\t" + String.format(error.getDescription(), parameters);
}
// All Errors must have a description.
public String getDescription();
}
/**
* Register of all error ranges.
*/
enum ErrorRange {
// All File errors start at 10,000
FileError(10_000);
final int range;
private ErrorRange(int range) {
this.range = range;
}
}
public enum FileError implements Error {
ParameterError("some error with parameters [{0}] and [{1}]"),
OtherError("some other error");
//<editor-fold defaultstate="collapsed" desc="Description">
// Start boilerplate
private final String description;
private FileError(String description) {
this.description = description;
}
#Override
public String getDescription() {
return description;
}
// End boilerplate
//</editor-fold>
}
static {
// Statically register me with the Error object.
Error.register(ErrorRange.FileError, FileError.class);
}
Hope you'll get some idea with this:
public enum FileError {
SOME_ERROR1("0", "Error something1"),
SOME_ERROR2("1", "Error something2"),
SOME_ERROR3("2", "Error something3"),
private final String code;
private final String message;
FileError(String code, String message) {
this.code = code;
this.message = message;
}
public String get() {
return new CustomException(code, message).toString();
}
}
And you're CustomException class
public class CustomException {
...
#Override
public String toString() {
return String.format(Locale.getDefault(), "%s, %s", code, message);
}
}

Java: Unable to use EnumSet within an Enumeration : Initialization error : Tech Research Talent Tree example

Error:
...
Caused by: java.lang.ExceptionInInitializerError
...
Caused by: java.lang.ClassCastException:
class com.evopulse.ds2150.TechTrees$BuildingTechTree
not an enum
at java.util.EnumSet.noneOf(Unknown Source)
at java.util.EnumSet.of(Unknown Source)
at com.evopulse.ds2150.TechTrees$BuildingTechTree.<clinit>(TechTrees.java:38)
Here is a snippet of my enumeration
public enum BuildingTechTree {
//Name SoftName Requirements
NONE ("NULL", null),
--> This next line is where it crashes
BARRACKS ("Barracks", EnumSet.of(NONE),
WALLS_SANDBAGS ("Sandbag wall", EnumSet.of(NONE),
POWERPLANT ("Power plant", EnumSet.of(BARRACKS)),
GUARDTOWER ("Guard Tower", EnumSet.of(BARRACKS));
Replacing EnumSet.of(NONE) and EnumSet.of(BARRACKS) with null, lets initialization work, but breaks my code, due to missing data structure... obviously, but I did it to test the rest of my code wasn't somehow the cause.
Removing EnumSet.of(NONE) and replacing with just NONE, and the same for BARRACKS, and changing all related variables, constructor, and methods, that didn't work either... (and even couldn't use the contains.all, since is wasn't "applicable to my changed variable"... )
I extended this example, using the second implementation:
https://gamedev.stackexchange.com/a/25652/48573
I also tried retracing my steps by copying the example verbatim. added
private static Set<BuildingTechTree> techsKnown;
techsKnown = (BuildingTechTree.BIODOME);
test = TechTrees.researchTech(techsKnown);
to another class to be called from for testing initialization. and had to change
public boolean researchTech(BuildingTechTree tech) {
to static
This resulted in the same "in not an enum" error. I don't have any rep, to comment on his answer to point out the initialization error...
Added info for both current answers, as both solutions cause the same new error:
public class TechTrees {
private static Set<BuildingTechTree> techsKnown;
public TechTrees() {
techsKnown = EnumSet.of(BuildingTechTree.NONE); //Using this
techsKnown = EnumSet.noneOf(BuildingTechTree.class); //Or this
}
public static boolean researchTech(BuildingTechTree tech) {
if (techsKnown.containsAll(tech.requirements)) { //Causes null pointer
return true; //exception # techsKnown
}
return false;
}
Your declaration structure is so clever it's a shame it doesn't work. But EnumSet apparently needs the enum to be fully initialized first. It tries to fetch the array of constants from the enum so that, among other things, it knows how much space is needed for its internal bitset.
Here's one workaround. It uses a helper method that creates an ordinary set (HashSet) first, and then, in a static initialization block, it iterates the enum constants and replaces all the sets with EnumSets.
public enum BuildingTechTree {
// Named constants
//Name SoftName Requirements
NONE ("NULL", null),
BARRACKS ("Barracks", setOf(NONE)),
WALLS_SANDBAGS ("Sandbag wall", setOf(NONE)),
POWERPLANT ("Power plant", setOf(BARRACKS)),
GUARDTOWER ("Guard Tower", setOf(BARRACKS));
private final String softName;
private Set<BuildingTechTree> requirements;
private BuildingTechTree(String softName, Set<BuildingTechTree> requirements) {
this.softName = softName;
this.requirements = requirements;
}
private static Set<BuildingTechTree> setOf(BuildingTechTree... values) {
return new HashSet<>(Arrays.asList(values));
}
static {
for (BuildingTechTree v : values()) {
if (v.requirements == null) {
v.requirements = EnumSet.noneOf(BuildingTechTree.class);
} else {
v.requirements = EnumSet.copyOf(v.requirements);
}
}
}
}
You have a chicken and egg problem. You could refactor your enum to something like this:
public enum BuildingTechTree {
NONE("NULL"),
BARRACKS("Barracks"),
WALLS_SANDBAGS("Sandbag wall"),
POWERPLANT("Power plant"),
GUARDTOWER("Guard Tower");
static {
NONE.trees = EnumSet.noneOf(BuildingTechTree.class);
BARRACKS.trees = EnumSet.of(NONE);
WALLS_SANDBAGS.trees = EnumSet.of(NONE);
POWERPLANT.trees = EnumSet.of(BARRACKS);
GUARDTOWER.trees = EnumSet.of(BARRACKS);
}
private String name;
private Set<BuildingTechTree> trees;
private BuildingTechTree(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Set<BuildingTechTree> getTrees() {
return Collections.unmodifiableSet(trees);
}
}
EDIT:
regarding your second problem: you're accessing a static variable, from a static method. But this variable is initialized when the constructor of the class has been called (which is a huge design problem). Don't use non-final static fields. And don't initialize static fields from instance methods or constructors. That doesn't make sense. You don't set the color that all cars should have when constructing a car. Initialize your static fields statically:
public class TechTrees {
private static final Set<BuildingTechTree> TECHS_KNOWN =
EnumSet.of(BuildingTechTree.NONE);
public static boolean researchTech(BuildingTechTree tech) {
return TECHS_KNOWN.containsAll(tech.requirements));
}
}

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.

Get enum by its inner field

Have enum with inner fields, kind of map.
Now I need to get enum by its inner field.
Wrote this:
package test;
/**
* Test enum to test enum =)
*/
public enum TestEnum {
ONE(1), TWO(2), THREE(3);
private int number;
TestEnum(int number) {
this.number = number;
}
public TestEnum findByKey(int i) {
TestEnum[] testEnums = TestEnum.values();
for (TestEnum testEnum : testEnums) {
if (testEnum.number == i) {
return testEnum;
}
}
return null;
}
}
But it's not very efficient to look up through all enums each time I need to find appropriate instance.
Is there any other way to do the same?
You can use a static Map<Integer,TestEnum> with a static initializer that populates it with the TestEnum values keyed by their number fields.
Note that findByKey has been made static, and number has also been made final.
import java.util.*;
public enum TestEnum {
ONE(1), TWO(2), SIXTY_NINE(69);
private final int number;
TestEnum(int number) {
this.number = number;
}
private static final Map<Integer,TestEnum> map;
static {
map = new HashMap<Integer,TestEnum>();
for (TestEnum v : TestEnum.values()) {
map.put(v.number, v);
}
}
public static TestEnum findByKey(int i) {
return map.get(i);
}
public static void main(String[] args) {
System.out.println(TestEnum.findByKey(69)); // prints "SIXTY_NINE"
System.out.println(
TestEnum.values() == TestEnum.values()
); // prints "false"
}
}
You can now expect findByKey to be a O(1) operation.
References
JLS 8.7 Static initializers
JLS 8.9 Enums
Related questions
Static initalizer in Java
How to Initialise a static Map in Java
Note on values()
The second println statement in the main method is revealing: values() returns a newly allocated array with every invokation! The original O(N) solution could do a little better by only calling values() once and caching the array, but that solution would still be O(N) on average.
Although someone has suggested using Map<Integer, TestEnum> think twice about it.
Your original solution, especially for small enums, may be magnitudes faster than using HashMap.
HashMap will probably be not faster until your enum contains at least 30 to 40 elements.
This is one case of "If it ain't broken, don't fix it".
Here is the most convenient way to find enum value by its field:
public enum TestEnum {
A("EXAMPLE_1", "Qwerty", 1),
B("EXAMPLE_2", "Asdfgh", 2),
C("EXAMPLE_3", "Zxcvbn", 3);
private final String code;
private final String name;
private final Integer typeID;
TestEnum(String code, String name, Integer typeID) {
this.code = code;
this.name = name;
this.key = typeID;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public Integer getKey() {
return key;
}
public static TestEnum findValueByTypeId(Integer key) {
return Arrays.stream(TestEnum.values()).filter(v ->
v.getKey().equals(key)).findFirst().orElseThrow(() ->
new Exception(String.format("Unknown TestEnum.key: '%s'", key)));
}
}
You should have a HashMap with the numbers as keys and the enum values as values.
This map can typically be in your repository. Then you can easily replace an int variable from the database with your preferred enum value.
If your keys (int values) are stored in a database, then I will say its bad design to carry those keys around in an enum on your business layer. If that's the case, I will recommend not to store the int value in the enum.
One solution is to add
public final Test[] TESTS = { null, ONE, TWO, THREE };
public static Test getByNumber(int i) {
return TESTS[i];
}
To the enum.
If the internal data is not an integer, you could have a Map which you populate in a static { ... } initializer. This map could later be used in the getByNumber method above.

Categories

Resources