package org.exist.xquery.value;

import org.apache.commons.io.output.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.output.CloseShieldOutputStream;
import org.apache.log4j.Logger;
import org.exist.xquery.XPathException;

/**
 * Representation of an XSD binary value e.g. (xs:base64Binary or xs:hexBinary)
 * whose source is backed by a pre-encoded String.
 *
 * Note - BinaryValueFromBinaryString is a special case of BinaryValue
 * where the value is already encoded.
 * 
 * @author Adam Retter <adam@existsolutions.com>
 */
public class BinaryValueFromBinaryString extends BinaryValue {

    private final static Logger LOG = Logger.getLogger(BinaryValueFromBinaryString.class);
    
    private final String value;

    public BinaryValueFromBinaryString(BinaryValueType binaryValueType, String value) throws XPathException {
        super(null, binaryValueType);
        this.value = binaryValueType.verifyAndFormatString(value);
    }

    @Override
    public BinaryValue convertTo(BinaryValueType binaryValueType) throws XPathException {
        //TODO temporary approach, consider implementing a TranscodingBinaryValueFromBinaryString(BinaryValueFromBinaryString) class
        //that only does the transncoding lazily

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FilterOutputStream fos = null;
        try {

            //transcode
            fos = binaryValueType.getEncoder(baos);
            streamBinaryTo(fos);

        } catch(final IOException ioe) {
            throw new XPathException(ioe);
        } finally {
            if(fos != null) {
                try {
                    fos.close();
                } catch(final IOException ioe) {
                    LOG.error("Unable to close stream: " + ioe.getMessage(), ioe);
                }
            }

            try {
                baos.close();
            } catch(final IOException ioe) {
                LOG.error("Unable to close stream: " + ioe.getMessage(), ioe);
            }
        }

        return new BinaryValueFromBinaryString(binaryValueType, new String(baos.toByteArray()));
    }

    @Override
    public void streamBinaryTo(OutputStream os) throws IOException {
        
        //we need to create a safe output stream that cannot be closed
        final OutputStream safeOutputStream = new CloseShieldOutputStream(os);
        
        //get the decoder
        final FilterOutputStream fos = getBinaryValueType().getDecoder(safeOutputStream);
        
        //write with the decoder
        final byte data[] = value.getBytes();
        fos.write(data);
        
        //we do have to close the decoders output stream though
        //to ensure that all bytes have been written, this is
        //particularly nessecary for Apache Commons Codec stream encoders
        try {
            fos.close();
        } catch(final IOException ioe) {
            LOG.error("Unable to close stream: " + ioe.getMessage(), ioe);
        }
    }

    @Override
    public void streamTo(OutputStream os) throws IOException {
        //write
        final byte data[] = value.getBytes(); //TODO consider a more efficient approach for writting large strings
        os.write(data);
    }

    @Override
    public InputStream getInputStream() {

        //TODO consider a more efficient approach for writting large strings
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            streamBinaryTo(baos);
        } catch(final IOException ioe) {
            LOG.error("Unable to get read only buffer: " + ioe.getMessage(), ioe);
        }

        return new InputStream() {
            int offset = 0;
            final byte data[] = baos.toByteArray();

            @Override
            public int read() throws IOException {

                if(offset >= data.length) {
                    return -1;
                }
                return data[offset++];
            }
        };
    }

    @Override
    public void close() throws IOException {
    }
}
@@ -89,128 +89,8 @@
 am;%0A
-import java.io.FilterOutputStream;%0Aimport java.io.IOException;%0Aimport java.io.InputStream;%0Aimport java.io.OutputStream;%0A
 impo
@@ -219,16 +219,35 @@
 ption;%0A%0A
+import java.io.*;%0A%0A
 /**%0A * R
@@ -3645,16 +3645,25 @@
 urn new 
+ByteArray
 InputStr
@@ -3670,322 +3670,27 @@
 eam(
-) %7B%0A            int offset = 0;%0A            final byte data%5B%5D = baos.toByteArray();%0A%0A            @Override%0A            public int read() throws IOException %7B%0A%0A                if(offset %3E= data.length) %7B%0A                    return -1;%0A                %7D%0A                return data%5Boffset++%5D;%0A            %7D%0A        %7D
+baos.toByteArray())
 ;%0A  

fix BinaryValueFromBinaryString use standard ByteArrayInputString instead overriding InputStream (avoid NPE in some cases)