Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ A library for handling exceptions with reasons.
In Java programming, it is cumbersome to implement a separate exception class for each exception case.
However, trying to handle multiple exception cases with a single exception class makes it difficult to distinguish between them.

The exception class `Exc` provided by this library solves this problem by accepting a `Record` object that represents the reason for the exception.
The exception class `Exc` provided by this library solves this problem by accepting an object that represents the reason for the exception.
Typically, the type of this reason object is `Record`.
Since a `Record` object can have any fields, it can store information about the situation at the time the exception occurred.
The type of the `Record` object can be determined and cast using a switch statement, making it easy to write handling logic for each exception case.
The type of the reason can be determined and cast using a switch statement, making it easy to write handling logic for each exception case.

Optionally, when an `Exc` object is instantiated, pre-registered exception handlers can receive notifications either synchronously or asynchronously.
However, to enable this feature, you must specify the system property `-Dgithub.sttk.errs.notify=true` at program startup.
However, to enable this feature, the system property `-Dgithub.sttk.errs.notify=true` must be specified at program startup.

## Install

Expand Down
36 changes: 15 additions & 21 deletions src/main/java/com/github/sttk/errs/Exc.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
/**
* Is the exception class with a reason.
* <p>
* This class has a record field which indicates a reason for this exception. The class name of the reason record
* represents the type of reason, and the fields of the reason record hold the situation where the exception occurred.
* This class has a field which indicates a reason for this exception. Typically the type of this field is
* {@link Record}. In this case, the class name of this record represents the reason, and the fields of the record hold
* the situation where the exception occurred.
* <p>
* Optionally, this exception class can notify its instance creation to pre-registered exception handlers. This
* notification feature can be enabled by specifying the system property {@code -Dgithub.sttk.errs.notify=true} when the
Expand All @@ -44,18 +45,18 @@ public final class Exc extends Exception {
private static final long serialVersionUID = 260427082865587554L;

/** The reason for this exception. */
private transient Record reason;
private transient Object reason;

/** The stack trace for the location of occurrence. */
private StackTraceElement trace;

/**
* Is the constructor which takes a {@link Record} object indicating the reason for this exception.
* Is the constructor which takes an object indicating the reason for this exception.
*
* @param reason
* A reason for this exception.
*/
public Exc(final Record reason) {
public Exc(final Object reason) {
if (reason == null) {
throw new IllegalArgumentException("reason is null");
}
Expand All @@ -67,16 +68,16 @@ public Exc(final Record reason) {
}

/**
* Is the constructor which takes a {@link Record} object indicating the reason and {@link Throwable} object
* indicating the cause for this exception.
* Is the constructor which takes an object indicating the reason and {@link Throwable} object indicating the cause
* for this exception.
*
* @param reason
* A reason for this exception.
* @param cause
* A cause for this exception.
*/
@SuppressWarnings("this-escape")
public Exc(final Record reason, final Throwable cause) {
public Exc(final Object reason, final Throwable cause) {
super(cause);

if (reason == null) {
Expand All @@ -94,7 +95,7 @@ public Exc(final Record reason, final Throwable cause) {
*
* @return The reason for this exception.
*/
public Record getReason() {
public Object getReason() {
return this.reason;
}

Expand All @@ -105,13 +106,7 @@ public Record getReason() {
*/
@Override
public String getMessage() {
var rsn = this.reason.toString();
var rname = this.reason.getClass().getSimpleName();
rsn = rsn.substring(rname.length() + 1, rsn.length() - 1);

var buf = new StringBuilder(this.reason.getClass().getName());
buf.append(" { ").append(rsn).append(" }");
return buf.toString();
return new StringBuilder(this.reason.getClass().getName()).append(" ").append(reason.toString()).toString();
Comment thread
sttk marked this conversation as resolved.
Outdated
}

/**
Expand Down Expand Up @@ -166,8 +161,8 @@ public RuntimeException toRuntimeException() {
/**
* Writes a serial data of this exception to a stream.
* <p>
* Since a {@link Record} object is not necessarily serializable, this method will throw a
* {@link NotSerializableException} if the {@code reason} field does not inherit {@link Serializable}.
* Since a reason object is not necessarily serializable, this method will throw a {@link NotSerializableException}
* if the {@code reason} field does not inherit {@link Serializable}.
*
* @param out
* An {@link ObjectOutputStream} to which data is written.
Expand All @@ -185,8 +180,7 @@ private void writeObject(ObjectOutputStream out) throws IOException {

/**
* Reconstitutes the {@code Exc} instance from a stream and initialize the reason and cause properties when
* deserializing. If the reason by deserialization is null or invalid, this method throws
* {@link InvalidObjectException}.
* deserializing. If the reason by deserialization is null, this method throws {@link InvalidObjectException}.
*
* @param in
* An {@link ObjectInputStream} from which data is read.
Expand All @@ -198,7 +192,7 @@ private void writeObject(ObjectOutputStream out) throws IOException {
*/
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
this.reason = Record.class.cast(in.readObject());
this.reason = in.readObject();
Comment thread
sttk marked this conversation as resolved.

if (this.reason == null) {
throw new InvalidObjectException("reason is null or invalid.");
Expand Down
63 changes: 52 additions & 11 deletions src/test/java/com/github/sttk/errs/ExcTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -31,7 +32,7 @@ record SerializableReason(String name, int index, int min, int max) implements S
@Nested
class TestConstructor {
@Test
void with_reason() {
void with_Record_reason() {
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3));
var reason = IndexOutOfRange.class.cast(exc.getReason());
assertThat(reason.name()).isEqualTo("data");
Expand All @@ -43,6 +44,28 @@ void with_reason() {
// exc.printStackTrace();
}

@Test
void with_enum_reason() {
enum Reasons {
FailToDoSomething,
}

var exc = new Exc(Reasons.FailToDoSomething);
var reason = Reasons.class.cast(exc.getReason());
assertThat(reason.name()).isEqualTo("FailToDoSomething");
assertThat(exc.getCause()).isNull();

// exc.printStackTrace();
}

@Test
void with_String_reason() {
var exc = new Exc("FailToDoSomething");
var reason = String.class.cast(exc.getReason());
assertThat(reason).isEqualTo("FailToDoSomething");
assertThat(exc.getCause()).isNull();
}

@Test
void with_reason_but_reason_is_null() {
try {
Expand Down Expand Up @@ -104,7 +127,7 @@ void identify_reason_with_instanceOf() {
}

@Test
void identify_reason_with_switch_expression() {
void identify_Record_reason_with_switch_expression() {
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3));
switch (exc.getReason()) {
case IndexOutOfRange reason -> {
Expand All @@ -116,6 +139,24 @@ void identify_reason_with_switch_expression() {
default -> fail();
}
}

@Test
void identify_Enum_reason_with_switch_expression() {
enum Reasons {
FailToDoSomething, InvalidValue,
}

var exc = new Exc(Reasons.FailToDoSomething);

var s = switch (exc.getReason()) {
case Reasons enm -> switch (enm) {
case FailToDoSomething -> "fail to do something";
case InvalidValue -> "invalid value";
};
default -> "unknown";
};
assertThat(s).isEqualTo("fail to do something");
}
}

@Nested
Expand Down Expand Up @@ -151,7 +192,7 @@ void getFile() {
@Test
void getLine() {
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3));
assertThat(exc.getLine()).isEqualTo(153);
assertThat(exc.getLine()).isEqualTo(194);
}
}

Expand All @@ -160,16 +201,16 @@ class TestGetMessage {
@Test
void with_cause() {
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3));
assertThat(exc.getMessage())
.isEqualTo("com.github.sttk.errs.ExcTest$IndexOutOfRange { name=data, index=4, min=0, max=3 }");
assertThat(exc.getMessage()).isEqualTo(
"com.github.sttk.errs.ExcTest$IndexOutOfRange IndexOutOfRange[name=data, index=4, min=0, max=3]");
}

@Test
void with_no_cause() {
var cause = new IndexOutOfBoundsException(4);
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3), cause);
assertThat(exc.getMessage())
.isEqualTo("com.github.sttk.errs.ExcTest$IndexOutOfRange { name=data, index=4, min=0, max=3 }");
assertThat(exc.getMessage()).isEqualTo(
"com.github.sttk.errs.ExcTest$IndexOutOfRange IndexOutOfRange[name=data, index=4, min=0, max=3]");
}
}

Expand All @@ -179,15 +220,15 @@ class TestToString {
void with_reason() {
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3));
assertThat(exc.toString()).isEqualTo(
"com.github.sttk.errs.Exc { reason = com.github.sttk.errs.ExcTest$IndexOutOfRange { name=data, index=4, min=0, max=3 }, file = ExcTest.java, line = 180 }");
"com.github.sttk.errs.Exc { reason = com.github.sttk.errs.ExcTest$IndexOutOfRange IndexOutOfRange[name=data, index=4, min=0, max=3], file = ExcTest.java, line = 221 }");
}

@Test
void with_reason_and_cause() {
var cause = new IndexOutOfBoundsException(4);
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3), cause);
assertThat(exc.toString()).isEqualTo(
"com.github.sttk.errs.Exc { reason = com.github.sttk.errs.ExcTest$IndexOutOfRange { name=data, index=4, min=0, max=3 }, file = ExcTest.java, line = 188, cause = java.lang.IndexOutOfBoundsException: Index out of range: 4 }");
"com.github.sttk.errs.Exc { reason = com.github.sttk.errs.ExcTest$IndexOutOfRange IndexOutOfRange[name=data, index=4, min=0, max=3], file = ExcTest.java, line = 229, cause = java.lang.IndexOutOfBoundsException: Index out of range: 4 }");
}
}

Expand All @@ -197,8 +238,8 @@ class TestToRuntimeException {
void getMessage() {
var exc = new Exc(new IndexOutOfRange("data", 4, 0, 3));
var rtExc = exc.toRuntimeException();
assertThat(rtExc.getMessage())
.isEqualTo("com.github.sttk.errs.ExcTest$IndexOutOfRange { name=data, index=4, min=0, max=3 }");
assertThat(rtExc.getMessage()).isEqualTo(
"com.github.sttk.errs.ExcTest$IndexOutOfRange IndexOutOfRange[name=data, index=4, min=0, max=3]");
}

@Test
Expand Down