/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.util; import com.cedarsoftware.util.DeepEquals; import org.ethereum.crypto.HashUtil; import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; import java.util.Arrays; import java.util.List; /** * Class to encapsulate an object and provide utilities for conversion */ public class Value { private Object value; private byte[] rlp; private byte[] sha3; private boolean decoded = false; public static Value fromRlpEncoded(byte[] data) { if (data != null && data.length != 0) { Value v = new Value(); v.init(data); return v; } return null; } public Value(){ } public void init(byte[] rlp){ this.rlp = rlp; } public Value(Object obj) { this.decoded = true; if (obj == null) return; if (obj instanceof Value) { this.value = ((Value) obj).asObj(); } else { this.value = obj; } } public Value withHash(byte[] hash) { sha3 = hash; return this; } /* ***************** * Convert * *****************/ public Object asObj() { decode(); return value; } public List<Object> asList() { decode(); Object[] valueArray = (Object[]) value; return Arrays.asList(valueArray); } public int asInt() { decode(); if (isInt()) { return (Integer) value; } else if (isBytes()) { return new BigInteger(1, asBytes()).intValue(); } return 0; } public long asLong() { decode(); if (isLong()) { return (Long) value; } else if (isBytes()) { return new BigInteger(1, asBytes()).longValue(); } return 0; } public BigInteger asBigInt() { decode(); return (BigInteger) value; } public String asString() { decode(); if (isBytes()) { return new String((byte[]) value); } else if (isString()) { return (String) value; } return ""; } public byte[] asBytes() { decode(); if (isBytes()) { return (byte[]) value; } else if (isString()) { return asString().getBytes(); } return ByteUtil.EMPTY_BYTE_ARRAY; } public String getHex(){ return Hex.toHexString(this.encode()); } public byte[] getData(){ return this.encode(); } public int[] asSlice() { return (int[]) value; } public Value get(int index) { if (isList()) { // Guard for OutOfBounds if (asList().size() <= index) { return new Value(null); } if (index < 0) { throw new RuntimeException("Negative index not allowed"); } return new Value(asList().get(index)); } // If this wasn't a slice you probably shouldn't be using this function return new Value(null); } /* ***************** * Utility * *****************/ public void decode(){ if (!this.decoded) { this.value = RLP.decode(rlp, 0).getDecoded(); this.decoded = true; } } public byte[] encode() { if (rlp == null) rlp = RLP.encode(value); return rlp; } public byte[] hash(){ if (sha3 == null) sha3 = HashUtil.sha3(encode()); return sha3; } public boolean cmp(Value o) { return DeepEquals.deepEquals(this, o); } /* ***************** * Checks * *****************/ public boolean isList() { decode(); return value != null && value.getClass().isArray() && !value.getClass().getComponentType().isPrimitive(); } public boolean isString() { decode(); return value instanceof String; } public boolean isInt() { decode(); return value instanceof Integer; } public boolean isLong() { decode(); return value instanceof Long; } public boolean isBigInt() { decode(); return value instanceof BigInteger; } public boolean isBytes() { decode(); return value instanceof byte[]; } // it's only if the isBytes() = true; public boolean isReadableString() { decode(); int readableChars = 0; byte[] data = (byte[]) value; if (data.length == 1 && data[0] > 31 && data[0] < 126) { return true; } for (byte aData : data) { if (aData > 32 && aData < 126) ++readableChars; } return (double) readableChars / (double) data.length > 0.55; } // it's only if the isBytes() = true; public boolean isHexString() { decode(); int hexChars = 0; byte[] data = (byte[]) value; for (byte aData : data) { if ((aData >= 48 && aData <= 57) || (aData >= 97 && aData <= 102)) ++hexChars; } return (double) hexChars / (double) data.length > 0.9; } public boolean isHashCode() { decode(); return this.asBytes().length == 32; } public boolean isNull() { decode(); return value == null; } public boolean isEmpty() { decode(); if (isNull()) return true; if (isBytes() && asBytes().length == 0) return true; if (isList() && asList().isEmpty()) return true; if (isString() && asString().equals("")) return true; return false; } public int length() { decode(); if (isList()) { return asList().size(); } else if (isBytes()) { return asBytes().length; } else if (isString()) { return asString().length(); } return 0; } public String toString() { decode(); StringBuilder stringBuilder = new StringBuilder(); if (isList()) { Object[] list = (Object[]) value; // special case - key/value node if (list.length == 2) { stringBuilder.append("[ "); Value key = new Value(list[0]); byte[] keyNibbles = CompactEncoder.binToNibblesNoTerminator(key.asBytes()); String keyString = ByteUtil.nibblesToPrettyString(keyNibbles); stringBuilder.append(keyString); stringBuilder.append(","); Value val = new Value(list[1]); stringBuilder.append(val.toString()); stringBuilder.append(" ]"); return stringBuilder.toString(); } stringBuilder.append(" ["); for (int i = 0; i < list.length; ++i) { Value val = new Value(list[i]); if (val.isString() || val.isEmpty()) { stringBuilder.append("'").append(val.toString()).append("'"); } else { stringBuilder.append(val.toString()); } if (i < list.length - 1) stringBuilder.append(", "); } stringBuilder.append("] "); return stringBuilder.toString(); } else if (isEmpty()) { return ""; } else if (isBytes()) { StringBuilder output = new StringBuilder(); if (isHashCode()) { output.append(Hex.toHexString(asBytes())); } else if (isReadableString()) { output.append("'"); for (byte oneByte : asBytes()) { if (oneByte < 16) { output.append("\\x").append(ByteUtil.oneByteToHexString(oneByte)); } else { output.append(Character.valueOf((char) oneByte)); } } output.append("'"); return output.toString(); } return Hex.toHexString(this.asBytes()); } else if (isString()) { return asString(); } return "Unexpected type"; } public int countBranchNodes() { decode(); if (this.isList()) { List<Object> objList = this.asList(); int i = 0; for (Object obj : objList) { i += (new Value(obj)).countBranchNodes(); } return i; } else if (this.isBytes()) { this.asBytes(); } return 0; } }