org.coinspark.wallet.CSAsset.java Source code

Java tutorial

Introduction

Here is the source code for org.coinspark.wallet.CSAsset.java

Source

/* 
 * SparkBit's Bitcoinj
 *
 * Copyright 2014 Coin Sciences Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.coinspark.wallet;

import com.google.bitcoin.core.Block;
import com.google.bitcoin.core.PeerGroup;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionInput;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.coinspark.core.CSPDFParser;
import org.coinspark.core.CSUtils;
import org.coinspark.protocol.CoinSparkAssetRef;
import org.coinspark.protocol.CoinSparkGenesis;
import org.slf4j.LoggerFactory;

/**
 * Coinspark Asset class
 */

public class CSAsset {

    private static final org.slf4j.Logger log = LoggerFactory.getLogger(CSAsset.class);

    /**
     * Validity state of the asset.
     */

    public enum CSAssetState {
        INVALID, // Invalid for some reason
        NO_KEY, // Neither AssetRef, nor genesis is set
        ASSET_REF_ONLY, // Only Asset ref is set, but not validated yet. No genesis
        BLOCK_NOT_FOUND, // Block not found
        TX_NOT_FOUND, // Genesis transaction not found by AssetRef
        GENESIS_NOT_FOUND, // Genesis not found in AssetRef transaction
        NOT_VALIDATED_YET, // Genesis not validated yet
        ASSET_WEB_PAGE_NOT_FOUND, // Asset web page not found        
        ASSET_SPECS_NOT_FOUND, // Asset JSON specs not found on Web page
        ASSET_SPECS_NOT_PARSED, // Asset JSON specs cannot be parsed
        REQUIRED_FIELD_MISSING, // Some required fields are missing in JSON specs
        CONTRACT_NOT_FOUND, // Contract cannot be cached
        CONTRACT_INVALID, // Contract file is invalid
        HASH_MISMATCH, // Hash of retrieved fields down't match encoded in genesis
        REFRESH, // Validity should be checked (manually set), if genesis is set - starts from Asset web page
        VALID, // Asset passed validity check
        DUPLICATE, // There is Asset with the same keys, this one should be deleted after balances db updated
    }

    public enum CSAssetContractState {
        UNKNOWN, CANNOT_PARSE, // Cannot parse PDF
        POSSIBLE_EMBEDDED_URL, // Embedded file found in PDF - embedded URL can be hidden inside, or unclear FS key
        EMBEDDED_URL, // Embedded URL found
        OK, // Contract looks OK
    }

    private CSAssetState assetState;
    private CSAssetState assetValidationState;
    private CSAssetState assetStateBeforeRefresh;

    // Internal Asset ID in the database
    private int assetID;

    // The date and time when the asset began being tracked.
    private Date dateCreation;

    /**
     * Method used when inserting asset into database.
     */

    public enum CSAssetSource {
        GENESIS, TRANSFER, MANUAL
    }

    private CSAssetSource assetSource;
    private CSAssetContractState assetContractState = CSAssetContractState.UNKNOWN;

    // A flag indicating whether the asset should be displayed to the user.
    private boolean visible = true;

    // The full bitcoin txid of the asset's genesis transaction. [null?]
    private String genTxID;

    // Decoded genesis structure
    private CoinSparkGenesis genesis;

    // Asset reference
    private CoinSparkAssetRef assetRef;

    // The date and time when the asset's validity was last checked. [null?]
    private Date validChecked;

    // Count of consecutive failures to verify the assets validity
    private int validFailures = 0;

    // The contract underlying the asset, as retrieved from the URL in that JSON. [null?]
    private String contractPath;

    // The JSON of underlying the asset. [null?]
    private String jsonPath;

    // The last valid contract underlying the asset, as retrieved from the URL in that JSON. [null?]
    private String validContractPath;

    // The last valid JSON of underlying the asset. [null?]
    private String validJsonPath;

    private String validContractPathToSet;
    private String validJsonPathToSet;

    //The MIME type of that contract, as sent in the HTTP response headers when retrieving it. [null?]
    private CSUtils.CSMimeType contractMimeType;

    //The MIME type of that last valid contract, as sent in the HTTP response headers when retrieving it. [null?]
    private CSUtils.CSMimeType validContractMimeType;

    // Downloaded Asset icon
    private String iconPath;

    // Asset icon MIME type
    private CSUtils.CSMimeType iconMimeType;

    // Downloaded Asset image
    private String imagePath;

    // Asset image MIME type
    private CSUtils.CSMimeType imageMimeType;

    // Asset details as they are specified on Asset Web page

    private String firstSpentTxID = null;
    private long firstSpentVout = 0;

    private String name;
    private String nameShort;
    private String issuer;
    private String description;
    private String units;
    private String contractUrl;
    private String selectedTrackerUrl;
    private String[] coinsparkTrackerUrls;
    private String issueDate;
    private String expiryDate;
    private Double interestRate;
    private Double multiple;
    private String format;
    private String format1;
    private String iconUrl;
    private String imageUrl;
    private String feedUrl;
    private String redemptionUrl;

    /**
     * Creates new Asset object.
     */

    public CSAsset() {
        dateCreation = new Date();
        assetState = CSAssetState.NO_KEY;
    }

    /**
     * Creates asset object from JSON string stored in database
     * @param FilePrefix - prefix used for calculation of cached files directory 
     * @param AssetID - internal asset ID
     * @param Serialized - serialized JSON string stored in the databse
     */

    public CSAsset(String FilePrefix, int AssetID, String Serialized) {
        assetID = AssetID;
        parseJSONString(Serialized);

        if (assetID > 0) {
            String prefix = FilePrefix + String.format("asset%06d", assetID);
            String validPrefix = FilePrefix + String.format("asset%06d_valid", assetID);

            jsonPath = prefix + ".json";
            if (!(new File(jsonPath)).exists()) {
                jsonPath = null;
            }

            validJsonPath = validPrefix + ".json";
            if (!(new File(validJsonPath)).exists()) {
                validJsonPath = null;
            }

            if (contractMimeType != null) {
                contractPath = prefix + "_contract" + contractMimeType.getExtension();
                if (!(new File(contractPath)).exists()) {
                    contractMimeType = null;
                    contractPath = null;
                }
            }

            if (validContractMimeType != null) {
                validContractPath = validPrefix + "_contract" + validContractMimeType.getExtension();
                if (!(new File(validContractPath)).exists()) {
                    validContractMimeType = null;
                    validContractPath = null;
                }
            }

            if (iconMimeType != null) {
                iconPath = prefix + "_icon" + iconMimeType.getExtension();
                if (!(new File(iconPath)).exists()) {
                    iconMimeType = null;
                    iconPath = null;
                }
            }

            if (imageMimeType != null) {
                imagePath = prefix + "_image" + imageMimeType.getExtension();
                if (!(new File(imagePath)).exists()) {
                    imagePath = null;
                    imagePath = null;
                }
            }

            if (assetState == CSAssetState.VALID) {
                if (contractMimeType == null) {
                    assetState = CSAssetState.CONTRACT_NOT_FOUND;
                    validChecked = new Date();
                    validFailures = 1;
                }
            }
        }
    }

    /**
     * Creates Asset object by genesis TxID and genesis object.
     * This asset should be created by wallet when it creates new asset.
     * This asset may be unconfirmed and so lack Asset reference.
     * @param GenTxID Genesis Transaction ID 
     * @param Genesis Genesis object
     * @param Block height of the block in blockchain. When Asset is created by the tx from blockchain only its block height is known, not offset 
     */

    public CSAsset(String GenTxID, CoinSparkGenesis Genesis, int Block) {
        assetID = 0;
        genTxID = GenTxID;
        genesis = Genesis;
        assetSource = CSAssetSource.GENESIS;
        assetState = CSAssetState.NOT_VALIDATED_YET;
        dateCreation = new Date();
        if (Block > 0) {
            assetRef = new CoinSparkAssetRef();
            assetRef.setBlockNum(Block);
            assetRef.setTxOffset(0);
            assetRef.setTxIDPrefix(
                    Arrays.copyOf(CSUtils.hex2Byte(GenTxID), CoinSparkAssetRef.COINSPARK_ASSETREF_TXID_PREFIX_LEN));
        }
    }

    /**
     * Creates Asset object by Asset reference.
     * This asset should be created by wallet when it become aware of new asset either from transfer list or manually inserting by the user.
     * This asset is created without genesis information. It should be retrieved asynchronously by background process.
     * @param AssetRef Asset reference
     * @param AssetSource Asset source - TRANSFER or MANUAL 
     */

    public CSAsset(CoinSparkAssetRef AssetRef, CSAsset.CSAssetSource AssetSource) {
        assetID = 0;
        assetRef = AssetRef;
        assetSource = AssetSource;
        assetState = CSAssetState.ASSET_REF_ONLY;
        dateCreation = new Date();
    }

    public CSAsset.CSAssetState getAssetState() {
        return assetState;
    }

    public CSAsset.CSAssetState getAssetStateBeforeRefresh() {
        return (assetState == CSAssetState.REFRESH) ? assetStateBeforeRefresh : assetState;
    }

    public CSAsset.CSAssetSource getAssetSource() {
        return assetSource;
    }

    public CSAsset.CSAssetContractState getAssetContractState() {
        return assetContractState;
    }

    public int getAssetID() {
        return assetID;
    }

    public Date getDateCreation() {
        return dateCreation;
    }

    public boolean isVisible() {
        return visible;
    }

    public String getGenTxID() {
        return genTxID;
    }

    public CoinSparkGenesis getGenesis() {
        return genesis;
    };

    public CoinSparkAssetRef getAssetReference() {
        return assetRef;
    }

    public Date getValidChecked() {
        return validChecked;
    }

    public int getValidFailures() {
        return validFailures;
    }

    public String getJsonPath() {
        return jsonPath;
    }

    public String getContractPath() {
        return contractPath;
    }

    public CSUtils.CSMimeType getContractMimeType() {
        return contractMimeType;
    }

    public String getValidJsonPath() {
        return validJsonPath;
    }

    public String getValidContractPath() {
        return validContractPath;
    }

    public CSUtils.CSMimeType getValidContractMimeType() {
        return validContractMimeType;
    }

    public String getIconPath() {
        return iconPath;
    }

    public String getImagePath() {
        return imagePath;
    }

    public String[] getCoinsparkTrackerUrls() {
        return coinsparkTrackerUrls;
    }

    public String getName() {
        return name;
    }

    public String getNameShort() {
        return nameShort;
    }

    public String getIssuer() {
        return issuer;
    }

    public String getDescription() {
        return description;
    }

    public String getUnits() {
        return units;
    }

    public String getContractUrl() {
        return contractUrl;
    }

    /**
     * Selects one of the Tracker URLs and fixes it for getCoinsparkTrackerUrl.
     * @return selected tracker URL
     */
    public String selectCoinsparkTrackerUrl() {
        selectedTrackerUrl = null;

        if (coinsparkTrackerUrls == null) {
            return null;
        }

        if (coinsparkTrackerUrls.length <= 0) {
            return null;
        }

        selectedTrackerUrl = coinsparkTrackerUrls[new Random().nextInt(coinsparkTrackerUrls.length)];

        return selectedTrackerUrl;
    }

    public String getCoinsparkTrackerUrl() {
        return selectedTrackerUrl;
    }

    public Date getIssueDate() {
        return CSUtils.iso86012date(issueDate);
    }

    public Date getExpiryDate() {
        return CSUtils.iso86012date(expiryDate);
    }

    public double getInterestRate() {
        if (interestRate == null) {
            return 0.0;
        }
        return interestRate;
    }

    public double getMultiple() {
        if (multiple == null) {
            return 1.0;
        }
        return multiple;
    }

    public String getFormat() {
        return format;
    }

    public String getFormat1() {
        return format1;
    }

    public String getIconUrl() {
        return iconUrl;
    }

    public String getImageUrl() {
        return imageUrl;
    }

    public String getFeedUrl() {
        return feedUrl;
    }

    public String getRedemptionUrl() {
        return redemptionUrl;
    }

    public void setName(String Name) {
        name = Name;
    }

    public void setNameShort(String NameShort) {
        nameShort = NameShort;
    }

    public void setIssuer(String Issuer) {
        issuer = Issuer;
    }

    public void setDescription(String Description) {
        description = Description;
    }

    public void setUnits(String Units) {
        units = Units;
    }

    public void setContractUrl(String ContractUrl) {
        contractUrl = ContractUrl;
    }

    public void setIssueDate(String IssueDate) {
        issueDate = IssueDate;
    }

    public void setExpiryDate(String ExpiryDate) {
        expiryDate = ExpiryDate;
    }

    public void setInterestRate(double InterestRate) {
        interestRate = InterestRate;
    }

    public void setMultiple(double Multiple) {
        multiple = Multiple;
    }

    public void setFormat(String Format) {
        format = Format;
    }

    public void setFormat1(String Format1) {
        format1 = Format1;
    }

    public void setIconUrl(String IconUrl) {
        iconUrl = IconUrl;
    }

    public void setImageUrl(String ImageUrl) {
        imageUrl = ImageUrl;
    }

    public void setFeedUrl(String FeedUrl) {
        feedUrl = FeedUrl;
    }

    public void setRedemptionUrl(String RedemptionUrl) {
        redemptionUrl = RedemptionUrl;
    }

    public void setVisibility(boolean Visibility) {
        visible = Visibility;
    }

    protected void setAssetState(CSAssetState State) {
        assetState = State;
    }

    protected void setFirstSpentInput(String FirstSpentTxID, long FirstSpentVout) {
        firstSpentTxID = FirstSpentTxID;
        firstSpentVout = FirstSpentVout;
    }

    /**
     * Sets AssetID, This method should be used only by Asset Database, to ensure thread-safety.
     * This function is called by Asset Database when asset is inserted to the database
     * @param AssetID Asset ID in database
     */

    public void setAssetID(int AssetID) {
        assetID = AssetID;
    }

    /**
     * Sets Asset reference for asset created by genesis, This method should be used only by Asset Database, to ensure thread-safety.
     * When wallets sees confirmed genesis in relevant transaction, it should find it in database and set Asset reference.
     * If the asset was not found (somebody else sent us genesis) wallet should calculate genesis, insert asset into database and set Asset reference 
     * @param AssetRef Asset reference
     */

    public void setAssetRef(CoinSparkAssetRef AssetRef) {
        assetRef = AssetRef;
    }

    /**
     * Validates asset reference, This function should be called by Asset Database to ensure thread-safety;
     * @param pg
     * @return true if asset info was updated in this function
     */

    public boolean validateAssetRef(PeerGroup pg) {
        if (pg == null) {
            return false;
        }

        if (genesis == null) {
            return false;
        }

        if (assetRef == null) {
            return false;
        }

        if (assetRef.getTxOffset() > 0) {
            return false;
        }

        if (assetRef.getBlockNum() == 0) {
            return false;
        }

        log.info("Asset: Retrieving asset reference information for asset " + assetID + ", AssetRef "
                + assetRef.toString());

        Block block = pg.getBlock((int) assetRef.getBlockNum());

        if (block == null) {
            log.info("Asset: Cannot find block with height " + (int) assetRef.getBlockNum());
            return false;
        }

        int offset = block.getOffsetByTransaction(new Sha256Hash(genTxID));

        if (offset == 0) {
            log.info(
                    "Asset: Cannot find transaction with hash " + genTxID + " in block " + block.getHashAsString());
            return false;
        }

        assetRef.setTxOffset(offset);

        return true;
    }

    private boolean validateGenesis(PeerGroup pg) {
        CSAssetState initialState = assetValidationState;

        if (pg == null) {
            return false;
        }

        if (genesis != null) {
            if (firstSpentTxID != null) {
                return false;
            }
        }

        if (assetRef == null) {
            assetValidationState = CSAssetState.NO_KEY;
            return (assetValidationState != initialState);
        }

        CSEventBus.INSTANCE.postAsyncEvent(CSEventType.ASSET_VALIDATION_STARTED, assetID);

        assetValidationState = CSAssetState.NOT_VALIDATED_YET;
        log.info(
                "Asset: Retrieving genesis information for asset " + assetID + ", AssetRef " + assetRef.toString());

        Block block = pg.getBlock((int) assetRef.getBlockNum());

        if (block == null) {
            assetValidationState = CSAssetState.BLOCK_NOT_FOUND;
            log.info("Asset: Cannot find block with height " + (int) assetRef.getBlockNum());
            return (assetValidationState != initialState);
        }

        Transaction tx = block.getTransactionByOffset((int) assetRef.getTxOffset());
        if (tx == null) {
            assetValidationState = CSAssetState.TX_NOT_FOUND;
            log.info("Asset: Cannot find transaction with offset " + (int) assetRef.getTxOffset() + " in block "
                    + block.getHashAsString());
            return (assetValidationState != initialState);
        }

        if (!Arrays.equals(
                Arrays.copyOf(assetRef.getTxIDPrefix(), CoinSparkAssetRef.COINSPARK_ASSETREF_TXID_PREFIX_LEN),
                Arrays.copyOf(tx.getHash().getBytes(), CoinSparkAssetRef.COINSPARK_ASSETREF_TXID_PREFIX_LEN))) {
            assetValidationState = CSAssetState.TX_NOT_FOUND;
            log.info("Asset: TxID prefix doesn't match, need " + CSUtils.byte2Hex(assetRef.getTxIDPrefix())
                    + ", found " + CSUtils.byte2Hex(Arrays.copyOf(tx.getHash().getBytes(),
                            CoinSparkAssetRef.COINSPARK_ASSETREF_TXID_PREFIX_LEN)));
            return (assetValidationState != initialState);
        }

        genTxID = tx.getHash().toString();
        genesis = new CSTransactionAssets(tx).getGenesis();

        TransactionInput firstInput = tx.getInput(0);
        if (firstInput != null) {
            firstSpentTxID = firstInput.getOutpoint().getHash().toString();
            firstSpentVout = firstInput.getOutpoint().getIndex();
        }

        if (firstInput == null) {
            genesis = null;
        }

        assetValidationState = CSAssetState.NOT_VALIDATED_YET;
        if (genesis == null) {
            assetValidationState = CSAssetState.GENESIS_NOT_FOUND;
        }

        return (assetValidationState != initialState);
    }

    /**
     * Returns Asset Web Page URL.
     * @return Asset Web Page URL
     */

    public String getAssetWebPageURL() {
        if (genesis == null) {
            return null;
        }

        if (firstSpentTxID == null) {
            return null;
        }

        return genesis.calcAssetURL(firstSpentTxID, firstSpentVout);
    }

    /**
     * Returns Home page of the issuer as it is calculated from genesis domain information.
     * @return URL based on genesis domain information
     */

    public String getDomainURL() {
        if (genesis == null) {
            return null;
        }

        return genesis.getDomainURL();
    }

    private String fetchDetailsJSON(String FilePrefix) {
        if (genesis == null) {
            return null;
        }

        if (firstSpentTxID == null) {
            return null;
        }

        String jsonString = null;

        String response = "";

        String webPageAddress = genesis.calcAssetURL(firstSpentTxID, firstSpentVout);
        log.info("Asset: Fetching asset details for asset " + assetID + " from " + webPageAddress);

        CSUtils.CSDownloadedURL downloaded = CSUtils.getURL(webPageAddress, 15, null);

        if (downloaded.error != null) {
            assetValidationState = CSAssetState.ASSET_WEB_PAGE_NOT_FOUND;
            log.info("Asset: Cannot fetch URL: " + downloaded.error);
        } else {
            response = downloaded.contents;
        }

        String stringToFind = "_bitcoin_asset_specification_(";
        int posStart = response.indexOf(stringToFind);

        if (posStart >= 0) {
            posStart += stringToFind.length();

            byte[] tail = response.substring(posStart).getBytes();

            boolean inEscape = false;
            boolean inQuotes = false;
            int depth = 1;
            int pos = 0;

            int posEnd = -1;
            while ((depth > 0) && pos < tail.length) {
                if (inEscape) {
                    inEscape = false; // We don't care about more than 1 character
                } else {
                    if (tail[pos] == '"') {
                        inQuotes = !inQuotes;
                    }
                    if (inQuotes) {
                        if (tail[pos] == '\\') {
                            inEscape = true;
                        }
                    } else {
                        if (tail[pos] == '(') {
                            depth++;
                        }
                        if (tail[pos] == ')') {
                            depth--;
                        }
                    }
                }
                if (depth == 0) {
                    posEnd = pos;
                }
                pos++;
            }

            if (posEnd > 0) {
                jsonString = new String(Arrays.copyOfRange(tail, 0, posEnd));
                String prefix = FilePrefix + String.format("asset%06d", assetID);
                String validPrefix = FilePrefix + String.format("asset%06d_valid", assetID);

                jsonPath = prefix + ".json";
                validJsonPathToSet = validPrefix + ".json";

                RandomAccessFile aFile = null;
                try {
                    aFile = new RandomAccessFile(jsonPath, "rw");
                    aFile.write(jsonString.getBytes("UTF-8"));
                    aFile.close();

                } catch (FileNotFoundException ex) {
                    log.info("Asset DB: Cannot save json " + ex.getClass().getName() + " " + ex.getMessage());
                    jsonString = null;
                } catch (UnsupportedEncodingException ex) {
                    log.info("Asset DB: Cannot save json " + ex.getClass().getName() + " " + ex.getMessage());
                    jsonString = null;
                } catch (IOException ex) {
                    log.info("Asset DB: Cannot save json " + ex.getClass().getName() + " " + ex.getMessage());
                    jsonString = null;
                }

            }
        } else {
            assetValidationState = CSAssetState.ASSET_SPECS_NOT_FOUND;
            log.info("Asset: Asset specification not found");
        }
        return jsonString;
    }

    private CSUtils.CSMimeType downloadFile(String URLString, String FileName) {
        log.info("Asset: Downloading " + FileName + " from " + URLString);

        CSUtils.CSDownloadedURL downloaded = CSUtils.getURL(URLString, 30, FileName);

        if (downloaded.error != null) {
            log.info("Asset: Cannot download " + downloaded.error);
            return null;
        }

        return downloaded.mimeType;
    }

    private byte[] readContract() {
        if (contractPath == null) {
            return null;
        }

        RandomAccessFile aFile;
        try {
            aFile = new RandomAccessFile(contractPath, "r");
        } catch (FileNotFoundException ex) {
            log.info("Asset: Cannot open file " + ex.getClass().getName() + " " + ex.getMessage());
            return null;
        }

        int fileSize;
        try {
            fileSize = (int) aFile.length();
        } catch (IOException ex) {
            log.error("Asset: Cannot get file size " + ex.getClass().getName() + " " + ex.getMessage());
            return null;
        }

        if (fileSize <= 0) {
            return null;
        }

        byte[] raw = new byte[fileSize];

        if (!CSUtils.readFromFileToBytes(aFile, raw)) {
            log.error("Asset: Cannot read contract file");
            return null;
        }

        try {
            aFile.close();
        } catch (IOException ex) {
            log.error("Asset: Cannot close file " + ex.getClass().getName() + " " + ex.getMessage());
        }

        return raw;
    }

    private boolean validateContract(byte[] contractContent) {
        if (contractContent == null) {
            assetContractState = CSAssetContractState.CANNOT_PARSE;
            return false;
        }

        CSPDFParser parser = new CSPDFParser(contractContent);

        assetContractState = CSAssetContractState.OK;

        int next = 0;
        while (next < contractContent.length) {
            CSPDFParser.CSPDFObject obj;
            try {
                obj = parser.getObject(next);
                if (obj != null) {
                    next = obj.offsetNext;
                    if (obj.type == CSPDFParser.CSPDFObjectType.STRANGE) {
                        assetContractState = CSAssetContractState.CANNOT_PARSE;
                        return false;
                    } else {
                        CSPDFParser.CSPDFObjectEmbedded embedded = obj.hasEmbeddedFileOrURL();
                        switch (embedded) {
                        case NONE:
                            break;
                        case STRANGE:
                            assetContractState = CSAssetContractState.CANNOT_PARSE;
                            return false;
                        case UNCLEAR:
                        case FILE:
                            assetContractState = CSAssetContractState.POSSIBLE_EMBEDDED_URL;
                            return false;
                        case URL:
                            assetContractState = CSAssetContractState.EMBEDDED_URL;
                            return false;
                        }
                    }
                } else {
                    next = contractContent.length;
                }

            } catch (Exception ex) {
                Logger.getLogger(CSAsset.class.getName()).log(Level.SEVERE, null, ex);
            }

        }

        return true;
    }

    private boolean checkContractAndAssetHash() {
        if (genesis == null) {
            return false;
        }

        if (!checkRequiredFields()) {
            return false;
        }

        byte[] contractContent = readContract();

        if (!validateContract(contractContent)) {
            assetValidationState = CSAssetState.CONTRACT_INVALID;
            return false;
        }

        byte[] assetHash = CoinSparkGenesis.calcAssetHash(name, issuer, description, units, issueDate,
                (expiryDate != null) ? expiryDate : "", interestRate, multiple, contractContent);

        if (!genesis.validateAssetHash(assetHash)) {
            assetValidationState = CSAssetState.HASH_MISMATCH;
            return false;
        }

        try {
            Files.copy(new File(jsonPath).toPath(), new File(validJsonPathToSet).toPath(), REPLACE_EXISTING);
            validJsonPath = validJsonPathToSet;
            Files.copy(new File(contractPath).toPath(), new File(validContractPathToSet).toPath(),
                    REPLACE_EXISTING);
            validContractPath = validContractPathToSet;
            validContractMimeType = contractMimeType;
        } catch (IOException ex) {
            Logger.getLogger(CSAsset.class.getName()).log(Level.SEVERE, null, ex);
        }

        return true;
    }

    private boolean checkRequiredFields() {
        if (genesis == null) {
            return false;
        }
        if (name == null) {
            return false;
        }
        if (name.trim().length() == 0) {
            return false;
        }
        if (nameShort == null) {
            return false;
        }
        if (nameShort.trim().length() == 0) {
            return false;
        }
        if (issuer == null) {
            return false;
        }
        if (issuer.trim().length() == 0) {
            return false;
        }
        if (description == null) {
            return false;
        }
        if (description.trim().length() == 0) {
            return false;
        }

        if (units == null) {
            return false;
        }
        if (units.trim().length() == 0) {
            return false;
        }

        if (contractUrl == null) {
            return false;
        }
        try {
            final URL url = new URL(contractUrl);
            url.toURI();
        } catch (MalformedURLException ex) {
            log.info("Asset details: malformed contract URL " + contractUrl);
            return false;
        } catch (URISyntaxException ex) {
            log.info("Asset details: URL syntax error " + contractUrl);
            return false;
        }
        if (coinsparkTrackerUrls == null) {
            return false;
        }
        for (String trackerUrl : coinsparkTrackerUrls) {
            try {
                final URL url = new URL(trackerUrl);
                url.toURI();
            } catch (MalformedURLException ex) {
                log.info("Asset details: malformed tracker URL " + trackerUrl);
                return false;
            } catch (URISyntaxException ex) {
                log.info("Asset details: URL syntax error " + trackerUrl);
                return false;
            }
        }
        return true;
    }

    private boolean checkAssetDetails(String FilePrefix) {
        if (!checkRequiredFields()) {
            return false;
        }

        String prefix = FilePrefix + String.format("asset%06d", assetID);
        String validPrefix = FilePrefix + String.format("asset%06d_valid", assetID);

        contractMimeType = downloadFile(contractUrl, prefix + "_contract");
        if (contractMimeType != null) {
            contractPath = prefix + "_contract" + contractMimeType.getExtension();
            validContractPathToSet = validPrefix + "_contract" + contractMimeType.getExtension();
        } else {
            assetValidationState = CSAssetState.CONTRACT_NOT_FOUND;
            return false;
        }

        if (iconUrl != null && !iconUrl.isEmpty()) {
            iconMimeType = downloadFile(iconUrl, prefix + "_icon");
            if (iconMimeType != null) {
                iconPath = prefix + "_icon" + iconMimeType.getExtension();
            }
        }

        if (imageUrl != null && !imageUrl.isEmpty()) {
            imageMimeType = downloadFile(imageUrl, prefix + "_image");
            if (imageMimeType != null) {
                imagePath = prefix + "_image" + imageMimeType.getExtension();
            }
        }

        boolean hashValid = checkContractAndAssetHash();

        return hashValid;
    }

    private boolean parseJSONString(String JSONString) {
        if (JSONString == null) {
            return false;
        }

        if (JSONString.length() > 0) {
            try {
                JsonElement jelement = new JsonParser().parse(JSONString);
                JsonObject jresult = jelement.getAsJsonObject();
                JsonElement jvalue;

                // All JSON strings - from asset web page and from asset database 
                jvalue = jresult.get("name");
                if (jvalue != null) {
                    name = jvalue.getAsString();
                }
                jvalue = jresult.get("name_short");
                if (jvalue != null) {
                    nameShort = jvalue.getAsString();
                }
                jvalue = jresult.get("issuer");
                if (jvalue != null) {
                    issuer = jvalue.getAsString();
                }
                jvalue = jresult.get("description");
                if (jvalue != null) {
                    description = jvalue.getAsString();
                }
                jvalue = jresult.get("units");
                if (jvalue != null) {
                    units = jvalue.getAsString();
                }
                jvalue = jresult.get("contract_url");
                if (jvalue != null) {
                    contractUrl = jvalue.getAsString();
                }
                jvalue = jresult.get("coinspark_tracker_url");
                if (jvalue != null) {
                    if (jvalue.isJsonArray()) {
                        JsonArray jarray = jvalue.getAsJsonArray();
                        if (jarray.size() > 0) {
                            coinsparkTrackerUrls = new String[jarray.size()];
                            for (int i = 0; i < jarray.size(); i++) {
                                JsonElement jvalueUrl = jarray.get(i);
                                if (jvalueUrl != null) {
                                    coinsparkTrackerUrls[i] = CSUtils.addHttpIfMissing(jvalueUrl.getAsString());
                                }
                            }
                        }
                    } else {
                        coinsparkTrackerUrls = new String[] { CSUtils.addHttpIfMissing(jvalue.getAsString()) };
                    }
                }
                jvalue = jresult.get("issue_date");
                if (jvalue != null) {
                    issueDate = jvalue.getAsString();
                }
                jvalue = jresult.get("expiry_date");
                if (jvalue != null) {
                    expiryDate = jvalue.getAsString();
                }
                jvalue = jresult.get("interest_rate");
                if (jvalue != null) {
                    interestRate = jvalue.getAsDouble();
                }
                jvalue = jresult.get("multiple");
                if (jvalue != null) {
                    multiple = jvalue.getAsDouble();
                }
                jvalue = jresult.get("format");
                if (jvalue != null) {
                    format = jvalue.getAsString();
                }
                jvalue = jresult.get("format_1");
                if (jvalue != null) {
                    format1 = jvalue.getAsString();
                }
                jvalue = jresult.get("icon_url");
                if (jvalue != null) {
                    iconUrl = jvalue.getAsString();
                }
                jvalue = jresult.get("image_url");
                if (jvalue != null) {
                    imageUrl = jvalue.getAsString();
                }
                jvalue = jresult.get("feed_url");
                if (jvalue != null) {
                    feedUrl = jvalue.getAsString();
                }
                jvalue = jresult.get("redemption_irl");
                if (jvalue != null) {
                    redemptionUrl = jvalue.getAsString();
                }

                // From asset database only
                jvalue = jresult.get("asset_id");
                if (jvalue != null) {
                    assetID = jvalue.getAsInt();
                }
                jvalue = jresult.get("date_created");
                if (jvalue != null) {
                    dateCreation = CSUtils.iso86012date(jvalue.getAsString());
                }
                jvalue = jresult.get("asset_state");
                if (jvalue != null) {
                    assetState = CSAssetState.valueOf(jvalue.getAsString());
                }
                jvalue = jresult.get("asset_source");
                if (jvalue != null) {
                    assetSource = CSAssetSource.valueOf(jvalue.getAsString());
                }
                jvalue = jresult.get("asset_contract_state");
                if (jvalue != null) {
                    assetContractState = CSAssetContractState.valueOf(jvalue.getAsString());
                }
                jvalue = jresult.get("gen_txid");
                if (jvalue != null) {
                    genTxID = jvalue.getAsString();
                }
                jvalue = jresult.get("visible");
                if (jvalue != null) {
                    if (jvalue.getAsInt() == 0) {
                        visible = false;
                    } else {
                        visible = true;
                    }
                }
                jvalue = jresult.get("fsi_txid");
                if (jvalue != null) {
                    firstSpentTxID = jvalue.getAsString();
                }
                jvalue = jresult.get("fsi_vout");
                if (jvalue != null) {
                    firstSpentVout = jvalue.getAsInt();
                }
                jvalue = jresult.get("genesis");
                if (jvalue != null) {
                    genesis = new CoinSparkGenesis();
                    if (!genesis.decode(jvalue.getAsString())) {
                        genesis = null;
                    }
                }
                jvalue = jresult.get("asset_ref_block");
                if (jvalue != null) {
                    assetRef = new CoinSparkAssetRef();
                    assetRef.setBlockNum(jvalue.getAsInt());
                    jvalue = jresult.get("asset_ref_offset");
                    if (jvalue != null) {
                        assetRef.setTxOffset(jvalue.getAsInt());
                    }
                    jvalue = jresult.get("asset_ref_prefix");
                    if (jvalue != null) {
                        assetRef.setTxIDPrefix(CSUtils.hex2Byte(jvalue.getAsString()));
                    }
                }
                jvalue = jresult.get("valid_checked");
                if (jvalue != null) {
                    validChecked = CSUtils.iso86012date(jvalue.getAsString());
                }
                jvalue = jresult.get("valid_failures");
                if (jvalue != null) {
                    validFailures = jvalue.getAsInt();
                }
                jvalue = jresult.get("contract_path");
                if (jvalue != null) {
                    contractPath = jvalue.getAsString();
                }
                jvalue = jresult.get("valid_contract_path");
                if (jvalue != null) {
                    validContractPath = jvalue.getAsString();
                }
                jvalue = jresult.get("json_path");
                if (jvalue != null) {
                    jsonPath = jvalue.getAsString();
                }
                jvalue = jresult.get("valid_json_path");
                if (jvalue != null) {
                    validJsonPathToSet = jvalue.getAsString();
                }
                jvalue = jresult.get("contract_mime");
                if (jvalue != null) {
                    contractMimeType = CSUtils.CSMimeType.fromExtension(jvalue.getAsString());
                }
                jvalue = jresult.get("valid_contract_mime");
                if (jvalue != null) {
                    validContractMimeType = CSUtils.CSMimeType.fromExtension(jvalue.getAsString());
                }
                jvalue = jresult.get("image_path");
                if (jvalue != null) {
                    imagePath = jvalue.getAsString();
                }
                jvalue = jresult.get("image_mime");
                if (jvalue != null) {
                    imageMimeType = CSUtils.CSMimeType.fromExtension(jvalue.getAsString());
                }
                jvalue = jresult.get("icon_path");
                if (jvalue != null) {
                    iconPath = jvalue.getAsString();
                }
                jvalue = jresult.get("icon_mime");
                if (jvalue != null) {
                    iconMimeType = CSUtils.CSMimeType.fromExtension(jvalue.getAsString());
                }
            } catch (JsonSyntaxException ex) {
                assetValidationState = CSAssetState.ASSET_SPECS_NOT_PARSED;
                log.info("Asset details: Error while parsing JSON String " + JSONString);
                return false;
            }
        } else {
            return false;
        }

        if (!checkRequiredFields()) {
            assetValidationState = CSAssetState.REQUIRED_FIELD_MISSING;
            return false;
        }

        return true;
    }

    private boolean validateDetails(String FilePrefix) {
        if (assetValidationState == CSAssetState.NO_KEY) {
            return false;
        }

        if (genesis == null) {
            validChecked = new Date();
            validFailures++;

            return true;
        }

        String jsonString = fetchDetailsJSON(FilePrefix);

        if (!parseJSONString(jsonString)) {
            validChecked = new Date();
            validFailures++;

            return true;
        }

        if (checkAssetDetails(FilePrefix)) {
            validChecked = new Date();
            validFailures = 0;
            assetValidationState = CSAssetState.VALID;
        } else {
            validChecked = new Date();
            validFailures++;
        }

        return true;
    }

    /**
     * Sets refresh flag.
     */

    public void setRefreshState() {
        if (assetState != CSAssetState.REFRESH) {
            assetStateBeforeRefresh = assetState;
        }
        assetState = CSAssetState.REFRESH;
    }

    /**
     * Returns number of seconds from now when the asset will be validated. If 0 is returned asset will be validate next time CSAssetDatabase.validateAssets() is called.
     * @return interval until next validation.
     */

    public long nextValidationInterval() {
        long interval = 0;

        if (validChecked == null) {
            return 0;
        }

        if (genesis != null) {
            switch (assetValidationState) {
            case NOT_VALIDATED_YET:
            case REFRESH:
            case INVALID:
                break;
            case VALID:
                interval = 86400;
                interval -= (new Date().getTime() - validChecked.getTime()) / 1000;
                break;
            default:
                if (validFailures < 60) {
                    interval = 0;
                } else {
                    if (validFailures < 200) {
                        interval = 1800;
                    } else {
                        interval = 86400;
                    }
                }
                interval -= (new Date().getTime() - validChecked.getTime()) / 1000;
                break;
            }
        } else {
            switch (assetValidationState) {
            case TX_NOT_FOUND:
            case GENESIS_NOT_FOUND:
                if (validFailures < 10) {
                    interval = 0;
                } else {
                    if (validFailures < 20) {
                        interval = 600;
                    } else {
                        interval = 86400;
                    }
                }
                interval -= (new Date().getTime() - validChecked.getTime()) / 1000;
                break;
            }
        }

        if (interval < 0) {
            interval = 0;
        }

        return interval;
    }

    /**
     * Asset can be sent only if this function return true and asset.getAssetState()=CSAssetState.VALID
     * @return validity flag of Asset reference
     */

    public boolean isAssetRefValid() {
        if (assetRef == null) {
            return false;
        }

        if (assetRef.getTxOffset() == 0) {
            return false;
        }

        return true;
    }

    /**
     * Validate asset.Retrieves Genesis if only AssetRef is given
    Retrieves Asset details from JSON string if missing
    Validates Asset details and download contract, icon and image if needed
     * @param FilePrefix Full path to the directory (with possible prefix) where asset details files should be stored
     * @param pg PeerGroup used for network communications
     * @param ForceRefresh Validate asset even if nextValidationInterval is positive
     * @return true if update required
     */

    protected boolean validate(String FilePrefix, PeerGroup pg, boolean ForceRefresh) {
        boolean updateRequired = false;

        assetValidationState = assetState;

        updateRequired |= validateGenesis(pg);

        boolean refreshDetails = updateRequired;

        if (nextValidationInterval() == 0) {
            refreshDetails = true;
        }

        if (ForceRefresh) {
            refreshDetails = true;
        }

        if (refreshDetails) {
            CSEventBus.INSTANCE.postAsyncEvent(CSEventType.ASSET_VALIDATION_STARTED, assetID);
            updateRequired |= validateDetails(FilePrefix);
        }

        updateRequired |= (assetState != assetValidationState);

        assetState = assetValidationState;

        return updateRequired;
    }

    private JsonObject getJSONObject() {
        JsonObject jobject = new JsonObject();
        if (name != null) {
            jobject.addProperty("name", name);
        }
        if (nameShort != null) {
            jobject.addProperty("name_short", nameShort);
        }
        if (issuer != null) {
            jobject.addProperty("issuer", issuer);
        }
        if (description != null) {
            jobject.addProperty("description", description);
        }
        if (units != null) {
            jobject.addProperty("units", units);
        }
        if (contractUrl != null) {
            jobject.addProperty("contract_url", contractUrl);
        }
        if (coinsparkTrackerUrls != null) {
            JsonArray jarray = new JsonArray();
            for (String trackerUrl : coinsparkTrackerUrls) {
                jarray.add(new JsonPrimitive(trackerUrl));
            }
            jobject.add("coinspark_tracker_url", jarray);
        }
        if (issueDate != null) {
            jobject.addProperty("issue_date", issueDate);
        }
        if (expiryDate != null) {
            jobject.addProperty("expiry_date", expiryDate);
        }
        if (interestRate != null) {
            jobject.addProperty("interest_rate", interestRate);
        }
        if (multiple != null) {
            jobject.addProperty("multiple", multiple);
        }
        if (format != null) {
            jobject.addProperty("format", format);
        }
        if (format1 != null) {
            jobject.addProperty("format_1", format1);
        }
        if (iconUrl != null) {
            jobject.addProperty("icon_url", iconUrl);
        }
        if (imageUrl != null) {
            jobject.addProperty("image_url", imageUrl);
        }
        if (feedUrl != null) {
            jobject.addProperty("feed_url", feedUrl);
        }
        if (redemptionUrl != null) {
            jobject.addProperty("redemption_irl", redemptionUrl);
        }

        return jobject;
    }

    /**
     * Returns JSON string for this asset.
     * @return 
     */

    private String getJSONString() {
        JsonObject jobject = getJSONObject();
        return new Gson().toJson(jobject);
    }

    /**
     * Serializes asset for storing in database.
     * @return Serialized JSON string.
     */

    protected String serialize() {
        JsonObject jobject = getJSONObject();
        if (dateCreation != null) {
            jobject.addProperty("date_created", CSUtils.date2iso8601(dateCreation));
        }
        if (assetState != null) {
            jobject.addProperty("asset_state", assetState.toString());
        }
        if (assetSource != null) {
            jobject.addProperty("asset_source", assetSource.toString());
        }
        if (assetContractState != null) {
            jobject.addProperty("asset_contract_state", assetContractState.toString());
        }
        if (genTxID != null) {
            jobject.addProperty("gen_txid", genTxID);
        }
        if (visible) {
            jobject.addProperty("visible", 1);
        } else {
            jobject.addProperty("visible", 0);
        }
        if (firstSpentTxID != null) {
            jobject.addProperty("fsi_txid", firstSpentTxID);
            jobject.addProperty("fsi_vout", firstSpentVout);
        }
        if (genesis != null) {
            jobject.addProperty("genesis", genesis.encodeToHex(65536));// We don't care whether it fits OP_RETURN or not
        }
        if (assetRef != null) {
            jobject.addProperty("asset_ref_block", assetRef.getBlockNum());
            jobject.addProperty("asset_ref_offset", assetRef.getTxOffset());
            jobject.addProperty("asset_ref_prefix", CSUtils.byte2Hex(assetRef.getTxIDPrefix()));
        }
        if (validChecked != null) {
            jobject.addProperty("valid_checked", CSUtils.date2iso8601(validChecked));
        }
        jobject.addProperty("valid_failures", validFailures);
        if (contractMimeType != null) {
            jobject.addProperty("contract_mime", contractMimeType.getExtension());
        }
        if (validContractMimeType != null) {
            jobject.addProperty("valid_contract_mime", contractMimeType.getExtension());
        }
        if (imageMimeType != null) {
            jobject.addProperty("image_mime", imageMimeType.getExtension());
        }
        if (iconMimeType != null) {
            jobject.addProperty("icon_mime", iconMimeType.getExtension());
        }

        return new Gson().toJson(jobject);
    }

    /**
     * Returns short asset status.
     * @return short asset status.
     */

    public String status() {
        String s = "";

        s += assetState;
        if (assetRef != null) {
            s += ", Asset Ref.: " + assetRef.encode();
        }
        if (genTxID != null) {
            s += ", Gen. TxID: " + genTxID;
        }

        return s;
    }

    /**
     * Returns full asset status for debugging.
     * @return full asset status.
     */

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();

        sb.append("AssetID:        ").append(assetID).append("\n");
        sb.append("Date created:   ").append(dateCreation == null ? "" : dateCreation.toString()).append("\n");
        sb.append("State:          ").append(assetState == null ? "" : assetState).append("\n");
        sb.append("Source:         ").append(assetSource == null ? "" : assetSource).append("\n");
        sb.append("Genesis IxID:   ").append(genTxID == null ? "" : genTxID).append("\n");
        sb.append("Genesis:        ").append(genesis == null ? "" : genesis.toString()).append("\n");
        sb.append("AssetRef:       ").append(assetRef == null ? "" : assetRef.encode()).append("\n");
        sb.append("JSONString:     ").append(getJSONString()).append("\n");
        sb.append("Date validated: ").append(validChecked == null ? "" : validChecked.toString()).append("\n");
        sb.append("Failure count:  ").append(validFailures).append("\n");
        sb.append("Contract path:  ").append((contractPath == null || contractMimeType == null) ? ""
                : (contractPath + "(" + contractMimeType.getExtension() + ")")).append("\n");
        sb.append("Image path:     ")
                .append(imagePath == null ? "" : (imagePath + "(" + imageMimeType.getExtension() + ")"))
                .append("\n");
        sb.append("Icon path:      ")
                .append(iconPath == null ? "" : (iconPath + "(" + iconMimeType.getExtension() + ")")).append("\n");

        return sb.toString();
    }

}