fix(serialize): throw checked exceptions in SerializeReader iterator. Probably better than just trusting every TypeAdapter to catch wrapping exceptions.
ci/woodpecker/push/woodpecker Pipeline failed Details

This commit is contained in:
Johannes Frohnmeyer 2024-05-04 20:55:15 +02:00
parent 21beed944d
commit 754f418ceb
Signed by: Johannes
GPG Key ID: E76429612C2929F4
4 changed files with 147 additions and 72 deletions

View File

@ -1,10 +1,11 @@
package io.gitlab.jfronny.commons.serialize;
import io.gitlab.jfronny.commons.SamWithReceiver;
import io.gitlab.jfronny.commons.throwable.ExceptionWrapper;
import io.gitlab.jfronny.commons.throwable.Unchecked;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
@ -109,8 +110,8 @@ public abstract class SerializeReader<TEx extends Exception, T extends Serialize
/**
* Returns a {@link Stream} of the tokens within the current object or array.
* The stream consumes tokens and advances the reader, so it is not possible to iterate over the tokens more than once.
* Additionally, iteration may throw {@link RuntimeException}s created with {@link ExceptionWrapper#wrap(Throwable)} around {@link TEx}.
* That exception MUST be caught and handled by the caller immediately. Throwing it in a TypeAdapter is NOT allowed.
* Additionally, although it is not marked as such, iteration may throw a TEx.
* The caller is responsible for making sure it is caught and not propagated.
*
* @return a stream of the tokens
*/
@ -121,32 +122,59 @@ public abstract class SerializeReader<TEx extends Exception, T extends Serialize
/**
* Returns an {@link Iterator} over the tokens within the current object or array.
* The iterator consumes tokens and advances the reader, so it is not possible to iterate over the tokens more than once.
* Additionally, iteration may throw {@link RuntimeException}s created with {@link ExceptionWrapper#wrap(Throwable)} around {@link TEx}.
* That exception MUST be caught and handled by the caller immediately. Throwing it in a TypeAdapter is NOT allowed.
* Additionally, although it is not marked as such, iteration may throw a TEx.
* The caller is responsible for making sure it is caught and not propagated.
*
* @return an iterator over the tokens
*/
@Override
public @NotNull Iterator<RToken> iterator() {
return new Iterator<>() {
@Override
public boolean hasNext() {
try {
return SerializeReader.this.hasNext();
} catch (Exception e) {
throw ExceptionWrapper.wrap(e);
}
}
public @NotNull TokenIterator iterator() {
return new TokenIterator();
}
@Override
public RToken next() {
try {
return SerializeReader.this.next();
} catch (Exception e) {
throw ExceptionWrapper.wrap(e);
}
/**
* An iterator over the tokens within the current object or array.
* The iterator consumes tokens and advances the reader, so it is not possible to iterate over the tokens more than once.
* Additionally, although it is not marked as such in code, iteration may throw a TEx.
* <p>
* Note: this iterator tracks the depth of the tree it traversed,
* so consuming BEGIN_OBJECT or BEGIN_ARRAY tokens while it is in use
* without consuming the corresponding END_OBJECT or END_ARRAY tokens before the next call to it is not supported.
*/
public class TokenIterator implements Iterator<RToken> {
int depth = 0;
/**
* {@inheritDoc}
* @throws TEx if an error occurs
*/
@Override
public boolean hasNext() {
try {
return depth > 0 || SerializeReader.this.hasNext();
} catch (Exception e) {
return Unchecked.sneakyThrow(e);
}
};
}
/**
* {@inheritDoc}
* @throws TEx if an error occurs
*/
@Override
public RToken next() {
if (!hasNext()) throw new NoSuchElementException("No more tokens");
try {
RToken token = SerializeReader.this.next();
if (token == RToken.Simple.BEGIN_ARRAY || token == RToken.Simple.BEGIN_OBJECT) {
depth++;
} else if (token == RToken.Simple.END_ARRAY || token == RToken.Simple.END_OBJECT) {
depth--;
}
return token;
} catch (Exception e) {
return Unchecked.sneakyThrow(e);
}
}
}
/**

View File

@ -1,6 +1,7 @@
package io.gitlab.jfronny.commons.concurrent;
import io.gitlab.jfronny.commons.throwable.Assume;
import io.gitlab.jfronny.commons.throwable.Unchecked;
import org.jetbrains.annotations.ApiStatus;
public interface WithScopedValue<T> {
@ -8,12 +9,12 @@ public interface WithScopedValue<T> {
@ApiStatus.Internal T self();
default <TEx1 extends Throwable, TEx2 extends Throwable> void withContext(Action<TEx1, TEx2> action) throws TEx1, TEx2 {
Assume.<TEx1>reintroduce();
Assume.<TEx2>reintroduce(() -> ScopedValue.runWhere(getAttached(), self(), Assume.isSafe(action::run)));
Unchecked.<TEx1>reintroduce();
Unchecked.<TEx2>reintroduce(() -> ScopedValue.runWhere(getAttached(), self(), Assume.isSafe(action::run)));
}
default <T, TEx1 extends Throwable, TEx2 extends Throwable> T withContext(Returnable<T, TEx1, TEx2> action) throws TEx1, TEx2 {
Assume.<TEx1>reintroduce();
return Assume.<T, TEx2>reintroduce(() -> ScopedValue.getWhere(getAttached(), self(), Assume.isSafe(action::run)));
Unchecked.<TEx1>reintroduce();
return Unchecked.<T, TEx2>reintroduce(() -> ScopedValue.getWhere(getAttached(), self(), Assume.isSafe(action::run)));
}
interface Returnable<T, TEx1 extends Throwable, TEx2 extends Throwable> {

View File

@ -4,6 +4,7 @@ import java.util.function.Supplier;
/**
* A utility class to hide checked exceptions from the java compiler.
* For immediate execution or to reintroduce exceptions, use {@link Unchecked}.
*/
public class Assume {
/**
@ -33,63 +34,28 @@ public class Assume {
return rn::get;
}
/**
* Hides an exception from the java compiler, effectively making it unchecked.
* Runs a runnable.
*
* @param runnable the runnable to run
* @param <TEx> the exception the runnable might throw
*/
@Deprecated(forRemoval = true)
public static <TEx extends Throwable> void sneaky(ThrowingRunnable<TEx> runnable) {
isSafe(runnable).run();
Unchecked.sneaky(runnable);
}
/**
* Hides an exception from the java compiler, effectively making it unchecked.
* Runs a supplier and returns the result.
*
* @param supplier the supplier to run
* @param <T> the return type of the supplier
* @param <TEx> the exception the supplier might throw
* @return the result of the supplier
*/
@Deprecated(forRemoval = true)
public static <T, TEx extends Throwable> T sneaky(ThrowingSupplier<T, TEx> supplier) {
return isSafe(supplier).get();
return Unchecked.sneaky(supplier);
}
/**
* Reintroduces an exception previously made sneaky, allowing you to catch it.
* The call does nothing except tricking the compiler into thinking that the exception is thrown.
*
* @param <TEx> the exception to reintroduce into the set of possible exceptions
* @throws TEx the introduced exception
*/
@Deprecated(forRemoval = true)
public static <TEx extends Throwable> void reintroduce() throws TEx {
Unchecked.reintroduce();
}
/**
* Reintroduces an exception previously made sneaky, allowing you to catch it.
* Simply runs the runnable.
*
* @param runnable the runnable to run
* @param <TEx> the exception to reintroduce into the set of possible exceptions
* @throws TEx the exception that was thrown by the runnable
*/
@Deprecated(forRemoval = true)
public static <TEx extends Throwable> void reintroduce(Runnable runnable) throws TEx {
runnable.run();
Unchecked.reintroduce(runnable);
}
/**
* Reintroduces an exception previously made sneaky, allowing you to catch it.
* Simply runs the supplier and returns the result.
*
* @param supplier the supplier to run
* @param <T> the return type of the supplier
* @param <TEx> the exception to reintroduce into the set of possible exceptions
* @return the result of the supplier
* @throws TEx the exception that was thrown by the supplier
*/
@Deprecated(forRemoval = true)
public static <T, TEx extends Throwable> T reintroduce(Supplier<T> supplier) throws TEx {
return supplier.get();
return Unchecked.reintroduce(supplier);
}
}

View File

@ -0,0 +1,80 @@
package io.gitlab.jfronny.commons.throwable;
import java.util.function.Supplier;
/**
* A utility class to hide checked exceptions from the java compiler.
* For conversion of checked lambdas to unchecked lambdas, use {@link Assume}.
*/
public class Unchecked {
/**
* Throws a checked exception without the need to declare it.
*
* @param t the exception to throw
*/
public static <T> T sneakyThrow(Throwable t) {
return sneaky(() -> {
throw t;
});
}
/**
* Hides an exception from the java compiler, effectively making it unchecked.
* Runs a runnable.
*
* @param runnable the runnable to run
* @param <TEx> the exception the runnable might throw
*/
public static <TEx extends Throwable> void sneaky(ThrowingRunnable<TEx> runnable) {
Assume.isSafe(runnable).run();
}
/**
* Hides an exception from the java compiler, effectively making it unchecked.
* Runs a supplier and returns the result.
*
* @param supplier the supplier to run
* @param <T> the return type of the supplier
* @param <TEx> the exception the supplier might throw
* @return the result of the supplier
*/
public static <T, TEx extends Throwable> T sneaky(ThrowingSupplier<T, TEx> supplier) {
return Assume.isSafe(supplier).get();
}
/**
* Reintroduces an exception previously made sneaky, allowing you to catch it.
* The call does nothing except tricking the compiler into thinking that the exception is thrown.
*
* @param <TEx> the exception to reintroduce into the set of possible exceptions
* @throws TEx the introduced exception
*/
public static <TEx extends Throwable> void reintroduce() throws TEx {
}
/**
* Reintroduces an exception previously made sneaky, allowing you to catch it.
* Simply runs the runnable.
*
* @param runnable the runnable to run
* @param <TEx> the exception to reintroduce into the set of possible exceptions
* @throws TEx the exception that was thrown by the runnable
*/
public static <TEx extends Throwable> void reintroduce(Runnable runnable) throws TEx {
runnable.run();
}
/**
* Reintroduces an exception previously made sneaky, allowing you to catch it.
* Simply runs the supplier and returns the result.
*
* @param supplier the supplier to run
* @param <T> the return type of the supplier
* @param <TEx> the exception to reintroduce into the set of possible exceptions
* @return the result of the supplier
* @throws TEx the exception that was thrown by the supplier
*/
public static <T, TEx extends Throwable> T reintroduce(Supplier<T> supplier) throws TEx {
return supplier.get();
}
}