[jOOQ/jOOQ#8949] Add CharsetProvider SPI

This commit is contained in:
Lukas Eder 2020-06-25 12:34:34 +02:00
parent 54f601c96e
commit 92e39a692f
9 changed files with 260 additions and 42 deletions

View File

@ -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 <code>byte[]</code> data.
*
* @author Lukas Eder
*/
@FunctionalInterface
public interface CharsetProvider {
/**
* Provide a charset for string to binary conversion.
*/
Charset provide();
}

View File

@ -387,6 +387,12 @@ public interface Configuration extends Serializable {
@NotNull
UnwrapperProvider unwrapperProvider();
/**
* Get the configured <code>CharsetProvider</code> 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.
* <p>
* This method is not thread-safe and should not be used in globally
* available <code>Configuration</code> 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.
* <p>
@ -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.

View File

@ -94,8 +94,4 @@ final class FilenameComparator implements Comparator<String> {
return split1.length - split2.length;
}
public static void main(String[] args) {
INSTANCE.compare("0", new String(new byte[] { 0 }));
}
}

View File

@ -4355,32 +4355,14 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
private static final long serialVersionUID = 3430629127218407737L;
private static final Set<SQLDialect> EMULATE_AS_BLOB = SQLDialect.supportedBy(HSQLDB);
private final Converter<byte[], JSONB> BYTES_CONVERTER;
private final DefaultBytesBinding<U> BYTES;
@SuppressWarnings({ "serial", "unchecked", "rawtypes" })
DefaultJSONBBinding(Converter<JSONB, U> converter, boolean isLob) {
super(converter, isLob);
// [#8949] TODO: Support overriding the system default Charset
BYTES_CONVERTER = new AbstractConverter<byte[], JSONB>(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<U> 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<T, U> implements Binding<T, U> {
@Override
final void set0(BindingSetStatementContext<U> 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<T, U> implements Binding<T, U> {
@Override
final void set0(BindingSetSQLOutputContext<U> 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<T, U> implements Binding<T, U> {
@Override
final JSONB get0(BindingGetResultSetContext<U> 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<T, U> implements Binding<T, U> {
@Override
final JSONB get0(BindingGetStatementContext<U> 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<T, U> implements Binding<T, U> {
@Override
final JSONB get0(BindingGetSQLInputContext<U> 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<T, U> implements Binding<T, U> {
@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<byte[], JSONB> bytesConverter(Configuration configuration) {
return new AbstractConverter<byte[], JSONB>(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<U> bytes(Configuration configuration) {
return new DefaultBytesBinding<>((Converter) bytesConverter(configuration), isLob);
}
}
static final class DefaultXMLBinding<U> extends AbstractBinding<XML, U> {

View File

@ -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();
}
}

View File

@ -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<>();

View File

@ -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) {

View File

@ -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

View File

@ -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);