I have this code:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger();
public static void main (String[] args) {
logger.info("Text: {}", getText());
}
static String getText() {
// Expensive action using IO
return "";
}
}
In my log4j2.json, the logger is set to ERROR.
I want that when it is not needed, getText() is not called at all. For that, I used the message API and I wrote the following instead:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.Message;
public class Test {
private static final Logger logger = LogManager.getLogger();
public static void main (String[] args) {
logger.info(new TextMessage());
}
static String getText() {
// Expensive action using IO
return "";
}
static class TextMessage implements Message {
#Override public String getFormat() { return null; }
#Override public String getFormattedMessage() {
return String.format("text: %s", getText());
}
#Override public Object[] getParameters() { return null; }
#Override public Throwable getThrowable() { return null; }
}
}
I have two issues with this code.
I cannot use the {} usually used for the logging.
It is extremely verbose.
I checked in the javadoc for any Message that I could use in Java 8's lambda expressions (meaning, only one abstract method), but there is none.
I've thought about creating a ToStringable interface to be used as the following.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger();
public static void main (String[] args) {
logger.info("Text: {}", new ToStringable() { public String toString() { return getText();}});
}
static String getText() {
// Expensive action using IO
return "";
}
}
interface ToStringable { #Override String toString(); }
This gets the job done, but it's barely readable and I cannot use Java 8's lambda on this (see below, note this code fails to compile).
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static Logger logger = LogManager.getLogger();
public static void main (String[] args) {
logger.info("Text: {}", () -> getText());
}
static String getText() {
// Expensive action using IO
return "";
}
}
interface ToStringable {#Override String toString(); }
Finally, only the good old if (logger.isXxxxEnabled()) { logger.xxxx(...) } solved the issue reliably, but what's the point of using a modern, lazy logger?
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Test {
private static final Logger logger = LogManager.getLogger();
public static void main (String[] args) {
if (logger.isInfoEnabled()) {
logger.info("Text: {}", getText());
}
}
static String getText() {
// Expensive action using IO
return "";
}
}
So did anyone encounter this issue and if yes, how was it solved?
Final words: the 3 first code snippets are short, self-contained, correct examples. This means that they're only here to show the problem. Don't think outside of the problem please (meaning: don't tell me to be pragmatic and just let go).
There are two issues with your ToStringable approach.
When you converted the inner class to a lambda expression you removed any hint for the compiler that your lambda expression ought to implement ToStringable. The receiver type is Object (or well, whatever the compiler might attempt when trying one of the overloaded methods of the log4j API). The lambda expression needs a target type, in other words, if you don’t assign it to an appropriately typed variable and the parameter type isn’t suitable, you have to insert a type cast like (ToStringable) ()->""
But this doesn’t help here as you are attempting to override a method declared in java.lang.Object. Lambda expressions can’t override methods inherited from java.lang.Object ¹.
In order to solve this you need a helper method using the good old inner class approach for overriding Object.toString() delegating to another interface which can be implemented via lambda expression:
import java.util.function.Supplier;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4jTest {
private static final Logger logger = LogManager.getLogger();
public static void main (String[] args) {
logger.info("Text: {}", lazy( ()->getText() ));
logger.error("Text: {}", lazy(Log4jTest::getText));
}
// reusable helper method
static Object lazy(Supplier<String> s) {
return new Object() {
#Override
public String toString() {
return s.get();
}
};
}
static String getText() {
System.out.println("very long computation");
return "";
}
}
This example shows both, using a lambda expression and a method reference but of course, the difference in behavior stems from the different logging level. As long as log4j doesn’t offer a functional interface input like the other API, the helper method with the one inner class is unavoidable. But you need to implement it only once while all callers can use lambda expressions.
UPDATE (2015 Aug 5)
Consider voting for this ticket LOG4J2-599 to bring support for lambda expressions to Log4j 2.
UPDATE (2015 Aug 10)
The next version of Log4J, 2.4, will have lambda support. Example usage:
// log message is not constructed if DEBUG level is not enabled
logger.debug("Result of some expensive operation is {}", () -> someLongRunningOperation());
The question has already been answered, this is just some extra advice in case you are planning to use this with async loggers or an async appender:
You mention that creating the message result is an expensive operation. I assume that you want to use async loggers/appenders to improve your app's throughput and/or response time. Makes sense. Be aware that if creating the message result is really expensive then (depending on how much you are logging) your queue may fill up; once that happens you are essentially logging synchronously again. You can tune the queue size to help you deal with bursts, but if the sustained logging rate is very high you may run into this problem. Log4j2 includes JMX MBeans (1, 2) you can use to query how full the queue is.
The output log result will reflect the value of the message at the time the background I/O thread called Message.getFormattedMessage, not the value of the message at the time your application thread called Logger.info. If many lazy messages are logged, the time gap between these two events may grow. Also be aware that your message object needs to be thread-safe. You probably know this already, but I thought it was worth pointing out.
I hope this is useful.
Related
I have a simple scenario in which am trying to verify some behavior when a method is called (i.e. that a certain method was called with given parameter, a function pointer in this scenario). Below are my classes:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
AppBootStrapper bootStrapper = context.getBean(AppBootStrapper.class);
bootStrapper.start();
}
}
#Component
public class AppBootStrapper {
private NetworkScanner networkScanner;
private PacketConsumer packetConsumer;
public AppBootStrapper(NetworkScanner networkScanner, PacketConsumer packetConsumer) {
this.networkScanner = networkScanner;
this.packetConsumer = packetConsumer;
}
public void start() {
networkScanner.addConsumer(packetConsumer::consumePacket);
networkScanner.startScan();
}
}
#Component
public class NetworkScanner {
private List<Consumer<String>> consumers = new ArrayList<>();
public void startScan(){
Executors.newSingleThreadExecutor().submit(() -> {
while(true) {
// do some scanning and get/parse packets
consumers.forEach(consumer -> consumer.accept("Package Data"));
}
});
}
public void addConsumer(Consumer<String> consumer) {
this.consumers.add(consumer);
}
}
#Component
public class PacketConsumer {
public void consumePacket(String packet) {
System.out.println("Packet received: " + packet);
}
}
#RunWith(JUnit4.class)
public class AppBootStrapperTest {
#Test
public void start() throws Exception {
NetworkScanner networkScanner = mock(NetworkScanner.class);
PacketConsumer packetConsumer = mock(PacketConsumer.class);
AppBootStrapper appBootStrapper = new AppBootStrapper(networkScanner, packetConsumer);
appBootStrapper.start();
verify(networkScanner).addConsumer(packetConsumer::consumePacket);
verify(networkScanner, times(1)).startScan();
}
}
I want to verify that bootStrapper did in fact do proper setup by registering the packet consumer(there might be other consumers registered later on, but this one is mandatory) and then called startScan. I get the following error message when I execute the test case:
Argument(s) are different! Wanted:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapperTest$$Lambda$8/438123546#282308c3
);
-> at com.spring.starter.AppBootStrapperTest.start(AppBootStrapperTest.java:24)
Actual invocation has different arguments:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapper$$Lambda$7/920446957#5dda14d0
);
-> at com.spring.starter.AppBootStrapper.start(AppBootStrapper.java:12)
From the exception, clearly the function pointers aren't the same.
Am I approaching this the right way? Is there something basic I am missing? I played around and had a consumer injected into PacketConsumer just to see if it made a different and that was OK, but I know that's certainly not the right way to go.
Any help, perspectives on this would be greatly appreciated.
Java doesn't have any concept of "function pointers"; when you see:
networkScanner.addConsumer(packetConsumer::consumePacket);
What Java actually compiles is (the equivalent of):
networkScanner.addConsumer(new Consumer<String>() {
#Override void accept(String packet) {
packetConsumer.consumePacket(packet);
}
});
This anonymous inner class happens to be called AppBootStrapper$$Lambda$7. Because it doesn't (and shouldn't) define an equals method, it will never be equal to the anonymous inner class that the compiler generates in your test, which happens to be called AppBootStrapperTest$$Lambda$8. This is regardless of the fact that the method bodies are the same, and are built in the same way from the same method reference.
If you generate the Consumer explicitly in your test and save it as a static final Consumer<String> field, then you can pass that reference in the test and compare it; at that point, reference equality should hold. This should work with a lambda expression or method reference just fine.
A more apt test would probably verify(packetConsumer, atLeastOnce()).consumePacket(...), as the contents of the lambda are an implementation detail and you're really more concerned about how your component collaborates with other components. The abstraction here should be at the consumePacket level, not at the addConsumer level.
See the comments and answer on this SO question.
I'm essentially asking the same as this old question, but for Java 14 instead of Java 8. To spare answerers the trouble of navigating to the old question, I'll rephrase it here.
I want to get the name of a function from a referenced method. The following Java code should give you the idea:
public class Main
{
public static void main(String[] args)
{
printMethodName(Main::main);
}
private static void printMethodName(Consumer<String[]> theFunc)
{
String funcName = // somehow get name from theFunc
System.out.println(funcName)
}
}
The equivalent in C# would be:
public class Main
{
public static void Main()
{
var method = Main.Main;
PrintMethodName(method)
}
private static void PrintMethodName(Action action)
{
Console.WriteLine(action.GetMethodInfo().Name);
}
}
According to the accepted answer of the old question, this was not possible in Java 8 without considerable work, such as this solution. Is there a more elegant solution in Java 14?
Getting a method info from a method reference never was a goal on the JDK developer’s side, so no effort was made to change the situation.
However, the approach shown in your link can be simplified. Instead of serializing the information, patching the serialized data, and restoring the information using a replacement object, you can simply intercept the original SerializedLambda object while serializing.
E.g.
public class GetSerializedLambda extends ObjectOutputStream {
public static void main(String[] args) { // example case
var lambda = (Consumer<String[]>&Serializable)GetSerializedLambda::main;
SerializedLambda sl = GetSerializedLambda.get(lambda);
System.out.println(sl.getImplClass() + " " + sl.getImplMethodName());
}
private SerializedLambda info;
GetSerializedLambda() throws IOException {
super(OutputStream.nullOutputStream());
super.enableReplaceObject(true);
}
#Override protected Object replaceObject(Object obj) throws IOException {
if(obj instanceof SerializedLambda) {
info = (SerializedLambda)obj;
obj = null;
}
return obj;
}
public static SerializedLambda get(Object obj) {
try {
GetSerializedLambda getter = new GetSerializedLambda();
getter.writeObject(obj);
return getter.info;
} catch(IOException ex) {
throw new IllegalArgumentException("not a serializable lambda", ex);
}
}
}
which will print GetSerializedLambda main. The only newer feature used here, is the OutputStream.nullOutputStream() to drop the written information immediately. Prior to JDK 11, you could write into a ByteArrayOutputStream and drop the information after the operation which is only slightly less efficient. The example also using var, but this is irrelevant to the actual operation of getting the method information.
The limitations are the same as in JDK 8. It requires a serializable method reference. Further, there is no guaranty that the implementation will map to a method directly. E.g., if you change the example’s declaration to public static void main(String... args), it will print something like lambda$1 when being compiled with Eclipse. When also changing the next line to var lambda = (Consumer<String>&Serializable)GetSerializedLambda::main;, the code will always print a synthetic method name, as using a helper method is unavoidable. But in case of javac, the name is rather something like lambda$main$f23f6912$1 instead of Eclipse’s lambda$1.
In other words, you can expect encountering surprising implementation details. Do not write applications relying on the availability of such information.
A user enters a code and the type of that code is determined by regular expressions. There are many different type of codes, such as EAN, ISBN, ISSN and so on. After the type is detected, a custom query has to be created for the code. I thought it might be a good idea to create a strategy for type, but with time it feels wrong.
public interface SearchQueryStrategie {
SearchQuery createSearchQuery(String code);
}
-
public class IssnSearchQueryStrategie implements SearchQueryStrategie {
#Override
public SearchQuery createSearchQuery(final String code) {
// Create search query for issn number
}
}
-
public class IsbnSearchQueryStrategie implements SearchQueryStrategie {
#Override
public SearchQuery createSearchQuery(final String code) {
// Create search query for ISBN number
}
}
-
public class EanShortNumberSearchQueryStrategie implements SearchQueryStrategie {
#Override
public SearchQuery createSearchQuery(final String code) {
// Create search query for ean short number
}
}
-
public class TestApplication {
public static void main(final String... args) {
final String code = "1144875X";
SearchQueryStrategie searchQueryStrategie = null;
if (isIssn(code)) {
searchQueryStrategie = new IssnSearchQueryStrategie();
} else if (isIsbn(code)) {
searchQueryStrategie = new IsbnSearchQueryStrategie();
} else if (isEan(code)) {
searchQueryStrategie = new EanShortNumberSearchQueryStrategie();
}
if (searchQueryStrategie != null) {
performSearch(searchQueryStrategie.createSearchQuery(code));
}
}
private SearchResult performSearch(final SearchQuery searchQuery) {
// perform search
}
// ...
}
I have to say that there are many more strategies. How should I dispatch the code to the right strategy?
My second approach was to put a boolean method into every strategy to decide if the code is correct for that strategy.
public class TestApplication {
final SearchQueryStrategie[] searchQueryStrategies = {new IssnSearchQueryStrategie(), new IsbnSearchQueryStrategie(),
new EanShortNumberSearchQueryStrategie()};
public static void main(final String... args) {
final String code = "1144875X";
for (final SearchQueryStrategie searchQueryStrategie : searchQueryStrategie) {
if (searchQueryStrategie.isRightCode(code)) {
searchQueryStrategie.createSearchQuery(code);
break;
}
}
}
private SearchResult performSearch(final SearchQuery searchQuery) {
// perform search
}
// ...
}
How would you solve this problem? Is the strategy pattern the right one for my purposes?
If you are using Java 8 and you can profit from the functional features I think one Enum will be sufficient.
You can avoid using if/else statements by mapping each type of code with a Function that will return the query that needs to be executed:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
public enum CodeType
{
EAN("1|2|3"),
ISBN("4|5|6"),
ISSN("7|8|9");
String regex;
Pattern pattern;
CodeType(String regex)
{
this.regex = regex;
this.pattern = Pattern.compile(regex);
}
private static Map<CodeType, Function<String, String>> QUERIES =
new HashMap<>();
static
{
QUERIES.put(EAN, (String code) -> String.format("Select %s from EAN", code));
QUERIES.put(ISBN, (String code) -> String.format("Select %s from ISBB", code));
QUERIES.put(ISSN, (String code) -> String.format("Select %s from ISSN", code));
}
private static CodeType evalType(String code)
{
for(CodeType codeType : CodeType.values())
{
if (codeType.pattern.matcher(code).matches())
return codeType;
}
// TODO DON'T FORGET ABOUT THIS NULL HERE
return null;
}
public static String getSelect(String code)
{
Function<String, String> function = QUERIES.get(evalType(code));
return function.apply(code);
}
}
And in the main you can test your query:
public class Main
{
public static void main(String... args)
{
System.out.println(CodeType.getSelect("1"));
// System.out: Select 1 from EAN
System.out.println(CodeType.getSelect("4"));
// System.out: Select 4 from ISBB
System.out.println(CodeType.getSelect("9"));
// System.out: Select 9 from ISSN
}
}
I usually tend to keep the code as compact as possible.
Some people dislike enums, so I believe you can use a normal class instead.
You can engineer further the way you obtain the QUERIES (selects), so instead of having String templates you can have a Runnable there.
If you don't want to use the the functional aspects of Java 8 you can use Strategy objects that are associated with each type of code:
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
public enum CodeType2
{
EAN("1|2|3", new StrategyEAN()),
ISBN("4|5|6", new StrategyISBN()),
ISSN("7|8|9", new StrategyISSN());
String regex;
Pattern pattern;
Strategy strategy;
CodeType2(String regex, Strategy strategy)
{
this.regex = regex;
this.pattern = Pattern.compile(regex);
this.strategy = strategy;
}
private static CodeType2 evalType(String code)
{
for(CodeType2 codeType2 : CodeType2.values())
{
if (codeType2.pattern.matcher(code).matches())
return codeType2;
}
// TODO DON'T FORGET ABOUT THIS NULL HERE
return null;
}
public static void doQuery(String code)
{
evalType(code).strategy.doQuery(code);
}
}
interface Strategy { void doQuery(String code); }
class StrategyEAN implements Strategy {
#Override
public void doQuery(String code)
{
System.out.println("EAN-" + code);
}
}
class StrategyISBN implements Strategy
{
#Override
public void doQuery(String code)
{
System.out.println("ISBN-" + code);
}
}
class StrategyISSN implements Strategy
{
#Override
public void doQuery(String code)
{
System.out.println("ISSN-" + code);
}
}
And the main method will look like this:
public class Main
{
public static void main(String... args)
{
CodeType2.doQuery("1");
CodeType2.doQuery("4");
CodeType2.doQuery("9");
}
}
So, The strategy pattern is indeed the right choice here, but strategy by itself is not enough. You have several options:
Use a Factory with simple if/else or switch. It's ugly, error prone to extend with new strategies, but is simple and quick to implement.
Use a registry. During the application initialization phase you can register in a registry each SearchQueryStratgeyFactory with the right code. For instance if you use a simple Map you can just do :
strategyRegistry.put("isbn", new IsbnSearchStrategyFactory());
strategyRegistry.put("ean", new EanSearchStrategyFactory());
.... and so on
Then when you need to get the right strategy you just get() the strategy factory from the map using the code id. This approach is better if you have a lot of strategies, but it requires an aditional iitialization step during the application startup.
Use a service locator. ServiceLocator is a pattern that enables the dynamic lookup of implementations. Java comes with an implementation of the ServiceLocator pattern -> the infamous ServiceLoader class. This is my favourite approach because it allows for complete decoupling of the consumer and implementation. Also using the service locator you can easily add new strategies without having to modify the existing code. I won't explain how to use the ServiceLoader - there is plenty of information online. I'll just mention that using the service locator you'll need to implement a "can process such codes ?" logic in each strategy factory. For instance if the factory cannot create a strategy for "isbn" then return null and try with the next factory.
Also note that in all cases you work with factories that produce the strategy implementations.
PS: It's strategy not strategie :)
Your approach is not the Strategy Pattern. Strategy Pattern is all about customizing behavior of an object (Context in terms of this pattern) by passing alternative Strategy object to it. By this way, we don't need to modify the source code of the Context class but still can customize the behavior of objects instanced from it.
Your problem is somewhat related to the Chain of Responsibility (CoR) Pattern where you have a request (your code) and need to figure out which SearchQueryStrategie in a predefined list should handle the request.
The second approach -- using array -- that you mentioned is fine. However, to make it usable in production code, you must have another object -- let's say Manager -- that manages the array and is responsible to find the relevant element for each request. So your client code have to depend on two objects: the Manager and the result SearchQueryStrategie. As you can see, the source code of Manager class tend to be changed frequently because new implementations of SearchQueryStrategie may come. This might make your client annoyed.
That's why the CoR Pattern uses the linked list mechanism instead of array. Each SearchQueryStrategie object A would hold a reference to a next SearchQueryStrategie B. If A cannot handle the request, it will delegate to B (it can even decorate the request before delegating). Of course, somewhere still must know all kinds of strategies and create a linked list of SearchQueryStrategie, but your client will then depend only on a SearchQueryStrategie object (the head one of the list).
Here is the code example:
class SearchQueryConsumer {
public void consume(SearchQuery sq) {
// ...
}
}
abstract class SearchQueryHandler {
protected SearchQueryHandler next = null;
public void setNext(SearchQueryHandler next) { this.next = next; }
public abstract void handle(String code, SearchQueryConsumer consumer);
}
class IssnSearchQueryHandler extends SearchQueryHandler {
#Override
public void handle(String code, SearchQueryConsumer consumer) {
if (issn(code)) {
consumer.consume(/* create a SearchQuery */);
} else if (next != null) {
next.handle(code, consumer);
}
}
private boolean issn(String code) { ... }
}
What i recommend is using the Factory pattern. It describes and handles your scenario better.
Factory Pattern
You can design in the following way (using concepts of factory DP and polymorphism):
Code as interface.
ISSNCode, ISBNCode and EANCode as concrete classes
implementing Code interface, having single-arg constructor taking text as String.
Code has method getInstanceOfCodeType(String text) which returns an instance of a sub-class of Code (decided by checking the type of text passed to it). Let's say the returned value be code
Class SearchQueryStrategieFactory with
getSearchQueryStrategie(code) method. It consumes the returned value from step 3, and generates different
instances of SearchQueryStrategie subclasses based on code type using new operator and, then returns the same.
So, you need to call two methods getInstanceOfCodeType(text) and getSearchQueryStrategie(code) from anywhere.
Instead of implicitly implementing the factory inside main, keep the whole factory code separate, to make it easily maintainable and extensible .
I have enum say ErrorCodes that
public enum ErrorCodes {
INVALID_LOGIN(100),
INVALID_PASSWORD(101),
SESSION_EXPIRED(102) ...;
private int errorCode;
private ErrorCodes(int error){
this.errorCode = error;
} //setter and getter and other codes
}
now I check my exception error codes with this error codes. I don't want to write if this do this, if this do this. How I can solve this problem (writing 10+ if blocks)
Is there any design patter to that situation ?
Thanks
Either you do it with a if-statement or a switch, or you just implement the logic in question into the ErrorCode somehow.
In an OO fashion it all depends on how you want the application or system react to the error code. Lets say you just want it to output somekind of dialog:
public doSomethingWithError() {
ErrorCodes e = getError();
// the source of error, or originator, returns the enum
switch(e) {
case ErrorCodes.INVALID_LOGIN:
prompt('Invalid Login');
case ErrorCodes.INVALID_PASSWORD:
prompt('Invalid password');
// and so on
}
}
We could instead create an ErrorHandler class that does this instead:
// We'll implement this using OO instead
public doSomethingWithError() {
ErrorHandler e = getError();
// the originator now returns an ErrorHandler object instead
e.handleMessage();
}
// We will need the following abstract class:
public abstract class ErrorHandler {
// Lets say we have a prompter class that prompts the message
private Prompter prompter = new Prompter();
public final void handleMessage() {
String message = this.getMessage();
prompter.prompt(message);
}
// This needs to be implemented in subclasses because
// handleMessage() method is using it.
public abstract String getMessage();
}
// And you'll have the following implementations, e.g.
// for invalid logins:
public final class InvalidLoginHandler() {
public final String getMessage() {
return "Invalid login";
}
}
// E.g. for invalid password:
public final class InvalidPasswordHandler() {
public final String getMessage() {
return "Invalid password";
}
}
The former solution is easy to implement, but becomes difficult to maintain as the code grows larger. The latter solution is more complex, (aka. Template Method pattern following the Open-Closed Principle) but enables you to add more methods into the ErrorHandler when you need it (such as restoring resources or whatever). You can also implement this with the Strategy pattern.
You won't get away completely with the conditional statements, but in the latter the conditional is pushed to the part of the code where the error is originated. That way you won't have double maintenance on conditional statements both at the originator and the error handling code.
EDIT:
See this answer by Michael Borgwardt and this answer by oksayt for how to implement methods on Java Enums if you want to do that instead.
Java enums are very powerful and allow per-instance method implementations:
public enum ErrorCode {
INVALID_LOGIN {
public void handleError() {
// do something
}
},
INVALID_PASSWORD {
public void handleError() {
// do something else
}
},
SESSION_EXPIRED {
public void handleError() {
// do something else again
}
};
public abstract void handleError();
}
Then you can simply call errorCode.handleError();. However, it is questionable whether an ErrorCode enum is really the right place for that logic.
As pointed out by Spoike, using polymorphism to pick the right error handling method is an option. This approach basically defers the 10+ if blocks to the JVM's virtual method lookup, by defining a class hierarchy.
But before going for a full-blown class hierarchy, also consider using enum methods. This option works well if what you plan to do in each case is fairly similar.
For example, if you want to return a different error message for each ErrorCode, you can simply do this:
// Note singular name for enum
public enum ErrorCode {
INVALID_LOGIN(100, "Your login is invalid"),
INVALID_PASSWORD(101, "Your password is invalid"),
SESSION_EXPIRED(102, "Your session has expired");
private final int code;
private final String
private ErrorCode(int code, String message){
this.code = code;
this.message = message;
}
public String getMessage() {
return message;
}
}
Then your error handling code becomes just:
ErrorCode errorCode = getErrorCode();
prompt(errorCode.getMessage());
One drawback of this approach is that if you want to add additional cases, you'll need to modify the enum itself, whereas with a class hierarchy you can add new cases without modifying existing code.
I believe the best you can do is implementing the strategy pattern. This way you won't have to change existing classes when adding new enums but will still be able to extend them. (Open-Closed-Principle).
Search for Strategy Pattern and Open Closed Principle.
You can create a map of error codes(Integer) against enum types
Edit
In this solution, once the map is prepared, you can look up an error code in the map and thus will not require if..else look ups.
E.g.
Map<Integer, ErrorCodes> errorMap = new HashMap<Integer, ErrorCodes>();
for (ErrorCodes error : ErrorCodes.values()) {
errorMap.put(error.getCode(), error);
}
Now when you want to check an error code coming from your aplpication, all you need to do is,
ErrorCodes error = errorMap.get(erro_code_from_application);
Thus removing the need for all the if..else.
You just need to set up the map in a way that adding error codes doesn't require changes in other code. Preparation of the map is one time activity and can be linked to a database, property file etc during the initialization of your application
In my opinion there is nothing wrong with ErrorCodes as enums and a switch statement to dispatch error handling. Enums and switch fit together really well.
However, maybe you find the following insteresting (kind of over-design), see an Example
or "Double dispatching" on Wikipedia.
Assumed requirements:
Error-handling should be encapsulated in an own class
Error-handling should be replacable
Type safety: Whenever an error is added, you are forced to add error handling at each error-handler implementation. It is not possible to "forget" an error in one (of maybe many) switch statments.
The code:
//Inteface for type-safe error handler
interface ErrorHandler {
void handleInvalidLoginError(InvalidLoginError error);
void handleInvalidPasswordError(InvalidLoginError error);
//One method must be added for each kind error. No chance to "forget" one.
}
//The error hierachy
public class AbstractError(Exception) {
private int code;
abstract public void handle(ErrorHandler);
}
public class InvalidLoginError(AbstractError) {
private String additionalStuff;
public void handle(ErrorHandler handler) {
handler.handleInvalidLoginError(this);
}
public String getAdditionalStuff();
}
public class InvalidPasswordError(AbstractError) {
private int code;
public void handle(ErrorHandler handler) {
handler.handleInvalidPasswordError(this);
}
}
//Test class
public class Test {
public void test() {
//Create an error handler instance.
ErrorHandler handler = new LoggingErrorHandler();
try {
doSomething();//throws AbstractError
}
catch (AbstractError e) {
e.handle(handler);
}
}
}
Consider this sample class,
class TargetClass {
private static String SENSITIVE_DATA = "sw0rdfish";
private static String getSensitiveData() {
return SENSITIVE_DATA;
}
}
When I do this,
import java.lang.reflect.Method;
public class ClassPiercing {
public static void main(String... args) throws Exception {
Class targetClass = Class.forName("TargetClass");
Method[] methods = targetClass.getDeclaredMethods();
methods[0].setAccessible(true);
String sensitiveData = (String)methods[0].invoke(null, null);
System.out.println("Sensitive Data: " + sensitiveData);
}
}
The output is,
Sensitive Data: sw0rdfish
This is dangerous. How do I prevent this from happening?
Well, use a SecurityManager.
http://java.sun.com/javase/6/docs/api/java/lang/SecurityManager.html
http://java.sun.com/javase/6/docs/technotes/guides/security/permissions.html#ReflectPermission
disabling ReflectPermission should do the trick.
The point of access control is not to prevent someone from hacking in to your code; It's a matter of signalling intend to other programmers (eg. api design). If you don't trust the other program, you should run use different measures. For example, you could encrypt the data somehow.