[main] Some additions from Inceptum

This commit is contained in:
Johannes Frohnmeyer 2022-09-04 12:34:46 +02:00
parent 57b3e2b60d
commit 58fa5c5620
Signed by: Johannes
GPG Key ID: E76429612C2929F4
11 changed files with 552 additions and 45 deletions

View File

@ -68,34 +68,6 @@ public class GsonHolder {
* Register this in {@code SerializerHolder}
*/
public static void register() {
SerializerHolder.setInstance(new Serializer() {
@Override
public String serialize(Object o) {
return getGson().toJson(o);
}
@Override
public <T> T deserialize(Reader source, Type typeOfT) throws SerializeException {
try {
return getGson().fromJson(source, typeOfT);
} catch (JsonIOException | JsonSyntaxException e) {
throw new SerializeException(e);
}
}
@Override
public <T> T deserialize(String source, Type typeOfT) throws SerializeException {
try {
return getGson().fromJson(source, typeOfT);
} catch (JsonIOException | JsonSyntaxException e) {
throw new SerializeException(e);
}
}
@Override
public String getFormatMime() {
return "application/json";
}
});
Serializer.setInstance(new GsonHolderSerializer());
}
}

View File

@ -0,0 +1,99 @@
package io.gitlab.jfronny.commons.serialize.gson.impl;
import com.google.gson.*;
import io.gitlab.jfronny.commons.serialize.Serializer;
import io.gitlab.jfronny.commons.serialize.gson.api.GsonHolder;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
public class GsonHolderSerializer implements Serializer {
@Override
public String serialize(Object object) throws IOException {
try {
return getGson().toJson(object);
} catch (JsonIOException | JsonSyntaxException e) {
throw generateException(e);
}
}
@Override
public void serialize(Object object, Appendable writer) throws IOException {
try {
getGson().toJson(object, writer);
} catch (Exception e) {
throw generateException(e);
}
}
@Override
public <T> T deserialize(Reader source, Class<T> typeOfT) throws IOException {
try {
return getGson().fromJson(source, typeOfT);
} catch (Exception e) {
throw generateException(e);
}
}
@Override
public <T> T deserialize(Reader source, Type typeOfT) throws IOException {
if (typeOfT instanceof Class<?> k) {
//noinspection unchecked
return (T) deserialize(source, k);
}
try {
return getGson().fromJson(source, typeOfT);
} catch (Exception e) {
throw generateException(e);
}
}
@Override
public <T> T deserialize(String source, Class<T> typeOfT) throws IOException {
try {
return getGson().fromJson(source, typeOfT);
} catch (Exception e) {
throw generateException(e);
}
}
@Override
public <T> T deserialize(String source, Type typeOfT) throws IOException {
if (typeOfT instanceof Class<?> k) {
//noinspection unchecked
return (T) deserialize(source, k);
}
try {
return getGson().fromJson(source, typeOfT);
} catch (Exception e) {
throw generateException(e);
}
}
private IOException generateException(Exception e) {
if (e instanceof IOException io) return io;
Throwable cause = e.getCause();
String message = e.getMessage();
if (cause == null) return new SerializeException(message);
boolean lackingMessage = message == null || message.equals(cause.toString());
if (e instanceof JsonIOException) {
if (lackingMessage) {
if (cause instanceof IOException io) return io;
return new SerializeException(cause);
}
return new SerializeException(message, cause);
}
if (lackingMessage) return new SerializeException(cause);
return new SerializeException(message, cause);
}
private Gson getGson() {
return GsonHolder.getGson();
}
@Override
public String getFormatMime() {
return "application/json";
}
}

View File

@ -5,6 +5,8 @@ import io.gitlab.jfronny.commons.serialize.*;
import io.gitlab.jfronny.commons.serialize.gson.api.*;
import org.junit.jupiter.api.*;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.*;
public class GsonTest {
@ -14,25 +16,25 @@ public class GsonTest {
}
@Test
void gsonIgnore() {
assertEquals("null", SerializerHolder.getInstance().serialize(new ExampleTypeHidden()));
void gsonIgnore() throws IOException {
assertEquals("null", Serializer.getInstance().serialize(new ExampleTypeHidden()));
assertEquals("""
{
"shouldShow": "Yes"
}""", SerializerHolder.getInstance().serialize(new TransitiveHiddenType()));
}""", Serializer.getInstance().serialize(new TransitiveHiddenType()));
assertEquals("""
{
"id": "Yes",
"shown": {
"shouldShow": "Yes"
}
}""", SerializerHolder.getInstance().serialize(new ExampleType()));
}""", Serializer.getInstance().serialize(new ExampleType()));
}
@Test
void comparableVersionAdapter() {
assertEquals("\"1.0.0\"", SerializerHolder.getInstance().serialize(new ComparableVersion("1.0.0")));
assertDoesNotThrow(() -> assertEquals(new ComparableVersion("1.0.0"), SerializerHolder.getInstance().deserialize("\"1.0.0\"", ComparableVersion.class)));
void comparableVersionAdapter() throws IOException {
assertEquals("\"1.0.0\"", Serializer.getInstance().serialize(new ComparableVersion("1.0.0")));
assertDoesNotThrow(() -> assertEquals(new ComparableVersion("1.0.0"), Serializer.getInstance().deserialize("\"1.0.0\"", ComparableVersion.class)));
}
public static class ExampleType {

View File

@ -1,7 +1,6 @@
package io.gitlab.jfronny.commons;
import io.gitlab.jfronny.commons.serialize.Serializer;
import io.gitlab.jfronny.commons.serialize.SerializerHolder;
import java.io.IOException;
import java.io.InputStream;
@ -129,8 +128,8 @@ public class HttpUtils {
return this;
}
public Request bodySerialized(Object object) {
Serializer serializer = SerializerHolder.getInstance();
public Request bodySerialized(Object object) throws IOException {
Serializer serializer = Serializer.getInstance();
builder.header("Content-Type", serializer.getFormatMime());
builder.method(method.name(), HttpRequest.BodyPublishers.ofString(serializer.serialize(object)));
method = null;
@ -189,7 +188,7 @@ public class HttpUtils {
}
public <T> T sendSerialized(Type type) throws IOException {
Serializer serializer = SerializerHolder.getInstance();
Serializer serializer = Serializer.getInstance();
InputStream in = _send(serializer.getFormatMime(), HttpResponse.BodyHandlers.ofInputStream());
try {
return in == null ? null : serializer.deserialize(new InputStreamReader(in), type);

View File

@ -0,0 +1,43 @@
package io.gitlab.jfronny.commons.cache;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
public class FileBackedOperationResultCache {
private final ConcurrentHashMap<String, Object> container = new ConcurrentHashMap<>();
private final Path cacheDir;
public FileBackedOperationResultCache(Path cacheDir) {
this.cacheDir = cacheDir;
}
public void remove(String key) throws IOException {
container.remove(key);
Files.delete(cacheDir.resolve(key));
}
public void clear() throws IOException {
container.clear();
JFiles.clearDirectory(cacheDir);
}
public <T, TEx extends Throwable> T get(String key, ThrowingSupplier<T, TEx> builder, Type klazz) throws IOException, TEx {
if (!container.containsKey(key)) {
Path cd = cacheDir.resolve(key);
if (Files.exists(cd)) {
T value = JFiles.readObject(cd, klazz);
JFiles.writeObject(cd, value);
container.put(key, value);
}
else container.put(key, builder.get());
}
//noinspection unchecked
return (T) container.get(key);
}
}

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.commons.cache;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A linked map that discards earlier entries once a fixed size is reached
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public class FixedSizeMap<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public FixedSizeMap(int size) {
super(size + 2, 1F);
this.maxSize = size;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}

View File

@ -0,0 +1,123 @@
package io.gitlab.jfronny.commons.cache;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* An ordered set that discards earlier entries once a fixed size limit is reached
* @param <E> the type of elements in this set
*/
public class FixedSizeSet<E> implements Set<E> {
private final List<E> backing;
private final int size;
public FixedSizeSet(int size) {
this.backing = new LinkedList<>();
this.size = size;
}
@Override
public int size() {
return backing.size();
}
@Override
public boolean isEmpty() {
return backing.isEmpty();
}
@Override
public boolean contains(Object o) {
return backing.contains(o);
}
@NotNull
@Override
public Iterator<E> iterator() {
return backing.iterator();
}
@NotNull
@Override
public Object[] toArray() {
return backing.toArray();
}
@NotNull
@Override
public <T> T[] toArray(@NotNull T[] ts) {
return backing.toArray(ts);
}
@Override
public boolean add(E e) {
synchronized (backing) {
if (backing.contains(e)) return false;
if (size() >= size) backing.remove(0);
return backing.add(e);
}
}
@Override
public boolean remove(Object o) {
return backing.remove(o);
}
@Override
public boolean containsAll(@NotNull Collection<?> collection) {
for (Object o : Objects.requireNonNull(collection)) {
if (!contains(o)) return false;
}
return true;
}
@Override
public boolean addAll(@NotNull Collection<? extends E> collection) {
synchronized (backing) {
boolean result = false;
for (E e : collection) {
result |= add(e);
}
return result;
}
}
@Override
public boolean retainAll(@NotNull Collection<?> collection) {
return backing.retainAll(collection);
}
@Override
public boolean removeAll(@NotNull Collection<?> collection) {
return backing.removeAll(collection);
}
@Override
public void clear() {
backing.clear();
}
@Override
public Spliterator<E> spliterator() {
return backing.spliterator();
}
@Override
public String toString() {
return backing.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof Set s)) return false;
if (s.size() != this.size()) return false;
return this.containsAll(s);
}
@Override
public int hashCode() {
return backing.hashCode();
}
}

View File

@ -0,0 +1,43 @@
package io.gitlab.jfronny.commons.cache;
import io.gitlab.jfronny.commons.throwable.ThrowingSupplier;
import java.util.HashMap;
import java.util.Map;
/**
* Stores results of operations by keys
* @param <K> The key of the operation
* @param <V> The result of the operation
*/
public class MemoryOperationResultCache<K, V> {
private final Map<K, V> container;
/**
* Create a cache with a fixed size. Once it is reached, old entries will be discarded
* @param size The size to allocate
*/
public MemoryOperationResultCache(int size) {
this.container = new FixedSizeMap<>(size);
}
/**
* Create a cache with an unlimited size. Old entries will never be discarded. May fill up memory
*/
public MemoryOperationResultCache() {
this.container = new HashMap<>();
}
/**
* Get or set a value by its key. If a builder for the key is cached, the cached value will be returned instead.
* @param key The key to cache the value for
* @param builder A function to build a value should it not exist in the cache
* @param <TEx> An exception the builder may throw
* @return The result, either loaded from the cache or generated from the builder
* @throws TEx The builder threw an exception
*/
public synchronized <TEx extends Throwable> V get(K key, ThrowingSupplier<V, TEx> builder) throws TEx {
if (!container.containsKey(key)) container.put(key, builder.get());
return container.get(key);
}
}

View File

@ -0,0 +1,166 @@
package io.gitlab.jfronny.commons.io;
import io.gitlab.jfronny.commons.serialize.Serializer;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class JFiles {
public static void clearDirectory(Path directory) throws IOException {
clearDirectory(directory, p -> true);
}
public static void clearDirectory(Path directory, Predicate<Path> shouldDelete) throws IOException {
if (!Files.exists(directory)) return;
try {
listTo(directory, p -> {
if (Files.isDirectory(p)) {
try {
if (shouldDelete.test(p))
deleteRecursive(p, shouldDelete);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
else {
try {
if (shouldDelete.test(p))
Files.delete(p);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
} catch (Throwable t) {
throw new IOException("Could not clear directory", t);
}
}
public static void deleteRecursive(Path path) throws IOException {
deleteRecursive(path, p -> true);
}
public static void deleteRecursive(Path path, Predicate<Path> shouldDelete) throws IOException {
if (Files.isDirectory(path)) {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
FileVisitResult fv = super.visitFile(file, attrs);
if (fv != FileVisitResult.CONTINUE) return fv;
if (shouldDelete.test(file))
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
FileVisitResult fv = super.postVisitDirectory(dir, exc);
if (fv != FileVisitResult.CONTINUE) return fv;
if (shouldDelete.test(dir) && list(dir).isEmpty()) {
Files.delete(dir);
}
return FileVisitResult.CONTINUE;
}
});
}
else Files.delete(path);
}
/**
* If the source is a file, copy it to the target. If it is a directory, create the target directory if it doesn't exist and copy the source directories content there
* @param source The path to copy from
* @param destination The path to copy to
* @throws IOException Something went wrong
*/
public static void copyContent(Path source, Path destination) throws IOException {
copyContent(source, destination, StandardCopyOption.COPY_ATTRIBUTES);
}
/**
* If the source is a file, copy it to the target. If it is a directory, create the target directory if it doesn't exist and copy the source directories content there
* @param source The path to copy from
* @param target The path to copy to
* @param copyOptions Copy options to use
* @throws IOException Something went wrong
*/
public static void copyContent(Path source, Path target, CopyOption... copyOptions) throws IOException {
if (!Files.exists(target)) Files.createDirectories(target);
boolean replaceExisting = Arrays.asList(copyOptions).contains(StandardCopyOption.REPLACE_EXISTING);
if (Files.isDirectory(source)) {
listTo(source, sourceResolved -> {
Path targetResolved = target.resolve(sourceResolved.getFileName().toString());
if (Files.exists(target)) {
if (!replaceExisting) return;
if (!Files.isDirectory(sourceResolved)) Files.delete(target);
}
copyContent(sourceResolved, targetResolved, copyOptions);
});
} else if (Files.exists(source)) {
if (!Files.exists(target) || replaceExisting) Files.copy(source, target, copyOptions);
} else throw new FileNotFoundException(source.toString());
}
public static List<Path> list(Path directory) throws IOException {
try (Stream<Path> sp = Files.list(directory)) {
return sp.toList();
}
}
public static List<Path> list(Path directory, Predicate<Path> entryPredicate) throws IOException {
try (Stream<Path> sp = Files.list(directory); Stream<Path> fi = sp.filter(entryPredicate)) {
return fi.toList();
}
}
public static String[] listNames(Path directory) throws IOException {
try (Stream<Path> sp = Files.list(directory)) {
return sp
.map(p -> Files.isDirectory(p) ? p.getFileName().toString() + "/" : p.getFileName().toString())
.toArray(String[]::new);
}
}
public static <TEx extends Exception> void listTo(Path directory, ThrowingConsumer<Path, TEx> consumer) throws IOException, TEx {
try (Stream<Path> sp = Files.list(directory)) {
for (Path path : sp.toList()) consumer.accept(path);
}
}
public static <T> T readObject(Path file, Type type) throws IOException {
try (BufferedReader br = Files.newBufferedReader(file)) {
return Serializer.getInstance().deserialize(br, type);
}
}
public static <T> T readObject(Path file, Class<T> type) throws IOException {
try (BufferedReader br = Files.newBufferedReader(file)) {
return Serializer.getInstance().deserialize(br, type);
}
}
public static void writeObject(Path file, Object object) throws IOException {
try (BufferedWriter bw = Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
Serializer.getInstance().serialize(object, bw);
}
}
private static final Map<Path, MultiAccessFileSystem> zipFsCache = new HashMap<>();
public static FileSystem openZipFile(Path zip, boolean create, @Nullable ClassLoader classLoader) throws IOException, URISyntaxException {
synchronized (zipFsCache) {
if (!zipFsCache.containsKey(zip) || zipFsCache.get(zip).isClosed()) {
URI fileUri = zip.toUri();
zipFsCache.put(zip, MultiAccessFileSystem.create(new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null), create ? Map.of("create", "true") : Map.of(), classLoader));
}
return zipFsCache.get(zip).createLens();
}
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.commons.serialize;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
@ -7,12 +8,24 @@ import java.lang.reflect.Type;
* Represents an externally defined serializer. Configure the one to use here via {@code SerializerHolder.setInstance}
*/
public interface Serializer {
static Serializer getInstance() {
return SerializerHolder.getInstance();
}
static void setInstance(Serializer instance) {
SerializerHolder.setInstance(instance);
}
/**
* Serialize an object to a string
* @param object The object to serialize
* @return The serialized object as a string
*/
String serialize(Object object);
String serialize(Object object) throws IOException;
default void serialize(Object object, Appendable writer) throws IOException {
writer.append(serialize(object));
}
/**
* Deserialize data from a reader to an object
@ -22,7 +35,9 @@ public interface Serializer {
* @return The deserialized object
* @throws SerializeException if there was a problem during serialization
*/
<T> T deserialize(Reader source, Type typeOfT) throws SerializeException;
default <T> T deserialize(Reader source, Class<T> typeOfT) throws IOException {
return deserialize(source, (Type) typeOfT);
}
/**
* Deserialize data from a reader to an object
@ -32,7 +47,29 @@ public interface Serializer {
* @return The deserialized object
* @throws SerializeException if there was a problem during serialization
*/
<T> T deserialize(String source, Type typeOfT) throws SerializeException;
<T> T deserialize(Reader source, Type typeOfT) throws IOException;
/**
* Deserialize data from a reader to an object
* @param source The source from which the object should be deserialized
* @param typeOfT The type of the desired object
* @param <T> The type of the desired object
* @return The deserialized object
* @throws SerializeException if there was a problem during serialization
*/
default <T> T deserialize(String source, Class<T> typeOfT) throws IOException {
return deserialize(source, (Type) typeOfT);
}
/**
* Deserialize data from a reader to an object
* @param source The source from which the object should be deserialized
* @param typeOfT The type of the desired object
* @param <T> The type of the desired object
* @return The deserialized object
* @throws SerializeException if there was a problem during serialization
*/
<T> T deserialize(String source, Type typeOfT) throws IOException;
/**
* The MIME type for serialized data, for example application/json
@ -43,7 +80,7 @@ public interface Serializer {
/**
* An exception to be thrown when deserialization fails
*/
class SerializeException extends Exception {
class SerializeException extends IOException {
public SerializeException(String message) {
super(message);
}

View File

@ -7,7 +7,7 @@ import java.util.Objects;
/**
* Holds a serializer for use elsewhere
*/
public class SerializerHolder {
class SerializerHolder {
private static Serializer instance;
/**