
dev.denwav.hypo.model.HypoModelUtil Maven / Gradle / Ivy
/*
* Hypo, an extensible and pluggable Java bytecode analytical model.
*
* Copyright (C) 2023 Kyle Wood (DenWav)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as published by
* the Free Software Foundation, version 3 of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
package dev.denwav.hypo.model;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* General utility class used by all of Hypo. As this module, {@code hypo-model}, is the base model in Hypo's module
* dependency graph, this utility class acts as the base generic utility class for the entire Hypo project as well.
*/
public final class HypoModelUtil {
private HypoModelUtil() {}
/**
* Normalize the given class name into a standard format matching the JVM internal class name format. Class type
* descriptors may also be passed in (that is, class names that looks like {@code Lcom/example/Main;}) and this
* method will strip the initial {@code L} and the final {@code ;}.
*
* This method does not validate the format of the class name provided, it simply runs a couple standard String
* functions on it to help increase cache-hit rate and reduce the likelihood of a class name in a slightly different
* format causing issues.
*
* @param className The class name to normalize.
* @return The normalized class name.
*/
public static @NotNull String normalizedClassName(final @NotNull String className) {
final int index = className.endsWith(";") ? 1 : 0;
return className.substring(index, className.length() - index).replace('.', '/');
}
/**
* Perform an unsafe, unchecked cast to the type specified by {@link T}. This method does not in any way verify or
* validate that the cast will succeed, nor does it protect from failed casts from occurring. This method's intended
* purpose is for handing cases where Java's type system falls short, such as when handling capture types. Any code
* which uses this method should independently verify that the cast will never fail on their own.
*
* @param o The object to cast to {@link T}. Can be {@code null}, if so then {@code null} will be returned.
* @param The type to cast {@code o} to.
* @return The given parameter {@code o}, cast to {@link T}.
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
@Contract(value = "_ -> param1", pure = true)
public static @Nullable T cast(final @Nullable Object o) {
@SuppressWarnings("unchecked") final T t = (T) o;
return t;
}
/**
* Rethrow the given {@link Throwable} {@code x} as if it were an unchecked exception, regardless if {@code x}
* actually is unchecked or not. This method does not wrap the given exception. This method returns
* {@link RuntimeException} purely to allow you to re-throw the result to tell the compiler that an unchecked
* exception will be thrown - in reality this method never returns.
*
* For example, the intended pattern for using this method when the compiler needs to know the code path will not
* continue (such as when initializing variables, etc.) is this:
*
*
* try {
* ...
* } catch (Exception e) {
* throw HypoModelUtil.rethrow(e);
* }
*
*
* The actual execution of the code is entirely unchanged regardless of whether the {@code throw} in the above
* code snippet exists, however including the {@code throw} keyword helps the Java compiler better understand the
* code.
*
* @param t The {@link Throwable} to unconditionally re-throw as unchecked.
* @param Generic hack to allow this method to trick the Java compiler into allowing this.
* @return A {@link RuntimeException} to help tell the Java compiler an exception will be thrown.
* @throws X Generic hack to allow this method to trick the Java compiler into allowing this.
*/
@Contract("_ -> fail")
@SuppressWarnings("unchecked")
public static RuntimeException rethrow(final @NotNull Throwable t) throws X {
throw (X) t;
}
/**
* Allow the creation of a {@link ThrowingFunction}, which also implements {@link Function}. This can be used to
* allow checked exceptions inside {@link Function} lambda expressions. Any exceptions thrown in this block will
* be re-thrown as unchecked using {@link #rethrow(Throwable)}.
*
* This method simply returns the {@link ThrowingFunction} directly, this method only acts as a shorthand for the
* following code (notably not needing to specify generic types):
*
*
* {@literal someMethod(HypoModelUtil.wrapFunction(t -> t))}
* // Shorthand for:
* {@literal someMethod((ThrowingFunction) k -> t)}
*
*
* @param func The function to return.
* @param The input type of the function.
* @param The return type of the function.
* @param The exception the function may throw.
* @return The given function as-is.
*/
@Contract(value = "_ -> param1", pure = true)
public static ThrowingFunction wrapFunction(
final @NotNull ThrowingFunction func
) {
return func;
}
/**
* Allow the creation of a {@link ThrowingConsumer}, which also implements {@link Consumer}. This can be used to
* allow checked exceptions inside {@link Consumer} lambda expressions. Any exceptions thrown in this block will
* be re-thrown as unchecked using {@link #rethrow(Throwable)}.
*
* This method simply returns the {@link ThrowingConsumer} directly, this method only acts as a shorthand for the
* following code (notably not needing to specify generic types):
*
*
* {@literal someMethod(HypoModelUtil.wrapConsumer(t -> {}))}
* // Shorthand for:
* {@literal someMethod(ThrowingFunction) t -> {}}
*
*
* @param consumer The consumer to return.
* @param The input type of the consumer.
* @param The exception the consumer may throw.
* @return The given consumer as-is.
*/
@Contract(value = "_ -> param1", pure = true)
public static ThrowingConsumer wrapConsumer(
final @NotNull ThrowingConsumer consumer
) {
return consumer;
}
/**
* Simplifies the logic of handling suppressed exceptions when multiple exceptions may be chained. If the
* {@code base} exception is {@code null}, {@code thrown} will be returned directly. Otherwise, {@code thrown} will
* be passed to {@code base's} {@link Throwable#addSuppressed(Throwable)} method, and {@code base} will be returned.
* The typical pattern this method is intended for is as follows:
*
*
* Throwable thrown = null;
* try {
* ...
* } catch (Exception e) {
* thrown = addSuppressed(thrown, e);
* }
* try {
* ...
* } catch (Exception e) {
* thrown = addSuppressed(thrown, e);
* }
* if (thrown != null) {
* // do something
* }
*
*
* @param base The current base exception, can be {@code null}.
* @param thrown The new thrown exception, must not be {@code null}.
* @param The type of the base exception.
* @param The type of the new exception, must be assignable to {@link T1}.
* @return The exception to act as the new base exception.
*/
@Contract("null, _ -> param2")
public static @NotNull T1 addSuppressed(
final @Nullable T1 base,
final @NotNull T2 thrown
) {
if (base == null) {
return thrown;
}
base.addSuppressed(thrown);
return base;
}
/**
* Create a copy of the given collection and return it as an immutable list.
*
* @param collection The collection to copy as an immutable list.
* @param The type param of the collection.
* @return The new immutable list.
*/
public static @NotNull List asImmutableList(final @NotNull Collection collection) {
return HypoModelUtilHelper.INSTANCE.asImmutableList(collection);
}
/**
* Create an immutable list from the given array.
*
* @param array The array to copy as an immutable list.
* @param The type param of the collection.
* @return The new immutable list.
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static @NotNull List immutableListOf(final @NotNull T @NotNull ... array) {
return HypoModelUtilHelper.INSTANCE.immutableListOf(array);
}
/**
* Version of {@link Function} which allows throwing checked exception inside the method implementation. When the
* {@link Function#apply(Object)} method is invoked it will call {@link #applyThrowing(Object)}, rethrowing any
* exceptions thrown as unchecked using {@link #rethrow(Throwable)}.
*
* @param The input type of the function.
* @param The return type of the function.
* @param The exception the function may throw.
*/
@FunctionalInterface
public interface ThrowingFunction extends Function {
/**
* The same as {@link #apply(Object)}, but also allowing checked exceptions to be thrown.
*
* @param t The function argument
* @return The function result
* @throws X The type of the exception which this method throws
*/
R applyThrowing(T t) throws X;
@SuppressWarnings("FunctionalInterfaceMethodChanged")
@Override
default R apply(T t) {
try {
return this.applyThrowing(t);
} catch (final Throwable x) {
throw HypoModelUtil.rethrow(x);
}
}
}
/**
* Version of {@link Consumer} which allows throwing checked exception inside the method implementation. When the
* {@link Consumer#accept(Object)} method is invoked it will call {@link #acceptThrowing(Object)}, rethrowing any
* exceptions thrown as unchecked using {@link #rethrow(Throwable)}.
*
* @param The input type of the consumer.
* @param The exception the consumer may throw.
*/
@FunctionalInterface
public interface ThrowingConsumer extends Consumer {
/**
* The same as {@link #accept(Object)}, but also allowing checked exceptions to be thrown.
*
* @param t The input argument
* @throws X The type of the exception which this method throws
*/
void acceptThrowing(T t) throws X;
@SuppressWarnings("FunctionalInterfaceMethodChanged")
@Override
default void accept(T t) {
try {
this.acceptThrowing(t);
} catch (final Throwable x) {
throw HypoModelUtil.rethrow(x);
}
}
}
}