diff --git a/graphenej/src/main/java/de/bitsharesmunich/graphenej/Transaction.java b/graphenej/src/main/java/de/bitsharesmunich/graphenej/Transaction.java index 13c3443..9c8ca5b 100644 --- a/graphenej/src/main/java/de/bitsharesmunich/graphenej/Transaction.java +++ b/graphenej/src/main/java/de/bitsharesmunich/graphenej/Transaction.java @@ -1,11 +1,16 @@ package de.bitsharesmunich.graphenej; import com.google.common.primitives.Bytes; -import com.google.gson.*; -import de.bitsharesmunich.graphenej.interfaces.ByteSerializable; -import de.bitsharesmunich.graphenej.interfaces.JsonSerializable; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; -import de.bitsharesmunich.graphenej.operations.TransferOperation; import org.bitcoinj.core.DumpedPrivateKey; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.Sha256Hash; @@ -19,6 +24,11 @@ import java.util.Date; import java.util.List; import java.util.TimeZone; +import de.bitsharesmunich.graphenej.interfaces.ByteSerializable; +import de.bitsharesmunich.graphenej.interfaces.JsonSerializable; +import de.bitsharesmunich.graphenej.operations.LimitOrderCreateOperation; +import de.bitsharesmunich.graphenej.operations.TransferOperation; + /** * Class used to represent a generic Graphene transaction. */ @@ -263,10 +273,9 @@ public class Transaction implements ByteSerializable, JsonSerializable { for (JsonElement jsonOperation : jsonObject.get(KEY_OPERATIONS).getAsJsonArray()) { int operationId = jsonOperation.getAsJsonArray().get(0).getAsInt(); if (operationId == OperationType.TRANSFER_OPERATION.ordinal()) { - System.out.println("Transfer operation detected!"); operation = context.deserialize(jsonOperation, TransferOperation.class); } else if (operationId == OperationType.LIMIT_ORDER_CREATE_OPERATION.ordinal()) { - //TODO: Add operation deserialization support + operation = context.deserialize(jsonOperation, LimitOrderCreateOperation.class); } else if (operationId == OperationType.LIMIT_ORDER_CANCEL_OPERATION.ordinal()) { //TODO: Add operation deserialization support } else if (operationId == OperationType.CALL_ORDER_UPDATE_OPERATION.ordinal()) { diff --git a/graphenej/src/main/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHub.java b/graphenej/src/main/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHub.java index c4aedab..871a133 100644 --- a/graphenej/src/main/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHub.java +++ b/graphenej/src/main/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHub.java @@ -15,6 +15,7 @@ import java.util.Map; import de.bitsharesmunich.graphenej.AssetAmount; import de.bitsharesmunich.graphenej.RPC; import de.bitsharesmunich.graphenej.Transaction; +import de.bitsharesmunich.graphenej.UserAccount; import de.bitsharesmunich.graphenej.interfaces.SubscriptionHub; import de.bitsharesmunich.graphenej.interfaces.SubscriptionListener; import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener; @@ -22,6 +23,7 @@ import de.bitsharesmunich.graphenej.models.ApiCall; import de.bitsharesmunich.graphenej.models.DynamicGlobalProperties; import de.bitsharesmunich.graphenej.models.SubscriptionResponse; import de.bitsharesmunich.graphenej.models.WitnessResponse; +import de.bitsharesmunich.graphenej.operations.LimitOrderCreateOperation; import de.bitsharesmunich.graphenej.operations.TransferOperation; /** @@ -54,7 +56,9 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs builder.registerTypeAdapter(SubscriptionResponse.class, mSubscriptionDeserializer); builder.registerTypeAdapter(Transaction.class, new Transaction.TransactionDeserializer()); builder.registerTypeAdapter(TransferOperation.class, new TransferOperation.TransferDeserializer()); + builder.registerTypeAdapter(LimitOrderCreateOperation.class, new LimitOrderCreateOperation.LimitOrderCreateDeserializer()); builder.registerTypeAdapter(AssetAmount.class, new AssetAmount.AssetAmountDeserializer()); + builder.registerTypeAdapter(UserAccount.class, new UserAccount.UserAccountSimpleDeserializer()); builder.registerTypeAdapter(DynamicGlobalProperties.class, new DynamicGlobalProperties.DynamicGlobalPropertiesDeserializer()); this.gson = builder.create(); } @@ -104,7 +108,7 @@ public class SubscriptionMessagesHub extends BaseGrapheneHandler implements Subs }else if(currentId == SUBCRIPTION_REQUEST){ // There's nothing to handle here. }else{ - SubscriptionResponse subscriptionResponse = gson.fromJson(message, SubscriptionResponse.class); + gson.fromJson(message, SubscriptionResponse.class); } currentId++; } diff --git a/graphenej/src/main/java/de/bitsharesmunich/graphenej/models/SubscriptionResponse.java b/graphenej/src/main/java/de/bitsharesmunich/graphenej/models/SubscriptionResponse.java index 1e8741c..bffcfbc 100644 --- a/graphenej/src/main/java/de/bitsharesmunich/graphenej/models/SubscriptionResponse.java +++ b/graphenej/src/main/java/de/bitsharesmunich/graphenej/models/SubscriptionResponse.java @@ -120,8 +120,8 @@ public class SubscriptionResponse { JsonArray subArray = paramsArray.get(1).getAsJsonArray().get(0).getAsJsonArray(); for(JsonElement object : subArray){ if(object.isJsonObject()){ - GrapheneObject grapheneObject = new GrapheneObject(object.getAsJsonObject().get(KEY_ID).getAsString()); + int listenerTypeCount = 0; if(this.listenerTypeCount.containsKey(grapheneObject.getObjectType())){ listenerTypeCount = this.listenerTypeCount.get(grapheneObject.getObjectType()); diff --git a/graphenej/src/main/java/de/bitsharesmunich/graphenej/operations/LimitOrderCreateOperation.java b/graphenej/src/main/java/de/bitsharesmunich/graphenej/operations/LimitOrderCreateOperation.java index c98ed0b..195682a 100644 --- a/graphenej/src/main/java/de/bitsharesmunich/graphenej/operations/LimitOrderCreateOperation.java +++ b/graphenej/src/main/java/de/bitsharesmunich/graphenej/operations/LimitOrderCreateOperation.java @@ -2,10 +2,15 @@ package de.bitsharesmunich.graphenej.operations; import com.google.common.primitives.Bytes; import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import java.lang.reflect.Type; import java.nio.ByteBuffer; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; @@ -151,4 +156,78 @@ public class LimitOrderCreateOperation extends BaseOperation { return Bytes.concat(feeBytes, sellerBytes, amountBytes, minAmountBytes, expirationBytes, fillOrKill, extensions); } + + /** + * Deserializer used to convert the JSON-formatted representation of a limit_order_create_operation + * into its java object version. + * + * The following is an example of the serialized form of this operation: + * + * [ + * 1, + * { + * "fee": { + * "amount": 14676, + * "asset_id": "1.3.0" + * }, + * "seller": "1.2.36449", + * "amount_to_sell": { + * "amount": 945472, + * "asset_id": "1.3.850" + * }, + * "min_to_receive": { + * "amount": 4354658, + * "asset_id": "1.3.861" + * }, + * "expiration": "1963-11-25T06:31:44", + * "fill_or_kill": false, + * "extensions": [] + * } + * ] + * + * + */ + public static class LimitOrderCreateDeserializer implements JsonDeserializer { + + @Override + public LimitOrderCreateOperation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if(json.isJsonArray()){ + // This block is used just to check if we are in the first step of the deserialization + // when we are dealing with an array. + JsonArray serializedTransfer = json.getAsJsonArray(); + if(serializedTransfer.get(0).getAsInt() != OperationType.LIMIT_ORDER_CREATE_OPERATION.ordinal()){ + // If the operation type does not correspond to a transfer operation, we return null + return null; + }else{ + // Calling itself recursively, this is only done once, so there will be no problems. + return context.deserialize(serializedTransfer.get(1), LimitOrderCreateOperation.class); + } + }else{ + // This block is called in the second recursion and takes care of deserializing the + // limit order data itself. + JsonObject jsonObject = json.getAsJsonObject(); + + AssetAmount fee = context.deserialize(jsonObject.get(KEY_FEE), AssetAmount.class); + UserAccount seller = context.deserialize(jsonObject.get(KEY_SELLER), UserAccount.class); + AssetAmount amountToSell = context.deserialize(jsonObject.get(KEY_AMOUNT_TO_SELL), AssetAmount.class); + AssetAmount minToReceive = context.deserialize(jsonObject.get(KEY_MIN_TO_RECEIVE), AssetAmount.class); + String expiration = jsonObject.get(KEY_EXPIRATION).getAsString(); + boolean fillOrKill = jsonObject.get(KEY_FILL_OR_KILL).getAsBoolean(); + + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Util.TIME_DATE_FORMAT); + int expirationPosix = 0; + try { + Date expirationDate = simpleDateFormat.parse(expiration); + expirationPosix = (int) (expirationDate.getTime() / 1000); + } catch (ParseException e) { + e.printStackTrace(); + } + + // Creating an instance of the LimitOrderCreateOperation and setting the fee + LimitOrderCreateOperation operation = new LimitOrderCreateOperation(seller, amountToSell, minToReceive, expirationPosix, fillOrKill); + operation.setFee(fee); + return operation; + } + } + } } diff --git a/graphenej/src/test/java/de/bitsharesmunich/graphenej/BrainKeyTest.java b/graphenej/src/test/java/de/bitsharesmunich/graphenej/BrainKeyTest.java index e423a6b..a534eb5 100644 --- a/graphenej/src/test/java/de/bitsharesmunich/graphenej/BrainKeyTest.java +++ b/graphenej/src/test/java/de/bitsharesmunich/graphenej/BrainKeyTest.java @@ -1,6 +1,7 @@ package de.bitsharesmunich.graphenej; -import org.junit.Assert; +import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; @@ -8,7 +9,6 @@ import org.junit.Test; * Created by nelson on 4/18/17. */ public class BrainKeyTest { -// public final String TEST_BRAINKEY = "SOAPILY GASSING FIFIE OZONATE WHYO TOPLINE PRISMY ZEUGMA GLOTTIC DAVEN CORODY PFUI"; public final String TEST_BRAINKEY = "BARIC BICKERN LITZ TIPFUL JINGLED POOL TUMBAK PURIST APOPYLE DURAIN SATLIJK FAUCAL"; private BrainKey mBrainKey; @@ -20,9 +20,6 @@ public class BrainKeyTest { @Test public void testAddress(){ Address address = mBrainKey.getPublicAddress(Address.BITSHARES_PREFIX); -// Assert.assertEquals("Assert that the address created is the expected one", -// "BTS7yT2vnjGAxPocqsnDfoJv3DHUtCNGWwqvc7mWRikkqwuhKtT5s", -// address.toString()); Assert.assertEquals("Assert that the address created is the expected one", "BTS61UqqgE3ARuTGcckzARsdQm4EMFdBEwYyi1pbwyHrZZWrCDhT2", address.toString()); diff --git a/graphenej/src/test/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHubTest.java b/graphenej/src/test/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHubTest.java index 9df50e0..3b6deda 100644 --- a/graphenej/src/test/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHubTest.java +++ b/graphenej/src/test/java/de/bitsharesmunich/graphenej/api/SubscriptionMessagesHubTest.java @@ -3,11 +3,16 @@ package de.bitsharesmunich.graphenej.api; import com.neovisionaries.ws.client.WebSocketException; import org.junit.Test; + +import java.io.Serializable; import java.util.List; + import de.bitsharesmunich.graphenej.ObjectType; +import de.bitsharesmunich.graphenej.Transaction; import de.bitsharesmunich.graphenej.interfaces.SubscriptionListener; import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener; import de.bitsharesmunich.graphenej.models.BaseResponse; +import de.bitsharesmunich.graphenej.models.BroadcastedTransaction; import de.bitsharesmunich.graphenej.models.DynamicGlobalProperties; import de.bitsharesmunich.graphenej.models.SubscriptionResponse; import de.bitsharesmunich.graphenej.models.WitnessResponse; @@ -82,4 +87,59 @@ public class SubscriptionMessagesHubTest extends BaseApiTest { System.out.println("InterruptedException. Msg: "+e.getMessage()); } } + + /** + * This is a basic test that will only display a count of operations per received broadcasted transactions. + */ + @Test + public void testBroadcastedTransactionDeserializer(){ + try{ + mMessagesHub = new SubscriptionMessagesHub("", "", mErrorListener); + mMessagesHub.addSubscriptionListener(new SubscriptionListener() { + private int MAX_MESSAGES = 15; + private int messageCounter = 0; + + @Override + public ObjectType getInterestObjectType() { + return ObjectType.TRANSACTION_OBJECT; + } + + @Override + public void onSubscriptionUpdate(SubscriptionResponse response) { + if(response.params.size() == 2){ + List payload = (List) response.params.get(1); + if(payload.size() > 0){ + for(Serializable item : payload){ + if(item instanceof BroadcastedTransaction){ + BroadcastedTransaction broadcastedTransaction = (BroadcastedTransaction) item; + Transaction tx = broadcastedTransaction.getTransaction(); + System.out.println(String.format("Got %d operations", tx.getOperations().size())); + } + } + } + } + + // Waiting for MAX_MESSAGES messages before releasing the wait lock + messageCounter++; + if(messageCounter > MAX_MESSAGES){ + synchronized (SubscriptionMessagesHubTest.this){ + SubscriptionMessagesHubTest.this.notifyAll(); + } + } + } + }); + + mWebSocket.addListener(mMessagesHub); + mWebSocket.connect(); + + // Holding this thread while we get update notifications + synchronized (this){ + wait(); + } + } catch (WebSocketException e) { + System.out.println("WebSocketException. Msg: " + e.getMessage()); + } catch (InterruptedException e) { + System.out.println("InterruptedException. Msg: "+e.getMessage()); + } + } } \ No newline at end of file