package com.ripple.core.coretypes; import com.ripple.core.coretypes.hash.Hash128; import com.ripple.core.coretypes.hash.Hash160; import com.ripple.core.coretypes.hash.Hash256; import com.ripple.core.coretypes.uint.UInt16; import com.ripple.core.coretypes.uint.UInt32; import com.ripple.core.coretypes.uint.UInt64; import com.ripple.core.coretypes.uint.UInt8; import com.ripple.core.fields.*; import com.ripple.core.formats.Format; import com.ripple.core.formats.LEFormat; import com.ripple.core.formats.TxFormat; import com.ripple.core.serialized.*; import com.ripple.core.serialized.enums.EngineResult; import com.ripple.core.serialized.enums.LedgerEntryType; import com.ripple.core.serialized.enums.TransactionType; import org.json.JSONObject; import java.util.EnumMap; import java.util.Iterator; import java.util.TreeMap; public class STObject implements SerializedType, Iterable<Field> { // Internally the fields are stored in a TreeMap public static class FieldsMap extends TreeMap<Field, SerializedType> {} // There's no nice predicates public static interface FieldFilter { boolean evaluate(Field a); } protected FieldsMap fields; public Format format; public STObject() { fields = new FieldsMap(); } public STObject(FieldsMap fieldsMap) { fields = fieldsMap; } public static STObject fromJSON(String offerJson) { return fromJSONObject(new JSONObject(offerJson)); } public static STObject fromJSONObject(JSONObject json) { return translate.fromJSONObject(json); } public static STObject fromHex(String hex) { return STObject.translate.fromHex(hex); } @Override public Iterator<Field> iterator() { return fields.keySet().iterator(); } public String prettyJSON() { return translate.toJSONObject(this).toString(4); } /** * @return a subclass of STObject using the same fields * * If the object has a TransactionType or LedgerEntryType * then we can up)grade to a child class, with more specific * helper methods, and we can use `instanceof` to great effect. */ public static STObject formatted(STObject source) { return STObjectFormatter.doFormatted(source); } public Format getFormat() { if (format == null) computeFormat(); return format; } public static class FormatException extends RuntimeException { public FormatException(String s) { super(s); } } public void checkFormat() { Format fmt = getFormat(); EnumMap<Field, Format.Requirement> requirements = fmt.requirements(); for (Field field : this) { if (!requirements.containsKey(field)) { throw new FormatException(fmt.name() + " doesn't have field: " + field); } } for (Field field : requirements.keySet()) { Format.Requirement req = requirements.get(field); if (!has(field)) { if (req == Format.Requirement.REQUIRED) { throw new FormatException(fmt.name() + " requires " + field + " of type " + field.getType()); } } else { SerializedType type = get(field); if (type.type() != field.getType()) { if (!(field.getType() == Type.Hash160 && type.type() == Type.AccountID)) { throw new FormatException(type.toString() + " is not " + field.getType()); } } } } } public void setFormat(Format format) { this.format = format; } private void computeFormat() { UInt16 tt = get(UInt16.TransactionType); if (tt != null) { setFormat(TxFormat.fromNumber(tt)); } UInt16 let = get(UInt16.LedgerEntryType); if (let != null) { setFormat(LEFormat.fromNumber(let)); } } public FieldsMap getFields() { return fields; } public SerializedType get(Field field) { return fields.get(field); } public static EngineResult engineResult(STObject obj) { return (EngineResult) obj.get(Field.TransactionResult); } static public LedgerEntryType ledgerEntryType(STObject obj) { return (LedgerEntryType) obj.get(Field.LedgerEntryType); } public static TransactionType transactionType(STObject obj) { return (TransactionType) obj.get(Field.TransactionType); } public SerializedType remove(Field f) { return fields.remove(f); } public boolean has(Field f) { return fields.containsKey(f); } public <T extends HasField> boolean has(T hf) { return has(hf.getField()); } public void put (UInt8Field f, UInt8 o) {put(f.getField(), o);} public void put (Vector256Field f, Vector256 o) {put(f.getField(), o);} public void put (BlobField f, Blob o) {put(f.getField(), o);} public void put (UInt64Field f, UInt64 o) {put(f.getField(), o);} public void put (UInt32Field f, UInt32 o) {put(f.getField(), o);} public void put (UInt16Field f, UInt16 o) {put(f.getField(), o);} public void put (PathSetField f, PathSet o) {put(f.getField(), o);} public void put (STObjectField f, STObject o) {put(f.getField(), o);} public void put (Hash256Field f, Hash256 o) {put(f.getField(), o);} public void put (Hash160Field f, Hash160 o) {put(f.getField(), o);} public void put (Hash128Field f, Hash128 o) {put(f.getField(), o);} public void put (STArrayField f, STArray o) {put(f.getField(), o);} public void put (AmountField f, Amount o) {put(f.getField(), o);} public void put (AccountIDField f, AccountID o) {put(f.getField(), o);} public <T extends HasField> void putTranslated(T f, Object value) { putTranslated(f.getField(), value); } public <T extends HasField> STObject as(T f, Object value) { putTranslated(f.getField(), value); return this; } public void put(Field f, SerializedType value) { fields.put(f, value); } public void putTranslated(Field f, Object value) { TypeTranslator typeTranslator = Translators.forField(f); SerializedType st = null; try { st = typeTranslator.fromValue(value); } catch (Exception e) { throw new RuntimeException("Couldn't put `" +value+ "` into field `" + f + "`\n" + e.toString()); } fields.put(f, st); } public AccountID get(AccountIDField f) { return (AccountID) get(f.getField()); } public Amount get(AmountField f) { return (Amount) get(f.getField()); } public STArray get(STArrayField f) { return (STArray) get(f.getField()); } public Hash128 get(Hash128Field f) { return (Hash128) get(f.getField()); } public Hash160 get(Hash160Field f) { return (Hash160) get(f.getField()); } public Hash256 get(Hash256Field f) { return (Hash256) get(f.getField()); } public STObject get(STObjectField f) { return (STObject) get(f.getField()); } public PathSet get(PathSetField f) { return (PathSet) get(f.getField()); } public UInt16 get(UInt16Field f) { return (UInt16) get(f.getField()); } public UInt32 get(UInt32Field f) { return (UInt32) get(f.getField()); } public UInt64 get(UInt64Field f) { return (UInt64) get(f.getField()); } public UInt8 get(UInt8Field f) { return (UInt8) get(f.getField()); } public Vector256 get(Vector256Field f) { return (Vector256) get(f.getField()); } public Blob get(BlobField f) { return (Blob) get(f.getField()); } // SerializedTypes implementation @Override public Object toJSON() { return translate.toJSON(this); } public JSONObject toJSONObject() { return translate.toJSONObject(this); } public byte[] toBytes() { return translate.toBytes(this); } @Override public String toHex() { return translate.toHex(this); } public void toBytesSink(BytesSink to, FieldFilter p) { BinarySerializer serializer = new BinarySerializer(to); for (Field field : this) { if (p.evaluate(field)) { SerializedType value = fields.get(field); serializer.add(field, value); } } } @Override public void toBytesSink(BytesSink to) { toBytesSink(to, new FieldFilter() { @Override public boolean evaluate(Field field) { return field.isSerialized(); } }); } @Override public Type type() { return Type.STObject; } public static class Translator extends TypeTranslator<STObject> { @Override public STObject fromParser(BinaryParser parser, Integer hint) { STObject so = new STObject(); TypeTranslator<SerializedType> tr; SerializedType st; Field field; Integer sizeHint; // hint, is how many bytes to parse if (hint != null) { // end hint hint = parser.pos() + hint; } while (!parser.end(hint)) { field = parser.readField(); if (field == Field.ObjectEndMarker) { break; } tr = Translators.forField(field); sizeHint = field.isVLEncoded() ? parser.readVLLength() : null; st = tr.fromParser(parser, sizeHint); if (st == null) { throw new IllegalStateException("Parsed " + field + " as null"); } so.put(field, st); } return STObject.formatted(so); } @Override public Object toJSON(STObject obj) { return toJSONObject(obj); } @Override public JSONObject toJSONObject(STObject obj) { JSONObject json = new JSONObject(); for (Field f : obj) { SerializedType obj1 = obj.get(f); Object object = obj1.toJSON(); json.put(f.name(), object); } return json; } @Override public STObject fromJSONObject(JSONObject jsonObject) { STObject so = new STObject(); Iterator keys = jsonObject.keys(); while (keys.hasNext()) { String key = (String) keys.next(); Object value = jsonObject.get(key); Field fieldKey = Field.fromString(key); if (fieldKey == null) { continue; } so.putTranslated(fieldKey, value); } return STObject.formatted(so); } } public int size() { return fields.size(); } static public Translator translate = new Translator(); public static STObjectField stobjectField(final Field f) { return new STObjectField() {@Override public Field getField() {return f; } }; } static public STObjectField TransactionMetaData = stobjectField(Field.TransactionMetaData); static public STObjectField CreatedNode = stobjectField(Field.CreatedNode); static public STObjectField DeletedNode = stobjectField(Field.DeletedNode); static public STObjectField ModifiedNode = stobjectField(Field.ModifiedNode); static public STObjectField PreviousFields = stobjectField(Field.PreviousFields); static public STObjectField FinalFields = stobjectField(Field.FinalFields); static public STObjectField NewFields = stobjectField(Field.NewFields); static public STObjectField TemplateEntry = stobjectField(Field.TemplateEntry); public static class Translators { private static TypeTranslator forType(Type type) { switch (type) { case STObject: return translate; case Amount: return Amount.translate; case UInt16: return UInt16.translate; case UInt32: return UInt32.translate; case UInt64: return UInt64.translate; case Hash128: return Hash128.translate; case Hash256: return Hash256.translate; case Blob: return Blob.translate; case AccountID: return AccountID.translate; case STArray: return STArray.translate; case UInt8: return UInt8.translate; case Hash160: return Hash160.translate; case PathSet: return PathSet.translate; case Vector256: return Vector256.translate; default: throw new RuntimeException("Unknown type"); } } public static TypeTranslator<SerializedType> forField(Field field) { if (field.tag == null) { switch (field) { case LedgerEntryType: field.tag = LedgerEntryType.translate; break; case TransactionType: field.tag = TransactionType.translate; break; case TransactionResult: field.tag = EngineResult.translate; break; default: field.tag = forType(field.getType()); break; } } return getCastedTag(field); } @SuppressWarnings("unchecked") private static TypeTranslator<SerializedType> getCastedTag(Field field) { return (TypeTranslator<SerializedType>) field.tag; } } }