org.exist.xquery.value.BinaryValue.java Source code

Java tutorial

Introduction

Here is the source code for org.exist.xquery.value.BinaryValue.java

Source

/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-04 The eXist Project
 *  http://exist-db.org
 *  
 *  This program 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 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 *  $Id$
 */
package org.exist.xquery.value;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.Collator;
import org.apache.commons.io.output.CloseShieldOutputStream;
import org.apache.log4j.Logger;
import org.apache.commons.io.output.ByteArrayOutputStream;

import org.exist.xquery.Constants;
import org.exist.xquery.XPathException;

/**
 * @author Adam Retter <adam@existsolutions.com>
 */
public abstract class BinaryValue extends AtomicValue {

    private final static Logger LOG = Logger.getLogger(BinaryValue.class);

    protected final int READ_BUFFER_SIZE = 4 * 1024; //4kb

    private final BinaryValueManager binaryValueManager;
    private final BinaryValueType binaryValueType;

    protected BinaryValue(BinaryValueManager binaryValueManager, BinaryValueType binaryValueType) {
        this.binaryValueManager = binaryValueManager;
        this.binaryValueType = binaryValueType;
    }

    protected final BinaryValueManager getManager() {
        return binaryValueManager;
    }

    protected BinaryValueType getBinaryValueType() {
        return binaryValueType;
    }

    @Override
    public int getType() {
        return getBinaryValueType().getXQueryType();
    }

    @Override
    public boolean compareTo(Collator collator, int operator, AtomicValue other) throws XPathException {
        if (other.getType() == Type.HEX_BINARY || other.getType() == Type.BASE64_BINARY) {
            final int value = compareTo((BinaryValue) other);
            switch (operator) {
            case Constants.EQ:
                return value == 0;
            case Constants.NEQ:
                return value != 0;
            case Constants.GT:
                return value > 0;
            case Constants.GTEQ:
                return value >= 0;
            case Constants.LT:
                return value < 0;
            case Constants.LTEQ:
                return value <= 0;
            default:
                throw new XPathException("Type error: cannot apply operator to numeric value");
            }
        } else {
            throw new XPathException(
                    "Cannot compare value of type xs:hexBinary with " + Type.getTypeName(other.getType()));
        }
    }

    @Override
    public int compareTo(Collator collator, AtomicValue other) throws XPathException {
        if (other.getType() == Type.HEX_BINARY || other.getType() == Type.BASE64_BINARY) {
            return compareTo((BinaryValue) other);
        } else {
            throw new XPathException(
                    "Cannot compare value of type xs:hexBinary with " + Type.getTypeName(other.getType()));
        }
    }

    private int compareTo(BinaryValue otherValue) {

        final InputStream is = getInputStream();
        final InputStream otherIs = otherValue.getInputStream();

        if (is == null && otherIs == null) {
            return 0;
        } else if (is == null) {
            return -1;
        } else if (otherIs == null) {
            return 1;
        } else {
            int read = -1;
            int otherRead = -1;
            while (true) {
                try {
                    read = is.read();
                } catch (final IOException ioe) {
                    return -1;
                }

                try {
                    otherRead = otherIs.read();
                } catch (final IOException ioe) {
                    return 1;
                }

                return read - otherRead;
            }
        }
    }

    @Override
    public <T> T toJavaObject(Class<T> target) throws XPathException {
        if (target.isAssignableFrom(getClass())) {
            return (T) this;
        }

        if (target == byte[].class) {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                streamBinaryTo(baos);
                return (T) baos.toByteArray();
            } catch (final IOException ioe) {
                LOG.error("Unable to Stream BinaryValue to byte[]: " + ioe.getMessage(), ioe);
            }

        }

        throw new XPathException("Cannot convert value of type " + Type.getTypeName(getType())
                + " to Java object of type " + target.getName());
    }

    /**
     * Return the underlying Java object for this binary value. Might be a File or byte[].
     */
    public <T> T toJavaObject() throws XPathException {
        return (T) toJavaObject(byte[].class);
    }

    @Override
    public AtomicValue max(Collator collator, AtomicValue other) throws XPathException {
        throw new XPathException("Cannot compare values of type " + Type.getTypeName(getType()));
    }

    @Override
    public AtomicValue min(Collator collator, AtomicValue other) throws XPathException {
        throw new XPathException("Cannot compare values of type " + Type.getTypeName(getType()));
    }

    @Override
    public AtomicValue convertTo(final int requiredType) throws XPathException {

        final AtomicValue result;

        if (requiredType == getType() || requiredType == Type.ITEM || requiredType == Type.ATOMIC) {
            result = this;
        } else {
            switch (requiredType) {
            case Type.BASE64_BINARY:
                result = convertTo(new Base64BinaryValueType());
                break;
            case Type.HEX_BINARY:
                result = convertTo(new HexBinaryValueType());
                break;
            case Type.UNTYPED_ATOMIC:
                //TODO still needed? Added trim() since it looks like a new line character is added
                result = new UntypedAtomicValue(getStringValue());
                break;
            case Type.STRING:
                //TODO still needed? Added trim() since it looks like a new line character is added
                result = new StringValue(getStringValue());
                break;
            default:
                throw new XPathException(
                        "cannot convert " + Type.getTypeName(getType()) + " to " + Type.getTypeName(requiredType));
            }
        }
        return result;
    }

    public abstract BinaryValue convertTo(BinaryValueType binaryValueType) throws XPathException;

    @Override
    public int conversionPreference(Class<?> javaClass) {
        if (javaClass.isArray() && javaClass.isInstance(Byte.class)) {
            return 0;
        }

        return Integer.MAX_VALUE;
    }

    @Override
    public boolean effectiveBooleanValue() throws XPathException {
        throw new XPathException(
                "FORG0006: value of type " + Type.getTypeName(getType()) + " has no boolean value.");
    }

    //TODO ideally this should be moved out into serialization where we can stream the output from the buf/channel by calling streamTo()
    @Override
    public String getStringValue() throws XPathException {

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            streamTo(baos);
        } catch (final IOException ex) {
            throw new XPathException("Unable to encode string value: " + ex.getMessage(), ex);
        } finally {
            try {
                baos.close(); //close the stream to ensure all data is flushed
            } catch (final IOException ioe) {
                LOG.error("Unable to close stream: " + ioe.getMessage(), ioe);
            }
        }

        return new String(baos.toByteArray());
    }

    /**
     * Streams the raw binary data
     */
    public abstract void streamBinaryTo(OutputStream os) throws IOException;

    /**
     * Streams the encoded binary data
     */
    public void streamTo(OutputStream os) throws IOException {

        //we need to create a safe output stream that cannot be closed
        final OutputStream safeOutputStream = new CloseShieldOutputStream(os);

        //get the encoder
        final FilterOutputStream fos = getBinaryValueType().getEncoder(safeOutputStream);

        //stream with the encoder
        streamBinaryTo(fos);

        //we do have to close the encoders 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);
        }
    }

    public abstract InputStream getInputStream();

    public abstract void close() throws IOException;
}