diff --git a/exist-core/src/main/java/org/exist/xquery/ArrowOperator.java b/exist-core/src/main/java/org/exist/xquery/ArrowOperator.java index 3afcf45b71a..cec8410c171 100644 --- a/exist-core/src/main/java/org/exist/xquery/ArrowOperator.java +++ b/exist-core/src/main/java/org/exist/xquery/ArrowOperator.java @@ -95,23 +95,7 @@ public Sequence eval(Sequence contextSequence, final Item contextItem) throws XP } contextSequence = leftExpr.eval(contextSequence, null); - final FunctionReference fref; - if (fcall != null) { - fref = new FunctionReference(this, fcall); - } else { - final Sequence funcSeq = funcSpec.eval(contextSequence, contextItem); - if (funcSeq.getCardinality() != Cardinality.EXACTLY_ONE) - {throw new XPathException(this, ErrorCodes.XPTY0004, - "Expected exactly one item for the function to be called, got " + funcSeq.getItemCount() + - ". Expression: " + ExpressionDumper.dump(funcSpec));} - final Item item0 = funcSeq.itemAt(0); - if (!Type.subTypeOf(item0.getType(), Type.FUNCTION_REFERENCE)) { - throw new XPathException(this, ErrorCodes.XPTY0004, - "Type error: expected function, got " + Type.getTypeName(item0.getType())); - } - fref = (FunctionReference)item0; - } - try { + try (final FunctionReference fref = getFunctionReference(contextSequence, contextItem)) { final List fparams = new ArrayList<>(parameters.size() + 1); fparams.add(new ContextParam(context, contextSequence)); fparams.addAll(parameters); @@ -122,11 +106,29 @@ public Sequence eval(Sequence contextSequence, final Item contextItem) throws XP fref.analyze(new AnalyzeContextInfo(cachedContextInfo)); // Evaluate the function return fref.eval(null); - } finally { - fref.close(); } } + private FunctionReference getFunctionReference(final Sequence contextSequence, final Item contextItem) throws XPathException { + if (fcall != null) { + return new FunctionReference(this, fcall); + } + + final Sequence funcSeq = funcSpec.eval(contextSequence, contextItem); + if (funcSeq.getCardinality() != Cardinality.EXACTLY_ONE) { + throw new XPathException(this, ErrorCodes.XPTY0004, + "Expected exactly one item for the function to be called, got " + funcSeq.getItemCount() + + ". Expression: " + ExpressionDumper.dump(funcSpec)); + } + + final Item item = funcSeq.itemAt(0); + if (!Type.subTypeOf(item.getType(), Type.FUNCTION_REFERENCE)) { + throw new XPathException(this, ErrorCodes.XPTY0004, + "Type error: expected function, got " + Type.getTypeName(item.getType())); + } + return (FunctionReference) item; + } + @Override public int returnsType() { return fcall == null ? Type.ITEM : fcall.returnsType(); diff --git a/exist-core/src/main/java/org/exist/xquery/functions/util/BinaryToString.java b/exist-core/src/main/java/org/exist/xquery/functions/util/BinaryToString.java index 082224fa542..7745fd71fe4 100644 --- a/exist-core/src/main/java/org/exist/xquery/functions/util/BinaryToString.java +++ b/exist-core/src/main/java/org/exist/xquery/functions/util/BinaryToString.java @@ -22,29 +22,18 @@ package org.exist.xquery.functions.util; import java.io.IOException; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Base64; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.exist.EXistException; import org.exist.dom.QName; -import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream; import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; -import org.exist.xquery.BasicFunction; -import org.exist.xquery.Cardinality; -import org.exist.xquery.FunctionSignature; -import org.exist.xquery.XPathException; -import org.exist.xquery.XQueryContext; -import org.exist.xquery.value.Base64BinaryValueType; -import org.exist.xquery.value.BinaryValue; -import org.exist.xquery.value.BinaryValueFromInputStream; -import org.exist.xquery.value.FunctionParameterSequenceType; -import org.exist.xquery.value.FunctionReturnSequenceType; -import org.exist.xquery.value.Sequence; -import org.exist.xquery.value.SequenceType; -import org.exist.xquery.value.StringValue; -import org.exist.xquery.value.Type; - -import static java.nio.charset.StandardCharsets.UTF_8; +import org.exist.xquery.*; +import org.exist.xquery.value.*; public class BinaryToString extends BasicFunction { @@ -95,37 +84,47 @@ public BinaryToString(XQueryContext context, FunctionSignature signature) { } @Override - public Sequence eval(Sequence[] args, Sequence contextSequence) - throws XPathException { + public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { - if(args[0].isEmpty()) { + if (args[0].isEmpty()) { return Sequence.EMPTY_SEQUENCE; } - String encoding = UTF_8.name(); - if(args.length == 2) { - encoding = args[1].getStringValue(); - } - if(isCalledAs("binary-to-string")) { + + final Charset encoding = getCharset(args); + + if (isCalledAs("binary-to-string")) { return binaryToString((BinaryValue) args[0].itemAt(0), encoding); + } + + return stringToBinary(args[0].getStringValue(), encoding); + } + + private Charset getCharset(Sequence[] args) throws XPathException { + final Charset encoding; + if (args.length == 2) { + final String stringValue = args[1].getStringValue(); + try { + encoding = stringValue.isEmpty() ? StandardCharsets.UTF_8 : Charset.forName(stringValue); + } catch(final UnsupportedCharsetException e) { + throw new XPathException(this, UtilErrorCodes.UNRECOGNIZED_ENCODING, "Unsupported encoding: " + stringValue); + } } else { - return stringToBinary(args[0].getStringValue(), encoding); + encoding = StandardCharsets.UTF_8; } + return encoding; } - protected StringValue binaryToString(BinaryValue binary, String encoding) throws XPathException { + protected StringValue binaryToString(final BinaryValue binary, final Charset encoding) throws XPathException { try (final UnsynchronizedByteArrayOutputStream os = new UnsynchronizedByteArrayOutputStream()) { binary.streamBinaryTo(os); return new StringValue(this, os.toString(encoding)); } catch(final IOException ioe) { - throw new XPathException(this, ioe); + throw new XPathException(this, UtilErrorCodes.IO_ERROR, ioe); } } - protected BinaryValue stringToBinary(String str, String encoding) throws XPathException { - try { - return BinaryValueFromInputStream.getInstance(context, new Base64BinaryValueType(), new UnsynchronizedByteArrayInputStream(str.getBytes(encoding)), this); - } catch(final UnsupportedEncodingException e) { - throw new XPathException(this, "Unsupported encoding: " + encoding); - } + protected BinaryValue stringToBinary(final String str, final Charset encoding) throws XPathException { + return new BinaryValueFromBinaryString(new Base64BinaryValueType(), + Base64.getEncoder().encodeToString(str.getBytes(encoding))); } } diff --git a/exist-core/src/main/java/org/exist/xquery/functions/util/UtilErrorCodes.java b/exist-core/src/main/java/org/exist/xquery/functions/util/UtilErrorCodes.java new file mode 100644 index 00000000000..a09e01a8c1e --- /dev/null +++ b/exist-core/src/main/java/org/exist/xquery/functions/util/UtilErrorCodes.java @@ -0,0 +1,37 @@ +/* + * eXist-db Open Source Native XML Database + * Copyright (C) 2001 The eXist-db Authors + * + * info@exist-db.org + * http://www.exist-db.org + * + * This 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 2.1 of the License, or (at your option) any later version. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +package org.exist.xquery.functions.util; + +import org.exist.dom.QName; +import org.exist.xquery.ErrorCodes; + +class UtilErrorCodes extends ErrorCodes.ErrorCode { + public static final ErrorCodes.ErrorCode UNRECOGNIZED_ENCODING = new UtilErrorCodes("UNRECOGNIZED_ENCODING", + "The encoding is not recognized."); + + public static final ErrorCodes.ErrorCode IO_ERROR = new UtilErrorCodes("IO_ERROR", + "There was an issue accessing system resources."); + + UtilErrorCodes(final String code, final String description) { + super(new QName(code, UtilModule.NAMESPACE_URI, UtilModule.PREFIX), description); + } +} diff --git a/exist-core/src/main/java/org/exist/xquery/value/BinaryValueFromBinaryString.java b/exist-core/src/main/java/org/exist/xquery/value/BinaryValueFromBinaryString.java index 8d998559b24..d8152d1f6fb 100644 --- a/exist-core/src/main/java/org/exist/xquery/value/BinaryValueFromBinaryString.java +++ b/exist-core/src/main/java/org/exist/xquery/value/BinaryValueFromBinaryString.java @@ -29,6 +29,7 @@ import org.exist.xquery.XPathException; import java.io.*; +import java.nio.charset.Charset; import static java.nio.charset.StandardCharsets.UTF_8; @@ -45,6 +46,7 @@ public class BinaryValueFromBinaryString extends BinaryValue { private final static Logger LOG = LogManager.getLogger(BinaryValueFromBinaryString.class); +// private final Charset encoding; private final String value; private boolean closed = false; @@ -55,40 +57,26 @@ public BinaryValueFromBinaryString(BinaryValueType binaryValueType, String value public BinaryValueFromBinaryString(final Expression expression, BinaryValueType binaryValueType, String value) throws XPathException { super(expression, null, binaryValueType); this.value = binaryValueType.verifyAndFormatString(value); + //this.encoding = Charset.defaultCharset(); + } + + public BinaryValueFromBinaryString(BinaryValueType binaryValueType, String value, Charset encoding) throws XPathException { + super(null, null, binaryValueType); + this.value = binaryValueType.verifyAndFormatString(value); +// this.encoding = encoding; } @Override - public BinaryValue convertTo(BinaryValueType binaryValueType) throws XPathException { + public BinaryValue convertTo(final BinaryValueType binaryValueType) throws XPathException { //TODO temporary approach, consider implementing a TranscodingBinaryValueFromBinaryString(BinaryValueFromBinaryString) class - //that only does the transncoding lazily - - final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream(); - FilterOutputStream fos = null; - try { - + //that only does the transcoding lazily + try (final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream(); final FilterOutputStream fos = binaryValueType.getEncoder(baos)) { //transcode - fos = binaryValueType.getEncoder(baos); streamBinaryTo(fos); - + return new BinaryValueFromBinaryString(getExpression(), binaryValueType, baos.toString(UTF_8)); } catch (final IOException ioe) { throw new XPathException(getExpression(), 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(getExpression(), binaryValueType, baos.toString(UTF_8)); } @Override @@ -101,7 +89,7 @@ public void streamBinaryTo(OutputStream os) throws IOException { final FilterOutputStream fos = getBinaryValueType().getDecoder(safeOutputStream); //write with the decoder - final byte data[] = value.getBytes(); + final byte[] data = value.getBytes(); fos.write(data); //we do have to close the decoders output stream though @@ -117,13 +105,13 @@ public void streamBinaryTo(OutputStream os) throws IOException { @Override public void streamTo(OutputStream os) throws IOException { //write - final byte data[] = value.getBytes(); //TODO consider a more efficient approach for writing large strings + final byte[] data = value.getBytes(); //TODO consider a more efficient approach for writing large strings os.write(data); } @Override public InputStream getInputStream() { - //TODO consider a more efficient approach for writting large strings + //TODO consider a more efficient approach for writing large strings final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream(); try { streamBinaryTo(baos); diff --git a/exist-core/src/test/java/org/exist/xquery/functions/util/BinaryToStringTest.java b/exist-core/src/test/java/org/exist/xquery/functions/util/BinaryToStringTest.java index c2e75c3018b..bad0aee8e99 100644 --- a/exist-core/src/test/java/org/exist/xquery/functions/util/BinaryToStringTest.java +++ b/exist-core/src/test/java/org/exist/xquery/functions/util/BinaryToStringTest.java @@ -27,6 +27,10 @@ import org.exist.xquery.value.BinaryValue; import org.exist.xquery.value.StringValue; import org.junit.Test; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + import static org.junit.Assert.assertEquals; /** @@ -36,9 +40,9 @@ public class BinaryToStringTest { @Test - public void roundtrip() throws XPathException { + public void roundTrip() throws XPathException { final String value = "hello world"; - final String encoding = "UTF-8"; + final Charset encoding = StandardCharsets.UTF_8; TestableBinaryToString testable = new TestableBinaryToString(new MockXQueryContext(), null); @@ -54,12 +58,12 @@ public TestableBinaryToString(XQueryContext context, FunctionSignature signature } @Override - public StringValue binaryToString(BinaryValue binary, String encoding) throws XPathException { + public StringValue binaryToString(BinaryValue binary, Charset encoding) throws XPathException { return super.binaryToString(binary, encoding); } @Override - public BinaryValue stringToBinary(String str, String encoding) throws XPathException { + public BinaryValue stringToBinary(String str, Charset encoding) throws XPathException { return super.stringToBinary(str, encoding); } } diff --git a/extensions/modules/compression/src/main/java/org/exist/xquery/modules/compression/DeflateFunction.java b/extensions/modules/compression/src/main/java/org/exist/xquery/modules/compression/DeflateFunction.java index 1ccd9b00579..d1ce2d9fde0 100644 --- a/extensions/modules/compression/src/main/java/org/exist/xquery/modules/compression/DeflateFunction.java +++ b/extensions/modules/compression/src/main/java/org/exist/xquery/modules/compression/DeflateFunction.java @@ -36,11 +36,12 @@ import org.exist.xquery.value.BinaryValue; import org.exist.xquery.value.BinaryValueFromInputStream; import org.exist.xquery.value.FunctionParameterSequenceType; -import org.exist.xquery.value.BooleanValue; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.SequenceType; import org.exist.xquery.value.Type; +import static java.util.zip.Deflater.DEFAULT_COMPRESSION; + /** * Deflate compression @@ -52,7 +53,7 @@ public class DeflateFunction extends BasicFunction { private final static QName DEFLATE_FUNCTION_NAME = new QName("deflate", CompressionModule.NAMESPACE_URI, CompressionModule.PREFIX); - public final static FunctionSignature signatures[] = { + public final static FunctionSignature[] signatures = { new FunctionSignature( DEFLATE_FUNCTION_NAME, "Deflate data (RFC 1950)", @@ -84,25 +85,29 @@ public DeflateFunction(XQueryContext context, FunctionSignature signature) public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { // is there some data to Deflate? - if(args[0].isEmpty()) + if (args[0].isEmpty()) { return Sequence.EMPTY_SEQUENCE; + } - BinaryValue bin = (BinaryValue) args[0].itemAt(0); + final BinaryValue bin = (BinaryValue) args[0].itemAt(0); - boolean rawflag = false; - if(args.length > 1 && !args[1].isEmpty()) - rawflag = args[1].itemAt(0).convertTo(Type.BOOLEAN).effectiveBooleanValue(); + boolean rawflag = false; + if (args.length > 1 && !args[1].isEmpty()) { + rawflag = args[1].itemAt(0).convertTo(Type.BOOLEAN).effectiveBooleanValue(); + } - Deflater defl = new Deflater(java.util.zip.Deflater.DEFAULT_COMPRESSION, rawflag); + final Deflater deflater = new Deflater(DEFAULT_COMPRESSION, rawflag); // deflate the data - try(final UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream(); - DeflaterOutputStream dos = new DeflaterOutputStream(baos, defl)) { + try ( + final UnsynchronizedByteArrayOutputStream stream = new UnsynchronizedByteArrayOutputStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(stream, deflater) + ) { bin.streamBinaryTo(dos); dos.flush(); dos.finish(); - return BinaryValueFromInputStream.getInstance(context, new Base64BinaryValueType(), baos.toInputStream(), this); + return BinaryValueFromInputStream.getInstance(context, new Base64BinaryValueType(), stream.toInputStream(), this); } catch(IOException ioe) { throw new XPathException(this, ioe.getMessage(), ioe); }