Add a dynamic serializer

This commit is contained in:
Aria 2023-06-15 23:33:13 -07:00
parent 4f9a39fc4d
commit df9525fca6
11 changed files with 612 additions and 0 deletions

View file

@ -0,0 +1,11 @@
package dev.zontreck.ariaslib.xml;
/**
* Serialization or Deserialization has completed.
*
* The method takes 1 argument.
*
* Boolean: True for deserialization.
*/
public @interface Completed {
}

View file

@ -0,0 +1,7 @@
package dev.zontreck.ariaslib.xml;
/**
* Used on a class to indicate that it is serializable by the dynamic serializer.
*/
public @interface DynSerial {
}

View file

@ -0,0 +1,58 @@
package dev.zontreck.ariaslib.xml;
import java.io.ByteArrayInputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
/**
* Deserializes objects!
*
* YOU MUST HAVE A NO-PARAMETER CONSTRUCTOR
*/
public class DynamicDeserializer
{
/**
* Constructs and deserializes an object from serialized data
*/
public static <T> T doDeserialize(Class<T> clazz, byte[] data) throws Exception {
ByteArrayInputStream BAIS = new ByteArrayInputStream ( data );
XmlDeserializer deserial = new XmlDeserializer ( BAIS );
return deserialize( ( Map<String, Object> ) deserial.readSettings () , clazz);
}
private static <T> T deserialize(Map<String, Object> map, Class<T> clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
if(!clazz.isAnnotationPresent ( DynSerial.class ))return null;
T object = clazz.getDeclaredConstructor ( ).newInstance ( );
Field[] fields = clazz.getDeclaredFields ();
for (
Field field :
fields
) {
if(XmlStreamWriter.isSerializableType ( field.getType () ))
{
field.set ( object, map.get ( field.getName () ) );
}else {
Object tmp = deserialize ( (Map<String, Object> ) map.get ( field.getName () ), field.getType ());
field.set ( object, tmp );
}
}
Method[] mth = clazz.getDeclaredMethods ();
for (
Method mt :
mth
) {
if(mt.isAnnotationPresent ( Completed.class ))
{
mt.invoke ( object, true );
}
}
return object;
}
}

View file

@ -0,0 +1,73 @@
package dev.zontreck.ariaslib.xml;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class DynamicSerializer {
/**
* Serializes the object instance
*
* @param inst The class object to serialize
* @return A byte array of serialized data
*/
public static byte[] doSerialize(Object inst) throws InvocationTargetException, IllegalAccessException {
Map<String, Object> ret = serialize(inst);
ByteArrayOutputStream baos = new ByteArrayOutputStream ( );
XmlSerializer ser = new XmlSerializer ( baos );
ser.writeValue ( ret );
return baos.toByteArray ();
}
private static Map<String,Object> serialize ( Object inst ) throws InvocationTargetException, IllegalAccessException {
Class<?> clazz = inst.getClass ( );
if(!clazz.isAnnotationPresent ( DynSerial.class ))
return null;
Method[] mth = clazz.getDeclaredMethods ( );
Method onComplete = null;
for (
Method mt :
mth
) {
if ( mt.isAnnotationPresent ( PreSerialize.class ) ) {
mt.invoke ( inst );
}
if(mt.isAnnotationPresent ( Completed.class ))
onComplete = mt;
}
Field[] fields = clazz.getDeclaredFields ();
Map<String,Object> ret = new HashMap<> ( );
for (
Field field :
fields
) {
if(field.isAnnotationPresent ( IgnoreSerialization.class )) continue;
Object fieldVal = field.get ( inst );
String fieldName = field.getName ();
if(XmlStreamWriter.isSerializableType ( fieldVal ))
{
ret.put ( fieldName, fieldVal );
}else {
Map<String,Object> TMP = serialize(fieldVal);
ret.put ( fieldName, TMP );
}
}
if(onComplete != null)
onComplete.invoke ( inst, false );
return ret;
}
}

View file

@ -0,0 +1,8 @@
package dev.zontreck.ariaslib.xml;
/**
* Marks an element to be ignored completely by the serializer or deserializer.
*/
public @interface IgnoreSerialization
{
}

View file

@ -0,0 +1,9 @@
package dev.zontreck.ariaslib.xml;
/**
* To be set on a method, and will invoke that method prior to serialization beginning.
*
* Preparations should be made here
*/
public @interface PreSerialize {
}

View file

@ -0,0 +1,44 @@
package dev.zontreck.ariaslib.xml;
import dev.zontreck.ariaslib.xmlrpc.XmlRpcStreamReader;
import javax.xml.stream.XMLStreamException;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
public class XmlDeserializer {
private XmlStreamReader xmlStreamReader;
public XmlDeserializer ( InputStream inputStream ) throws Exception {
xmlStreamReader = new XmlStreamReader ( inputStream );
}
public String skipXmlHeader(String xml) {
int startIndex = xml.indexOf("<?xml");
if (startIndex >= 0) {
int endIndex = xml.indexOf("?>", startIndex);
if (endIndex >= 0) {
return xml.substring(endIndex + 2);
}
}
return xml;
}
public XmlDeserializer ( String xml ) throws Exception {
byte[] xmlBytes = xml.getBytes ( );
ByteArrayInputStream inputStream = new ByteArrayInputStream ( xmlBytes );
xmlStreamReader = new XmlStreamReader ( inputStream );
}
public Object readSettings()
{
try {
return xmlStreamReader.readSettings ();
} catch ( XMLStreamException e ) {
throw new RuntimeException ( e );
}
}
public void close ( ) throws Exception {
xmlStreamReader.close ( );
}
}

View file

@ -0,0 +1,24 @@
package dev.zontreck.ariaslib.xml;
public class XmlException extends Exception {
public XmlException ( int code , String message ) {
super ( message );
FaultCode = code;
FaultString = message;
}
public final String FaultString;
public final int FaultCode;
@Override
public String toString ( ) {
StringBuilder sb = new StringBuilder ( );
sb.append ( "Code: " +FaultCode);
sb.append ( "\nMessage: " +FaultString);
sb.append ( "\n\n" );
return sb.toString ();
}
}

View file

@ -0,0 +1,29 @@
package dev.zontreck.ariaslib.xml;
import dev.zontreck.ariaslib.xmlrpc.XmlRpcStreamWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
public class XmlSerializer {
private XmlStreamWriter writer;
public XmlSerializer ( OutputStream outputStream ) {
this.writer = new XmlStreamWriter ( outputStream );
}
public void writeValue(Object value)
{
try {
writer.writeSettings ( value );
} catch ( IOException e ) {
throw new RuntimeException ( e );
}
}
public void close ( ) throws IOException {
writer.close ( );
}
}

View file

@ -0,0 +1,174 @@
package dev.zontreck.ariaslib.xml;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class XmlStreamReader {
private XMLStreamReader xmlStreamReader;
public XmlStreamReader ( InputStream inputStream ) throws XMLStreamException {
XMLInputFactory inputFactory = XMLInputFactory.newInstance ( );
xmlStreamReader = inputFactory.createXMLStreamReader ( inputStream );
}
private String CURRENT_TAG_NAME;
private int ELEM_TYPE;
public boolean nextTag ( ) throws XMLStreamException {
while ( xmlStreamReader.hasNext ( ) ) {
int eventType = xmlStreamReader.next ( );
if ( eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT ) {
CURRENT_TAG_NAME = getLocalName ( );
ELEM_TYPE = xmlStreamReader.getEventType ( );
return true;
}
}
return false;
}
public Object readSettings() throws XMLStreamException {
nextTag ();
require ( XMLStreamConstants.START_ELEMENT, null, "settings" );
return deserializeValue ();
}
public String getLocalName ( ) {
return xmlStreamReader.getLocalName ( );
}
public String getElementText ( ) throws XMLStreamException {
return xmlStreamReader.getElementText ( );
}
public void require ( int type , String namespaceURI , String localName ) throws XMLStreamException {
xmlStreamReader.require ( type , namespaceURI , localName );
}
public Object deserializeValue ( ) throws XMLStreamException {
nextTag ( );
int eventType = xmlStreamReader.getEventType ( );
if ( eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA ) {
return xmlStreamReader.getText ( );
}
else if ( eventType == XMLStreamConstants.START_ELEMENT ) {
String localName = xmlStreamReader.getLocalName ( );
switch ( localName ) {
case "string":
return deserializeString ( );
case "i4":
return deserializeByte ();
case "int":
return deserializeInt ( );
case "double":
return deserializeDouble ( );
case "bool":
return deserializeBoolean ( );
case "array":
return deserializeArray ( );
case "struct":
return deserializeStruct ( );
case "nil":
return null;
case "i8":
return deserializeLong ( );
case "float":
return deserializeFloat();
default:
throw new IllegalArgumentException ( "Unsupported element: " + localName );
}
}
else {
throw new IllegalArgumentException ( "Unexpected event type: " + eventType );
}
}
private String deserializeString ( ) throws XMLStreamException {
return getElementText ( );
}
private int deserializeInt ( ) throws XMLStreamException {
return Integer.parseInt ( getElementText ( ) );
}
private byte deserializeByte ( ) throws XMLStreamException {
return Byte.parseByte ( getElementText ( ) );
}
private long deserializeLong ( ) throws XMLStreamException {
return Long.parseLong ( getElementText ( ) );
}
private float deserializeFloat() throws XMLStreamException {
return Float.parseFloat ( getElementText () );
}
private double deserializeDouble ( ) throws XMLStreamException {
return Double.parseDouble ( getElementText ( ) );
}
private boolean deserializeBoolean ( ) throws XMLStreamException {
return Boolean.parseBoolean ( getElementText ( ) );
}
private Object[] deserializeArray ( ) throws XMLStreamException {
List<Object> arr = new ArrayList<> ( );
while ( nextTag ( ) ) {
if ( CURRENT_TAG_NAME.equals ( "data" ) && ELEM_TYPE == XMLStreamConstants.END_ELEMENT ) {
break;
}
else if ( CURRENT_TAG_NAME.equals ( "value" ) && ELEM_TYPE == XMLStreamConstants.START_ELEMENT ) {
arr.add ( deserializeValue ( ) );
}
}
return arr.toArray ( );
}
private Map<String, Object> deserializeStruct ( ) throws XMLStreamException {
Map<String, Object> struct = new HashMap<> ( );
String name = null;
while ( nextTag ( ) ) {
if ( xmlStreamReader.getLocalName ( ).equals ( "member" ) ) {
name = null;
}
else if ( xmlStreamReader.getLocalName ( ).equals ( "name" ) ) {
name = getElementText ( );
}
else if ( xmlStreamReader.getLocalName ( ).equals ( "value" ) && xmlStreamReader.getEventType ( ) == XMLStreamConstants.START_ELEMENT ) {
if ( name != null ) {
Object value = deserializeValue ( );
struct.put ( name , value );
}
}
else if ( CURRENT_TAG_NAME.equals ( "struct" ) && xmlStreamReader.getEventType ( ) == XMLStreamConstants.END_ELEMENT ) {
break;
}
}
return struct;
}
public static String skipXmlHeader ( String xml ) {
int startIndex = xml.indexOf ( "<?xml" );
if ( startIndex >= 0 ) {
int endIndex = xml.indexOf ( "?>" , startIndex );
if ( endIndex >= 0 ) {
return xml.substring ( endIndex + 2 );
}
}
return xml;
}
public void close ( ) throws XMLStreamException {
xmlStreamReader.close ( );
}
}

View file

@ -0,0 +1,175 @@
package dev.zontreck.ariaslib.xml;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class XmlStreamWriter {
private static final String XML_VERSION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
private static final String METHOD_CALL_START_TAG = "<methodCall>";
private static final String METHOD_CALL_END_TAG = "</methodCall>";
private static final String METHOD_NAME_START_TAG = "<methodName>";
private static final String METHOD_NAME_END_TAG = "</methodName>";
private static final String METHOD_RESPONSE_START_TAG = "<methodResponse>";
private static final String METHOD_RESPONSE_END_TAG = "</methodResponse>";
private static final String PARAMS_START_TAG = "<params>";
private static final String PARAMS_END_TAG = "</params>";
private static final String PARAM_START_TAG = "<param>";
private static final String PARAM_END_TAG = "</param>";
private static final String VALUE_START_TAG = "<value>";
private static final String VALUE_END_TAG = "</value>";
private static final String ARRAY_START_TAG = "<array>";
private static final String ARRAY_END_TAG = "</array>";
private static final String DATA_START_TAG = "<data>";
private static final String DATA_END_TAG = "</data>";
private static final String STRUCT_START_TAG = "<struct>";
private static final String STRUCT_END_TAG = "</struct>";
private static final String MEMBER_START_TAG = "<member>";
private static final String MEMBER_END_TAG = "</member>";
private static final String NAME_START_TAG = "<name>";
private static final String NAME_END_TAG = "</name>";
private static final String SETTINGS_START_TAG = "<settings>";
private static final String SETTINGS_END_TAG = "</settings>";
private Writer writer;
public XmlStreamWriter ( OutputStream outputStream ) {
this.writer = new OutputStreamWriter ( outputStream );
}
public static boolean isSerializableType ( Object value ) {
return isSerializableType ( value.getClass () );
}
public static boolean isSerializableType ( Class<?> clazz ) {
List<Class<?>> valid = new ArrayList<> ( );
valid.add ( null );
valid.add ( String.class );
valid.add ( Integer.class );
valid.add ( Long.class );
valid.add ( Double.class );
valid.add ( Boolean.class );
valid.add ( List.class );
valid.add ( Map.class );
valid.add ( Byte.class );
valid.add ( Float.class );
if(valid.contains ( clazz ))return true;
else return false;
}
public void writeSettings(Object value) throws IOException {
writer.write ( XML_VERSION );
writer.write ( SETTINGS_START_TAG );
writeValue ( value );
writer.write ( SETTINGS_END_TAG );
writer.flush ();
}
private void writeValue ( Object value ) throws IOException {
if ( value == null ) {
writer.write ( VALUE_START_TAG );
writer.write ( "<nil/>" );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof String ) {
writer.write ( VALUE_START_TAG );
writer.write ( "<string>" );
writer.write ( escapeXml ( ( String ) value ) );
writer.write ( "</string>" );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof Integer ) {
writer.write ( VALUE_START_TAG );
writer.write ( "<i4>" );
writer.write ( value.toString ( ) );
writer.write ( "</i4>" );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof Long ) {
writer.write ( VALUE_START_TAG );
writer.write ( "<i8>" );
writer.write ( value.toString ( ) ); // Save it as a int for now due to unclear handling
writer.write ( "</i8>" );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof Double ) {
writer.write ( VALUE_START_TAG );
writer.write ( "<double>" );
writer.write ( value.toString ( ) );
writer.write ( "</double>" );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof Boolean ) {
writer.write ( VALUE_START_TAG );
writer.write ( "<bool>" );
writer.write ( value.toString ( ) );
writer.write ( "</bool>" );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof List ) {
writer.write ( VALUE_START_TAG );
writer.write ( ARRAY_START_TAG );
writer.write ( DATA_START_TAG );
List<?> list = ( List<?> ) value;
for ( Object item : list ) {
writeValue ( item );
}
writer.write ( DATA_END_TAG );
writer.write ( ARRAY_END_TAG );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof Map ) {
writer.write ( VALUE_START_TAG );
writer.write ( STRUCT_START_TAG );
Map<?, ?> map = ( Map<?, ?> ) value;
for ( Map.Entry<?, ?> entry : map.entrySet ( ) ) {
writer.write ( MEMBER_START_TAG );
writer.write ( NAME_START_TAG );
writer.write ( escapeXml ( entry.getKey ( ).toString ( ) ) );
writer.write ( NAME_END_TAG );
writeValue ( entry.getValue ( ) );
writer.write ( MEMBER_END_TAG );
}
writer.write ( STRUCT_END_TAG );
writer.write ( VALUE_END_TAG );
}
else if ( value instanceof Byte ) {
writer.write ( VALUE_START_TAG );
writer.write ( "<i1>" );
writer.write ( value.toString ( ) ); // Treat as a integer for now
writer.write ( "</i1>" );
writer.write ( VALUE_END_TAG );
}
else if(value instanceof Float)
{
writer.write ( VALUE_START_TAG );
writer.write ( "<float>" );
writer.write ( value.toString () );
writer.write ( "</float>" );
writer.write ( VALUE_END_TAG );
}
else {
throw new IllegalArgumentException ( "Unsupported data type: " + value.getClass ( ).getName ( ) );
}
}
private String escapeXml ( String value ) {
return value
.replace ( "&" , "&amp;" )
.replace ( "<" , "&lt;" )
.replace ( ">" , "&gt;" )
.replace ( "\"" , "&quot;" )
.replace ( "'" , "&apos;" );
}
public void close ( ) throws IOException {
writer.close ( );
}
}