diff --git a/jOOQ/src/main/java/org/jooq/CharsetProvider.java b/jOOQ/src/main/java/org/jooq/CharsetProvider.java new file mode 100644 index 0000000000..a55213e1ff --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/CharsetProvider.java @@ -0,0 +1,55 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq; + +import java.nio.charset.Charset; + +/** + * A provider of a default {@link Charset} to be used when converting between + * {@link String} data and byte[] data. + * + * @author Lukas Eder + */ +@FunctionalInterface +public interface CharsetProvider { + + /** + * Provide a charset for string to binary conversion. + */ + Charset provide(); +} diff --git a/jOOQ/src/main/java/org/jooq/Configuration.java b/jOOQ/src/main/java/org/jooq/Configuration.java index 0b71ca1d77..81b99dab56 100644 --- a/jOOQ/src/main/java/org/jooq/Configuration.java +++ b/jOOQ/src/main/java/org/jooq/Configuration.java @@ -387,6 +387,12 @@ public interface Configuration extends Serializable { @NotNull UnwrapperProvider unwrapperProvider(); + /** + * Get the configured CharsetProvider from this configuration. + */ + @NotNull + CharsetProvider charsetProvider(); + /** * Get this configuration's underlying record mapper provider. */ @@ -927,6 +933,19 @@ public interface Configuration extends Serializable { @NotNull Configuration set(UnwrapperProvider newUnwrapperProvider); + /** + * Change this configuration to hold a new charset provider. + *

+ * This method is not thread-safe and should not be used in globally + * available Configuration objects. + * + * @param newCharsetProvider The new charset provider to be contained in + * the changed configuration. + * @return The changed configuration. + */ + @NotNull + Configuration set(CharsetProvider newCharsetProvider); + /** * Change this configuration to hold a new converter provider. *

@@ -1291,6 +1310,17 @@ public interface Configuration extends Serializable { @NotNull Configuration derive(UnwrapperProvider newUnwrapperProvider); + /** + * Create a derived configuration from this one, with a new charset + * provider. + * + * @param newCharsetProvider The new charset provider to be contained in + * the derived configuration. + * @return The derived configuration. + */ + @NotNull + Configuration derive(CharsetProvider newCharsetProvider); + /** * Create a derived configuration from this one, with new converter * provider. diff --git a/jOOQ/src/main/java/org/jooq/FilenameComparator.java b/jOOQ/src/main/java/org/jooq/FilenameComparator.java index afbaae6d5c..c57a52de98 100644 --- a/jOOQ/src/main/java/org/jooq/FilenameComparator.java +++ b/jOOQ/src/main/java/org/jooq/FilenameComparator.java @@ -94,8 +94,4 @@ final class FilenameComparator implements Comparator { return split1.length - split2.length; } - - public static void main(String[] args) { - INSTANCE.compare("0", new String(new byte[] { 0 })); - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index 5d4888b9ad..d38cacf26d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -4355,32 +4355,14 @@ public class DefaultBinding implements Binding { private static final long serialVersionUID = 3430629127218407737L; private static final Set EMULATE_AS_BLOB = SQLDialect.supportedBy(HSQLDB); - private final Converter BYTES_CONVERTER; - private final DefaultBytesBinding BYTES; - - @SuppressWarnings({ "serial", "unchecked", "rawtypes" }) DefaultJSONBBinding(Converter converter, boolean isLob) { super(converter, isLob); - - // [#8949] TODO: Support overriding the system default Charset - BYTES_CONVERTER = new AbstractConverter(byte[].class, JSONB.class) { - @Override - public JSONB from(byte[] t) { - return t == null ? null : JSONB.valueOf(new String(t)); - } - - @Override - public byte[] to(JSONB u) { - return u == null ? null : u.toString().getBytes(); - } - }; - BYTES = new DefaultBytesBinding<>((Converter) BYTES_CONVERTER, isLob); } @Override void sqlInline0(BindingSQLContext ctx, JSONB value) throws SQLException { if (EMULATE_AS_BLOB.contains(ctx.dialect())) { - BYTES.sqlInline0(ctx, BYTES_CONVERTER.to(value)); + bytes(ctx.configuration()).sqlInline0(ctx, bytesConverter(ctx.configuration()).to(value)); } else { super.sqlInline0(ctx, value); @@ -4401,7 +4383,7 @@ public class DefaultBinding implements Binding { @Override final void set0(BindingSetStatementContext ctx, JSONB value) throws SQLException { if (EMULATE_AS_BLOB.contains(ctx.dialect())) - BYTES.set0(ctx, BYTES_CONVERTER.to(value)); + bytes(ctx.configuration()).set0(ctx, bytesConverter(ctx.configuration()).to(value)); else ctx.statement().setString(ctx.index(), value.toString()); } @@ -4409,7 +4391,7 @@ public class DefaultBinding implements Binding { @Override final void set0(BindingSetSQLOutputContext ctx, JSONB value) throws SQLException { if (EMULATE_AS_BLOB.contains(ctx.dialect())) - BYTES.set0(ctx, BYTES_CONVERTER.to(value)); + bytes(ctx.configuration()).set0(ctx, bytesConverter(ctx.configuration()).to(value)); else ctx.output().writeString(value.toString()); } @@ -4417,7 +4399,7 @@ public class DefaultBinding implements Binding { @Override final JSONB get0(BindingGetResultSetContext ctx) throws SQLException { if (EMULATE_AS_BLOB.contains(ctx.dialect())) - return BYTES_CONVERTER.from(BYTES.get0(ctx)); + return bytesConverter(ctx.configuration()).from(bytes(ctx.configuration()).get0(ctx)); String string = ctx.resultSet().getString(ctx.index()); return string == null ? null : JSONB.valueOf(string); @@ -4426,7 +4408,7 @@ public class DefaultBinding implements Binding { @Override final JSONB get0(BindingGetStatementContext ctx) throws SQLException { if (EMULATE_AS_BLOB.contains(ctx.dialect())) - return BYTES_CONVERTER.from(BYTES.get0(ctx)); + return bytesConverter(ctx.configuration()).from(bytes(ctx.configuration()).get0(ctx)); String string = ctx.statement().getString(ctx.index()); return string == null ? null : JSONB.valueOf(string); @@ -4435,7 +4417,7 @@ public class DefaultBinding implements Binding { @Override final JSONB get0(BindingGetSQLInputContext ctx) throws SQLException { if (EMULATE_AS_BLOB.contains(ctx.dialect())) - return BYTES_CONVERTER.from(BYTES.get0(ctx)); + return bytesConverter(ctx.configuration()).from(bytes(ctx.configuration()).get0(ctx)); String string = ctx.input().readString(); return string == null ? null : JSONB.valueOf(string); @@ -4444,10 +4426,30 @@ public class DefaultBinding implements Binding { @Override final int sqltype(Statement statement, Configuration configuration) { if (EMULATE_AS_BLOB.contains(configuration.dialect())) - BYTES.sqltype(statement, configuration); + bytes(configuration).sqltype(statement, configuration); return Types.VARCHAR; } + + @SuppressWarnings({ "serial" }) + private final Converter bytesConverter(Configuration configuration) { + return new AbstractConverter(byte[].class, JSONB.class) { + @Override + public JSONB from(byte[] t) { + return t == null ? null : JSONB.valueOf(new String(t, configuration.charsetProvider().provide())); + } + + @Override + public byte[] to(JSONB u) { + return u == null ? null : u.toString().getBytes(configuration.charsetProvider().provide()); + } + }; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private final DefaultBytesBinding bytes(Configuration configuration) { + return new DefaultBytesBinding<>((Converter) bytesConverter(configuration), isLob); + } } static final class DefaultXMLBinding extends AbstractBinding { diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultCharsetProvider.java b/jOOQ/src/main/java/org/jooq/impl/DefaultCharsetProvider.java new file mode 100644 index 0000000000..15e94f4096 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultCharsetProvider.java @@ -0,0 +1,53 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + +import java.nio.charset.Charset; + +import org.jooq.CharsetProvider; + +/** + * @author Lukas Eder + */ +final class DefaultCharsetProvider implements CharsetProvider { + + @Override + public final Charset provide() { + return Charset.defaultCharset(); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java index 911b0e2786..a5cd75462e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultConfiguration.java @@ -53,6 +53,7 @@ import java.util.concurrent.Executor; import javax.sql.DataSource; +import org.jooq.CharsetProvider; import org.jooq.Configuration; import org.jooq.ConnectionProvider; import org.jooq.ConverterProvider; @@ -127,6 +128,7 @@ public class DefaultConfiguration implements Configuration { private transient TransactionListenerProvider[] transactionListenerProviders; private transient DiagnosticsListenerProvider[] diagnosticsListenerProviders; private transient UnwrapperProvider unwrapperProvider; + private transient CharsetProvider charsetProvider; private transient ConverterProvider converterProvider; // [#7062] Apart from the possibility of containing user defined objects, the data @@ -181,6 +183,7 @@ public class DefaultConfiguration implements Configuration { null, null, null, + null, null, @@ -216,6 +219,7 @@ public class DefaultConfiguration implements Configuration { configuration.transactionListenerProviders, configuration.diagnosticsListenerProviders, configuration.unwrapperProvider, + configuration.charsetProvider, configuration.converterProvider, configuration.clock, @@ -251,6 +255,7 @@ public class DefaultConfiguration implements Configuration { TransactionListenerProvider[] transactionListenerProviders, DiagnosticsListenerProvider[] diagnosticsListenerProviders, UnwrapperProvider unwrapperProvider, + CharsetProvider charsetProvider, ConverterProvider converterProvider, Clock clock, @@ -275,6 +280,7 @@ public class DefaultConfiguration implements Configuration { set(transactionListenerProviders); set(diagnosticsListenerProviders); set(unwrapperProvider); + set(charsetProvider); set(converterProvider); set(clock); @@ -334,6 +340,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -363,6 +370,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -392,6 +400,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -426,6 +435,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -455,6 +465,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -489,6 +500,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -523,6 +535,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -557,6 +570,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -591,6 +605,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -625,6 +640,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -659,6 +675,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -693,6 +710,7 @@ public class DefaultConfiguration implements Configuration { newTransactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -727,6 +745,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, newDiagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -761,6 +780,37 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, newUnwrapperProvider, + charsetProvider, + converterProvider, + + clock, + + dialect, + settings, + data + ); + } + + @Override + public final Configuration derive(CharsetProvider newCharsetProvider) { + return new DefaultConfiguration( + connectionProvider, + interpreterConnectionProvider, + systemConnectionProvider, + metaProvider, + versionProvider, + executorProvider, + transactionProvider, + recordMapperProvider, + recordUnmapperProvider, + recordListenerProviders, + executeListenerProviders, + migrationListenerProviders, + visitListenerProviders, + transactionListenerProviders, + diagnosticsListenerProviders, + unwrapperProvider, + newCharsetProvider, converterProvider, clock, @@ -790,6 +840,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, newConverterProvider, clock, @@ -820,6 +871,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, newClock, dialect, @@ -848,6 +900,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -877,6 +930,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders, diagnosticsListenerProviders, unwrapperProvider, + charsetProvider, converterProvider, clock, @@ -1076,6 +1130,12 @@ public class DefaultConfiguration implements Configuration { return this; } + @Override + public final Configuration set(CharsetProvider newCharsetProvider) { + this.charsetProvider = newCharsetProvider; + return this; + } + @Override public final Configuration set(ConverterProvider newConverterProvider) { this.converterProvider = newConverterProvider != null @@ -1450,6 +1510,13 @@ public class DefaultConfiguration implements Configuration { : new DefaultUnwrapperProvider(); } + @Override + public final CharsetProvider charsetProvider() { + return charsetProvider != null + ? charsetProvider + : new DefaultCharsetProvider(); + } + @Override public final ConverterProvider converterProvider() { return converterProvider; @@ -1552,6 +1619,10 @@ public class DefaultConfiguration implements Configuration { ? unwrapperProvider : null); + oos.writeObject(charsetProvider instanceof Serializable + ? charsetProvider + : null); + oos.writeObject(converterProvider instanceof Serializable ? converterProvider : null); @@ -1600,6 +1671,7 @@ public class DefaultConfiguration implements Configuration { transactionListenerProviders = (TransactionListenerProvider[]) ois.readObject(); diagnosticsListenerProviders = (DiagnosticsListenerProvider[]) ois.readObject(); unwrapperProvider = (UnwrapperProvider) ois.readObject(); + charsetProvider = (CharsetProvider) ois.readObject(); converterProvider = (ConverterProvider) ois.readObject(); data = new ConcurrentHashMap<>(); diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java index fbebb5cbd5..e0746a3a3a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultDSLContext.java @@ -1519,7 +1519,7 @@ public class DefaultDSLContext extends AbstractScope implements DSLContext, Seri // TODO: Why does the SAXParser replace \r by \n? XMLHandler handler = new XMLHandler(this); - saxParser.parse(new ByteArrayInputStream(string.getBytes()), handler); + saxParser.parse(new ByteArrayInputStream(string.getBytes(configuration().charsetProvider().provide())), handler); return handler.result; } catch (Exception e) { diff --git a/jOOQ/src/main/java/org/jooq/tools/Convert.java b/jOOQ/src/main/java/org/jooq/tools/Convert.java index 637355e660..cc51a4f0f9 100644 --- a/jOOQ/src/main/java/org/jooq/tools/Convert.java +++ b/jOOQ/src/main/java/org/jooq/tools/Convert.java @@ -530,14 +530,12 @@ public final class Convert { if (toClass.isPrimitive()) { // Characters default to the "zero" character - if (toClass == char.class) { + if (toClass == char.class) return (U) Character.valueOf((char) 0); - } // All others can be converted from (int) 0 - else { + else return convert(0, toClass); - } } @@ -592,21 +590,17 @@ public final class Convert { // [#3062] [#5796] Default collections if no specific collection type was requested if (Collection.class.isAssignableFrom(toClass) && - toClass.isAssignableFrom(ArrayList.class)) { + toClass.isAssignableFrom(ArrayList.class)) return (U) new ArrayList<>(Arrays.asList(fromArray)); - } else if (Collection.class.isAssignableFrom(toClass) && - toClass.isAssignableFrom(LinkedHashSet.class)) { + toClass.isAssignableFrom(LinkedHashSet.class)) return (U) new LinkedHashSet<>(Arrays.asList(fromArray)); - } // [#3443] Conversion from Object[] to JDBC Array - else if (toClass == java.sql.Array.class) { + else if (toClass == java.sql.Array.class) return (U) new MockArray(null, fromArray, fromClass); - } - else { + else return (U) convertArray(fromArray, toClass); - } } // [#3062] Default collections if no specific collection type was requested diff --git a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java index 8c8a0e9669..c913d814a6 100644 --- a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java +++ b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockConfiguration.java @@ -44,6 +44,7 @@ import java.util.concurrent.Executor; import javax.sql.DataSource; +import org.jooq.CharsetProvider; import org.jooq.Configuration; import org.jooq.ConnectionProvider; import org.jooq.ConverterProvider; @@ -205,6 +206,11 @@ public class MockConfiguration implements Configuration { return delegate.unwrapperProvider(); } + @Override + public CharsetProvider charsetProvider() { + return delegate.charsetProvider(); + } + @Override public ConverterProvider converterProvider() { return delegate.converterProvider(); @@ -367,6 +373,11 @@ public class MockConfiguration implements Configuration { return delegate.set(newUnwrapperProvider); } + @Override + public Configuration set(CharsetProvider newCharsetProvider) { + return delegate.set(newCharsetProvider); + } + @Override public Configuration set(ConverterProvider newConverterProvider) { return delegate.set(newConverterProvider); @@ -524,6 +535,11 @@ public class MockConfiguration implements Configuration { return delegate.derive(newUnwrapperProvider); } + @Override + public Configuration derive(CharsetProvider newCharsetProvider) { + return delegate.derive(newCharsetProvider); + } + @Override public Configuration derive(ConverterProvider newConverterProvider) { return delegate.derive(newConverterProvider);