You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
526 lines
12 KiB
526 lines
12 KiB
package mightypork.utils.ion;
|
|
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import mightypork.utils.Reflect;
|
|
import mightypork.utils.Support;
|
|
|
|
|
|
/**
|
|
* Universal data storage system (main API class)
|
|
*
|
|
* @author Ondřej Hruška (MightyPork)
|
|
*/
|
|
public class Ion {
|
|
|
|
private static final int RESERVED_LOW = 0;
|
|
private static final int RESERVED_HIGH = 49;
|
|
|
|
private static final int RANGE_LOW = 0;
|
|
private static final int RANGE_HIGH = 255;
|
|
|
|
// marks for object saving
|
|
/** Null mark */
|
|
static final int NULL = 0;
|
|
/** Boolean mark */
|
|
static final int BOOLEAN = 1;
|
|
/** Byte mark */
|
|
static final int BYTE = 2;
|
|
/** Character mark */
|
|
static final int CHAR = 3;
|
|
/** Short mark */
|
|
static final int SHORT = 4;
|
|
/** Integer mark */
|
|
static final int INT = 5;
|
|
/** Long mark */
|
|
static final int LONG = 6;
|
|
/** Float mark */
|
|
static final int FLOAT = 7;
|
|
/** Double mark */
|
|
static final int DOUBLE = 8;
|
|
/** String mark */
|
|
static final int STRING = 9;
|
|
/** Boolean array mark */
|
|
static final int BOOLEAN_ARRAY = 10;
|
|
/** Byte array mark */
|
|
static final int BYTE_ARRAY = 11;
|
|
/** Character array mark */
|
|
static final int CHAR_ARRAY = 12;
|
|
/** Short array mark */
|
|
static final int SHORT_ARRAY = 13;
|
|
/** Integer array mark */
|
|
static final int INT_ARRAY = 14;
|
|
/** Long array mark */
|
|
static final int LONG_ARRAY = 15;
|
|
/** Float array mark */
|
|
static final int FLOAT_ARRAY = 16;
|
|
/** Double array mark */
|
|
static final int DOUBLE_ARRAY = 17;
|
|
/** String array mark */
|
|
static final int STRING_ARRAY = 18;
|
|
/** Entry mark - start of map or sequence entry */
|
|
static final int ENTRY = 19;
|
|
/** End mark - end of sequence or map */
|
|
static final int END = 20;
|
|
/** Bundle */
|
|
static final int ION_BUNDLE = 21;
|
|
/** Sequence wrapper for bundle */
|
|
static final int SEQUENCE_WRAPPER = 22;
|
|
/** Map wrapper for bundle */
|
|
static final int MAP_WRAPPER = 23;
|
|
/** Sequence saved as object */
|
|
public static final int SEQUENCE = 24;
|
|
/** Map saved as object */
|
|
public static final int MAP = 25;
|
|
/** Array of arbitrary objects */
|
|
public static final int OBJECT_ARRAY = 26;
|
|
|
|
/** Ionizables<Mark, Class> */
|
|
private static Map<Integer, Class<?>> markToClass = new HashMap<>();
|
|
private static Map<Class<?>, Integer> classToMark = new HashMap<>();
|
|
|
|
private static Map<Class<?>, IonizerBinary<?>> ionizersBinary = new HashMap<>();
|
|
private static Map<Class<?>, IonizerBundled<?>> ionizersBundled = new HashMap<>();
|
|
|
|
private static boolean reservedMarkChecking;
|
|
|
|
static {
|
|
reservedMarkChecking = false;
|
|
|
|
// register built-ins
|
|
register(ION_BUNDLE, IonDataBundle.class);
|
|
register(SEQUENCE_WRAPPER, IonSequenceWrapper.class);
|
|
register(MAP_WRAPPER, IonMapWrapper.class);
|
|
|
|
reservedMarkChecking = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Register a type for writing/loading.
|
|
*
|
|
* @param mark binary ION mark
|
|
* @param objClass class of the registered object
|
|
*/
|
|
public static void register(int mark, Class<?> objClass)
|
|
{
|
|
if (!IonBinary.class.isAssignableFrom(objClass)) {
|
|
if (!IonBundled.class.isAssignableFrom(objClass)) {
|
|
throw new IllegalArgumentException("Cannot register directly: " + Support.str(objClass));
|
|
}
|
|
}
|
|
|
|
assertHasImplicitConstructor(objClass);
|
|
|
|
registerUsingMark(mark, objClass);
|
|
}
|
|
|
|
|
|
/**
|
|
* Try to register a type using a static final ION_MARK int field.
|
|
*
|
|
* @param objClass type class
|
|
*/
|
|
public static void register(Class<?> objClass)
|
|
{
|
|
if (!IonBinary.class.isAssignableFrom(objClass)) {
|
|
if (!IonBundled.class.isAssignableFrom(objClass)) {
|
|
throw new IllegalArgumentException("Cannot register directly: " + Support.str(objClass));
|
|
}
|
|
}
|
|
|
|
assertHasImplicitConstructor(objClass);
|
|
registerUsingConstant(objClass);
|
|
}
|
|
|
|
|
|
private static void registerUsingMark(int mark, Class<?> objClass)
|
|
{
|
|
assertMarkAvailable(mark, objClass);
|
|
|
|
markToClass.put(mark, objClass);
|
|
classToMark.put(objClass, mark);
|
|
}
|
|
|
|
|
|
public static void registerUsingConstant(Class<?> objClass)
|
|
{
|
|
try {
|
|
final int mark = ((Number) Reflect.getConstantFieldValue(objClass, "ION_MARK")).intValue();
|
|
|
|
registerUsingMark(mark, objClass);
|
|
|
|
} catch (final Exception e) {
|
|
throw new RuntimeException("Could not register " + Support.str(objClass) + " using an ION_MARK field.", e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Register new binary ionizer.
|
|
*
|
|
* @param mark binary ION mark
|
|
* @param ionizer ionizer
|
|
*/
|
|
public static void registerIndirect(int mark, IonizerBinary<?> ionizer)
|
|
{
|
|
final Class<?> objClass = Reflect.getGenericParameters(ionizer.getClass())[0];
|
|
|
|
registerUsingMark(mark, objClass);
|
|
|
|
ionizersBinary.put(objClass, ionizer);
|
|
}
|
|
|
|
|
|
/**
|
|
* Register new bundled ionizer.
|
|
*
|
|
* @param mark binary ION mark
|
|
* @param ionizer ionizer
|
|
*/
|
|
public static void registerIndirect(int mark, IonizerBundled<?> ionizer)
|
|
{
|
|
final Class<?> objClass = Reflect.getGenericParameters(ionizer.getClass())[0];
|
|
|
|
registerUsingMark(mark, objClass);
|
|
|
|
ionizersBundled.put(objClass, ionizer);
|
|
}
|
|
|
|
|
|
private static void assertHasImplicitConstructor(Class<?> objClass)
|
|
{
|
|
try {
|
|
objClass.getConstructor();
|
|
} catch (NoSuchMethodException | SecurityException e) {
|
|
throw new RuntimeException("Class " + objClass + " doesn't have an implicit constructor.");
|
|
}
|
|
}
|
|
|
|
|
|
private static void assertMarkAvailable(int mark, Class<?> objClass)
|
|
{
|
|
// negative marks are allowed.
|
|
if (mark > RANGE_HIGH) throw new IllegalArgumentException("Mark must be < 256.");
|
|
if (mark < RANGE_LOW) throw new IllegalArgumentException("Mark must be positive.");
|
|
|
|
if (reservedMarkChecking && isMarkReserved(mark)) {
|
|
throw new IllegalArgumentException("Marks " + RESERVED_LOW + ".." + RESERVED_HIGH + " are reserved.");
|
|
}
|
|
|
|
if (markToClass.containsKey(mark)) {
|
|
throw new IllegalArgumentException("Mark " + mark + " is already in use.");
|
|
}
|
|
|
|
if (classToMark.containsKey(objClass)) {
|
|
throw new IllegalArgumentException(Support.str(objClass) + " is already registered.");
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Load binary from file and cast.
|
|
*/
|
|
public static <T> T fromFile(String path) throws IOException
|
|
{
|
|
return fromFile(new File(path));
|
|
}
|
|
|
|
|
|
/**
|
|
* Load binary from file and cast.
|
|
*/
|
|
public static <T> T fromFile(File file) throws IOException
|
|
{
|
|
try(InputStream in = new FileInputStream(file)) {
|
|
return fromStream(in);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Write binary to file with mark.
|
|
*/
|
|
public static void toFile(String path, Object obj) throws IOException
|
|
{
|
|
toFile(new File(path), obj);
|
|
}
|
|
|
|
|
|
/**
|
|
* Write binary to file with mark.
|
|
*/
|
|
public static void toFile(File file, Object obj) throws IOException
|
|
{
|
|
try(OutputStream out = new FileOutputStream(file)) {
|
|
|
|
toStream(out, obj);
|
|
|
|
out.flush();
|
|
} catch (final Exception e) {
|
|
throw new IOException("Error writing to ION file.", e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Load object from stream based on mark, try to cast.
|
|
*/
|
|
public static <T> T fromStream(InputStream in) throws IOException
|
|
{
|
|
try(final IonInput inp = new IonInput(in)) {
|
|
return (T) inp.readObject();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Write object to output with a mark.
|
|
*/
|
|
public static void toStream(OutputStream out, Object obj) throws IOException
|
|
{
|
|
try(IonOutput iout = new IonOutput(out)) {
|
|
iout.writeObject(obj);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get ion input
|
|
*
|
|
* @param path file path to read
|
|
* @return input
|
|
* @throws IOException
|
|
*/
|
|
public static IonInput getInput(String path) throws IOException
|
|
{
|
|
return getInput(new File(path));
|
|
}
|
|
|
|
|
|
/**
|
|
* Get ion input
|
|
*
|
|
* @param file file to read
|
|
* @return input
|
|
* @throws IOException
|
|
*/
|
|
@SuppressWarnings("resource")
|
|
public static IonInput getInput(File file) throws IOException
|
|
{
|
|
return new IonInput(new FileInputStream(file));
|
|
}
|
|
|
|
|
|
/**
|
|
* Get ion output
|
|
*
|
|
* @param path file path to write
|
|
* @return output
|
|
* @throws IOException
|
|
*/
|
|
public static IonOutput getOutput(String path) throws IOException
|
|
{
|
|
return getOutput(new File(path));
|
|
}
|
|
|
|
|
|
/**
|
|
* Get ion output
|
|
*
|
|
* @param file file to write
|
|
* @return output
|
|
* @throws IOException
|
|
*/
|
|
@SuppressWarnings("resource")
|
|
public static IonOutput getOutput(File file) throws IOException
|
|
{
|
|
return new IonOutput(new FileOutputStream(file));
|
|
}
|
|
|
|
|
|
/**
|
|
* Create new bundle and write the object to it.
|
|
*/
|
|
public static IonDataBundle wrapBundled(IonBundled content) throws IOException
|
|
{
|
|
final IonDataBundle ib = new IonDataBundle();
|
|
content.save(ib);
|
|
return ib;
|
|
}
|
|
|
|
|
|
/**
|
|
* Try to unwrap an object from bundle. The object class must have implicit
|
|
* accessible constructor.
|
|
*
|
|
* @param bundle unwrapped bundle
|
|
* @param objClass class of desired object
|
|
* @return the object unwrapped
|
|
* @throws IOException
|
|
*/
|
|
public static <T extends IonBundled> T unwrapBundled(IonDataBundle bundle, Class<? extends T> objClass) throws IOException
|
|
{
|
|
try {
|
|
final T inst = objClass.newInstance();
|
|
inst.load(bundle);
|
|
return inst;
|
|
} catch (InstantiationException | IllegalAccessException e) {
|
|
throw new IOException("Could not instantiate " + Support.str(objClass) + ".");
|
|
}
|
|
}
|
|
|
|
|
|
static Class<?> getClassForMark(int mark)
|
|
{
|
|
return markToClass.get(mark);
|
|
}
|
|
|
|
|
|
static IonizerBinary<?> getIonizerBinaryForClass(Class<?> clz)
|
|
{
|
|
return ionizersBinary.get(clz);
|
|
}
|
|
|
|
|
|
static IonizerBundled<?> getIonizerBundledForClass(Class<?> clz)
|
|
{
|
|
return ionizersBundled.get(clz);
|
|
}
|
|
|
|
|
|
public static int getMark(Object object)
|
|
{
|
|
assertRegistered(object);
|
|
|
|
return classToMark.get(object.getClass());
|
|
}
|
|
|
|
|
|
/**
|
|
* @return true if the mark is for a registered {@link IonBinary} object
|
|
*/
|
|
static boolean isMarkForBinary(int mark)
|
|
{
|
|
if (!markToClass.containsKey(mark)) return false;
|
|
|
|
return IonBinary.class.isAssignableFrom(markToClass.get(mark));
|
|
}
|
|
|
|
|
|
/**
|
|
* @return true if the mark is for a registered {@link IonBinary} object
|
|
*/
|
|
static boolean isMarkForBundled(int mark)
|
|
{
|
|
if (!markToClass.containsKey(mark)) return false;
|
|
|
|
return IonBundled.class.isAssignableFrom(markToClass.get(mark));
|
|
}
|
|
|
|
|
|
/**
|
|
* @return true if the mark is for a registered indirectly saved binary
|
|
* object
|
|
*/
|
|
static boolean isMarkForIndirectBinary(int mark)
|
|
{
|
|
if (!markToClass.containsKey(mark)) return false;
|
|
|
|
return ionizersBinary.containsKey(markToClass.get(mark));
|
|
}
|
|
|
|
|
|
/**
|
|
* @return true if the mark is for a registered indirectly saved bundled
|
|
* object
|
|
*/
|
|
static boolean isMarkForIndirectBundled(int mark)
|
|
{
|
|
if (!markToClass.containsKey(mark)) return false;
|
|
|
|
return ionizersBundled.containsKey(markToClass.get(mark));
|
|
}
|
|
|
|
|
|
/**
|
|
* @return true if the mark is reserved for internal use
|
|
*/
|
|
static boolean isMarkReserved(int mark)
|
|
{
|
|
return mark >= RESERVED_LOW && mark <= RESERVED_HIGH;
|
|
}
|
|
|
|
|
|
/**
|
|
* @return true if the mark is for a registered {@link IonBinary} object
|
|
*/
|
|
static boolean isRegistered(Object object)
|
|
{
|
|
final Class<?> clazz = object.getClass();
|
|
if (classToMark.containsKey(clazz)) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Make sure object is registered in the table.
|
|
*
|
|
* @throws IOException if not registered or class mismatch
|
|
*/
|
|
static void assertRegistered(Object obj)
|
|
{
|
|
if (!isRegistered(obj)) {
|
|
throw new RuntimeException("Type not registered: " + Support.str(obj.getClass()));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* For get all external registered types - just like if the class was
|
|
* freshly loaded. Can be used for unit tests.
|
|
*/
|
|
public static void reset()
|
|
{
|
|
final List<Integer> toRemove = new ArrayList<>();
|
|
|
|
// remove direct
|
|
for (final Integer mark : markToClass.keySet()) {
|
|
if (!isMarkReserved(mark)) {
|
|
toRemove.add(mark);
|
|
}
|
|
}
|
|
|
|
for (final int i : toRemove) {
|
|
|
|
final Class<?> clz = markToClass.remove(i);
|
|
|
|
classToMark.remove(clz);
|
|
ionizersBinary.remove(clz);
|
|
ionizersBundled.remove(clz);
|
|
}
|
|
}
|
|
|
|
|
|
public static boolean isObjectIndirectBundled(Object obj)
|
|
{
|
|
return ionizersBundled.containsKey(obj.getClass());
|
|
}
|
|
|
|
|
|
public static boolean isObjectIndirectBinary(Object obj)
|
|
{
|
|
return ionizersBinary.containsKey(obj.getClass());
|
|
}
|
|
}
|
|
|