Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
38 changes: 16 additions & 22 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 reason.toString();
}

/**
Expand All @@ -123,7 +118,7 @@ public String getMessage() {
@Override
public String toString() {
var buf = new StringBuilder(getClass().getName());
buf.append(" { reason = ").append(getMessage());
buf.append(" { reason = ").append(reason.getClass().getName()).append(" ").append(reason.toString());
buf.append(", file = ").append(this.trace.getFileName());
buf.append(", line = ").append(this.trace.getLineNumber());
if (getCause() != null) {
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();

if (this.reason == null) {
throw new InvalidObjectException("reason is null or invalid.");
Expand Down
60 changes: 49 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,14 @@ 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("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("IndexOutOfRange[name=data, index=4, min=0, max=3]");
}
}

Expand All @@ -179,15 +218,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 = 219 }");
}

@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 = 227, cause = java.lang.IndexOutOfBoundsException: Index out of range: 4 }");
}
}

Expand All @@ -197,8 +236,7 @@ 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("IndexOutOfRange[name=data, index=4, min=0, max=3]");
}

@Test
Expand Down