package ee.shy.storage;

import ee.shy.io.Jsonable;
import ee.shy.map.UnkeyableSimpleMap;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Map-like abstract storage class for binary data.
 * Access data by hash.
 *
 * @see Hash
 * @see MapStorage
 * @see FileStorage
 */
public abstract class DataStorage implements UnkeyableSimpleMap<Hash, InputStream> {
    /**
     * Adds binary data from input stream to storage.
     * @param source input stream to get data from
     * @return hash of stored data
     */
    @Override
    public final Hash put(InputStream source) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            DigestInputStream dis = new DigestInputStream(source, md);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            IOUtils.copy(dis, baos);

            Hash hash = new Hash(md);
            put(hash, new ByteArrayInputStream(baos.toByteArray()));
            return hash;
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Adds object as JSON to storage.
     * @param object object to store
     * @return hash of stored data
     */
    public final Hash put(Jsonable object) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DigestOutputStream dos = new DigestOutputStream(baos, md);

            object.write(dos);

            Hash hash = new Hash(md);
            put(hash, new ByteArrayInputStream(baos.toByteArray()));
            return hash;
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Adds binary data from input stream with precomputed hash.
     * @param hash hash of data from input stream
     * @param source input stream to get data from
     * @throws IOException if there was a problem reading the input stream or writing to some output
     */
    protected abstract void put(Hash hash, InputStream source) throws IOException;

    /**
     * Gets binary data as input stream by hash.
     * Input data is checked against given hash to ensure correctness.
     * @param hash hash of data to get
     * @return input stream of data
     */
    @Override
    public final InputStream get(Hash hash) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            DigestInputStream dis = new DigestInputStream(getUnchecked(hash), md);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            IOUtils.copy(dis, baos);

            Hash hashComputed = new Hash(md);
            if (!hash.equals(hashComputed))
                throw new RuntimeException("stored file content does not match hash");

            return new ByteArrayInputStream(baos.toByteArray());
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Gets binary data as input stream by hash.
     * Input data is <b>not</b> checked against given hash to ensure correctness.
     * @param hash hash of data to get
     * @return input stream of data
     * @throws IOException if there was a problem reading from some input
     */
    protected abstract InputStream getUnchecked(Hash hash) throws IOException;
}

@@ -18,16 +18,39 @@
 orage;%0A%0A
+import ee.shy.io.Json;%0A
 import e
@@ -3468,32 +3468,403 @@
        %7D%0A    %7D%0A%0A
+    /**%0A     * Gets object from JSON by hash.%0A     * @param hash hash of object to get%0A     * @param classofT class of object to get%0A     * @param %3CT%3E type of obect to get%0A     * @return object from JSON%0A     * @throws IOException%0A     */%0A    public final %3CT%3E T get(Hash hash, Class%3CT%3E classofT) throws IOException %7B%0A        return Json.read(get(hash), classofT);%0A    %7D%0A%0A
     /**%0A     * G

Make DataStorage JSON functionality symmetric