Give final users hints when getting error messages without the need for documentation.
Wouldn't be beneficial to give users hints about what went wrong with a failing command right away, without the need to open any documentation ?
take-a-hint (a.k.a. Hint) is a tiny framework to change the look-and-feel of Java error messages. Giving the possibility to add hints on top of error message, final user can take-a-hint as quick as possible.
Given the following example:
public class Spaceship {
public static void main(String[] args) {
Spaceship main = new Spaceship();
main.goToMars();
}
private void goToMars() {
throw new IllegalStateException("Oxygen leak detected !");
}
}
Typically, executing this will result in the stacktrace we are all familiar with:
Adding Hint is as simple as initializing a Hint object using your main class:
public class Spaceship {
public static void main(String[] args) {
Spaceship main = new Spaceship();
new HintCommand(main).init();
main.goToMars();
}
// ...
}
Executing the new program, we get a new look for our error message.
By default, take-a-hint shows the essential part of the error which is the error message. You can customize your final look further more and obtain something like:
take-a-hint comes with pre-defined look-and-feel for your Java error messages. Still, you can -and should- customize final messages depending on your needs.
take-a-hint offers a set of properties you can customize in code to configure final error messages.
Property | Default value | Description |
---|---|---|
showStackTrace | false | shows or hides stacktrace in final output |
showHints | true | shows or hides hints messages in final output |
defaultExceptionMessage | 'Application failed with exception :' | default message for exceptions without custom error message |
defaultDocsMessage | 'See the docs for details : ' | default message for notes about documentations |
defaultExitCode | 1 | default exit code to be used by your program when an uncaught exception gets thrown |
hintPrefix | '✅ hints:' | default prefix to be used for each line in hints messages |
errorPrefix | '❌ error:' | default prefix to be used for each line in error messages |
stackPrefix | '⛔ stack:' | default prefix to be used for each line in stacktrace |
docsPrefix | ❔ usage: | default prefix to be used for each line in usage messages (docs) |
defaultDocsSeparator | '---' | default separator to be used before showing documentation message |
defaultSeparator | \t | default separator to be used between each token in final output (e.g between error prefix and message) |
docsUrl | global documentation url, if unset, documentation help message won't show up on your final output |
In order to configure how error messages appear, you can use following options:
- via programmatic API:
class Spaceship{
public static void main(String[] args){
new HintCommand(new Spaceship())
.errorPrefix("[ERROR] :")
.showStackTrace(true)
.docsUrl("https://github.com/tahabasri/take-a-hint")
.init();
}
}
- via annotations:
@Hint(
errorPrefix = "[ERROR] :",
showStackTrace = true,
docsUrl = "https://github.com/tahabasri/take-a-hint"
)
class Spaceship{
public static void main(String[] args){
new HintCommand(new Spaceship()).init();
}
}
When mixing annotation and programmatic API, take-a-hint will opt for configuration by programmatic API.
It's much better when the final user can get hints on how to fix errors at failure time. take-a-hint offers custom Exception classes to help you communicate hints easily.
If you have already a code block that throws an exception, you can wrap it inside HintException
or HintRuntimeException
. This will let you personalize final error messages depending on your needs. Here are some examples:
class Spaceship {
private void goToMars() {
throw HintRuntimeException.of(
new IllegalStateException("Oxygen leak !!!"), // this is your regular exception
"Check your equipments !" // you can set a custom hint message to be shown to final user
);
}
}
When the error gets thrown, the final user will get the following message (depending on configuration):
❌ error: Application failed with exception : java.lang.IllegalStateException: Oxygen leak !!!
✅ hints: Check your equipments !
You can work with checked exceptions while using take-a-hint via the custom exception class HintException
:
class Spaceship {
private void goToMars() throws HintException {
throw HintException.of(new IllegalStateException("Oxygen leak !!!"), "Check your equipments !");
}
}
// ...
try {
new Spaceship().goToMars();
} catch (HintException ex) { // HintException is a checked exception, should be thrown
System.out.println("Custom error : " + ex.getHintsMsg()); // use exception instance as you want
}
Another cool thing you can do with take-a-hint is set a global hint message for method or class. Let's say you have a method that may throw an exception in multiple occasions, and you want to provide a single hint message for the whole method. Then, you can use the annotation @HintMessage
to do that.
@Hint
class Spaceship {
@HintMessage("Check your equipments")
private void goToMars(int x) {
if(x==-1){
throw new IllegalStateException("Crash !");
}else if(x==0){
throw new IllegalStateException("Boom !");
}else{
// go
}
}
}
For each exception thrown within the method goToMars
, take-a-hint will display (depending on the configuration) the custom hint message provided by the annotation @HintMessage
.
You can use the same annotation with the parent class. In that case, the provided message in the class will be shown whenever an exception gets thrown within a method of that class. Each method with its own annotation will override the class custom message.
@Hint
@HintMessage("Check your equipments !")
class Spaceship {
@HintMessage("What about heat ?") // this message will override the one in parent class
private void goToTheSun(int x) {
throw new IllegalStateException("Burned !");
}
// will use parent message
private void goToMars() {
throw new IllegalStateException("Boom !");
}
}
Picocli is a one-file framework for creating Java command line applications with almost zero code.
In order to add take-a-hint features to your Picocli application, you need to use Hint custom handlers.
@CommandLine.Command
@Hint // this is optional
public class PicocliWithHint implements Runnable {
@CommandLine.Parameters
int apiRating;
public static void main(String[] args) {
PicocliWithHint picocliWithHint = new PicocliWithHint();
CommandLine cmd = new CommandLine(picocliWithHint);
HintCommand hintCmd = new HintCommand(picocliWithHint);
// add custom handler for parameter exceptions
cmd.setParameterExceptionHandler(new PicocliParameterExceptionHandler(hintCmd));
// add custom handler for all other exceptions
cmd.setExecutionExceptionHandler(new PicocliExecutionExceptionHandler(hintCmd));
System.exit(cmd.execute(args));
}
@Override
public void run() {
if (apiRating < 3) {
throw new RuntimeException("Not cool !");
}
}
}
After wiring take-a-hint with Picocli, exceptions will be shown with Hint style.
- When running Picocli program without required parameter
apiRating
, we get:
- When running Picocli with incompatible parameter for
apiRating
, we get:
take-a-hint comes with pre-defined messages with a set of Emojis, you can change yours via the configuration:
- via programmatic API, you can set your default prefix for error messages like this :
class Spaceship{
public static void main(String[] args){
new HintCommand(new Spaceship()).errorPrefix("\u26D4 error :").init();
}
}
Your final message will be :
⛔ error : Spaceship is no longer a ship!
- via annotations, you can achieve the same result via the following method :
@Hint(errorPrefix = "\u26D4 error :")
class Spaceship{
new HintCommand(new Spaceship()).init();
}
To use a new Emoji, you can follow the steps bellow:
- Visit Full Emoji List, v13.0 and find emoji to use.
- Copy the code in
Code
column and make a search in fileformat.info using the copied code as query string. - Click on the returned result (end of page) and copy the value for column
C/C++/Java source code
. - Put the value in your prefix string and recompile your program, it should show the new Emoji.