Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.hibernate.dialect.JsonHelper Maven / Gradle / Ivy
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.dialect;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.sql.SQLException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import org.hibernate.Internal;
import org.hibernate.internal.util.CharSequenceHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.ValuedModelPart;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.JdbcDateJavaType;
import org.hibernate.type.descriptor.java.JdbcTimeJavaType;
import org.hibernate.type.descriptor.java.JdbcTimestampJavaType;
import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import static org.hibernate.dialect.StructHelper.getEmbeddedPart;
import static org.hibernate.dialect.StructHelper.instantiate;
/**
* A Helper for serializing and deserializing JSON, based on an {@link org.hibernate.metamodel.mapping.EmbeddableMappingType}.
*/
@Internal
public class JsonHelper {
public static String toString(EmbeddableMappingType embeddableMappingType, Object value, WrapperOptions options) {
if ( value == null ) {
return null;
}
final StringBuilder sb = new StringBuilder();
toString( embeddableMappingType, value, options, new JsonAppender( sb ) );
return sb.toString();
}
private static void toString(EmbeddableMappingType embeddableMappingType, Object value, WrapperOptions options, JsonAppender appender) {
toString( embeddableMappingType, options, appender, value, '{' );
appender.append( '}' );
}
private static void toString(
EmbeddableMappingType embeddableMappingType,
WrapperOptions options,
JsonAppender appender,
Object domainValue,
char separator) {
final Object[] values = embeddableMappingType.getValues( domainValue );
final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings();
for ( int i = 0; i < values.length; i++ ) {
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i );
if ( attributeMapping instanceof SelectableMapping ) {
final String name = ( (SelectableMapping) attributeMapping ).getSelectableName();
appender.append( separator );
appender.append( '"' );
appender.append( name );
appender.append( "\":" );
toString( attributeMapping.getMappedType(), values[i], options, appender );
}
else if ( attributeMapping instanceof EmbeddedAttributeMapping ) {
if ( values[i] == null ) {
// Skipping the update of the separator is on purpose
continue;
}
final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType();
final SelectableMapping aggregateMapping = mappingType.getAggregateMapping();
if ( aggregateMapping == null ) {
toString(
mappingType,
options,
appender,
values[i],
separator
);
}
else {
final String name = aggregateMapping.getSelectableName();
appender.append( separator );
appender.append( '"' );
appender.append( name );
appender.append( "\":" );
toString( mappingType, values[i], options, appender );
}
}
else {
throw new UnsupportedOperationException( "Support for attribute mapping type not yet implemented: " + attributeMapping.getClass().getName() );
}
separator = ',';
}
}
private static void toString(MappingType mappedType, Object value, WrapperOptions options, JsonAppender appender) {
if ( value == null ) {
appender.append( "null" );
}
else if ( mappedType instanceof EmbeddableMappingType ) {
toString( (EmbeddableMappingType) mappedType, value, options, appender );
}
else if ( mappedType instanceof BasicType> ) {
//noinspection unchecked
final BasicType basicType = (BasicType) mappedType;
convertedBasicValueToString( basicType.convertToRelationalValue( value ), options, appender, basicType );
}
else {
throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + mappedType.getClass().getName() );
}
}
private static void convertedValueToString(
MappingType mappedType,
Object value,
WrapperOptions options,
JsonAppender appender) {
if ( value == null ) {
appender.append( "null" );
}
else if ( mappedType instanceof EmbeddableMappingType ) {
toString( (EmbeddableMappingType) mappedType, value, options, appender );
}
else if ( mappedType instanceof BasicType> ) {
//noinspection unchecked
final BasicType basicType = (BasicType) mappedType;
convertedBasicValueToString( value, options, appender, basicType );
}
else {
throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + mappedType.getClass().getName() );
}
}
private static void convertedBasicValueToString(
Object value,
WrapperOptions options,
JsonAppender appender,
BasicType basicType) {
//noinspection unchecked
final JavaType javaType = (JavaType) basicType.getJdbcJavaType();
switch ( basicType.getJdbcType().getDefaultSqlTypeCode() ) {
case SqlTypes.TINYINT:
case SqlTypes.SMALLINT:
case SqlTypes.INTEGER:
if ( value instanceof Boolean ) {
// BooleanJavaType has this as an implicit conversion
appender.append( (Boolean) value ? '1' : '0' );
break;
}
if ( value instanceof Enum ) {
appender.appendSql( ((Enum>) value ).ordinal() );
break;
}
case SqlTypes.BOOLEAN:
case SqlTypes.BIT:
case SqlTypes.BIGINT:
case SqlTypes.FLOAT:
case SqlTypes.REAL:
case SqlTypes.DOUBLE:
// These types fit into the native representation of JSON, so let's use that
javaType.appendEncodedString( appender, value );
break;
case SqlTypes.CHAR:
case SqlTypes.NCHAR:
case SqlTypes.VARCHAR:
case SqlTypes.NVARCHAR:
if ( value instanceof Boolean ) {
// BooleanJavaType has this as an implicit conversion
appender.append( '"' );
appender.append( (Boolean) value ? 'Y' : 'N' );
appender.append( '"' );
break;
}
case SqlTypes.LONGVARCHAR:
case SqlTypes.LONGNVARCHAR:
case SqlTypes.LONG32VARCHAR:
case SqlTypes.LONG32NVARCHAR:
case SqlTypes.CLOB:
case SqlTypes.MATERIALIZED_CLOB:
case SqlTypes.NCLOB:
case SqlTypes.MATERIALIZED_NCLOB:
case SqlTypes.ENUM:
case SqlTypes.NAMED_ENUM:
// These literals can contain the '"' character, so we need to escape it
appender.append( '"' );
appender.startEscaping();
javaType.appendEncodedString( appender, value );
appender.endEscaping();
appender.append( '"' );
break;
case SqlTypes.DATE:
appender.append( '"' );
JdbcDateJavaType.INSTANCE.appendEncodedString(
appender,
javaType.unwrap( value, java.sql.Date.class, options )
);
appender.append( '"' );
break;
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
appender.append( '"' );
JdbcTimeJavaType.INSTANCE.appendEncodedString(
appender,
javaType.unwrap( value, java.sql.Time.class, options )
);
appender.append( '"' );
break;
case SqlTypes.TIMESTAMP:
appender.append( '"' );
JdbcTimestampJavaType.INSTANCE.appendEncodedString(
appender,
javaType.unwrap( value, java.sql.Timestamp.class, options )
);
appender.append( '"' );
break;
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
appender.append( '"' );
DateTimeFormatter.ISO_OFFSET_DATE_TIME.formatTo(
javaType.unwrap( value, OffsetDateTime.class, options ),
appender
);
appender.append( '"' );
break;
case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC:
case SqlTypes.DURATION:
case SqlTypes.UUID:
// These types need to be serialized as JSON string, but don't have a need for escaping
appender.append( '"' );
javaType.appendEncodedString( appender, value );
appender.append( '"' );
break;
case SqlTypes.BINARY:
case SqlTypes.VARBINARY:
case SqlTypes.LONGVARBINARY:
case SqlTypes.LONG32VARBINARY:
case SqlTypes.BLOB:
case SqlTypes.MATERIALIZED_BLOB:
// These types need to be serialized as JSON string, and for efficiency uses appendString directly
appender.append( '"' );
appender.write(
javaType.unwrap(
value,
byte[].class,
options
)
);
appender.append( '"' );
break;
case SqlTypes.ARRAY:
final int length = Array.getLength( value );
appender.append( '[' );
if ( length != 0 ) {
final BasicType elementType = ( (BasicPluralType, Object>) basicType ).getElementType();
Object arrayElement = Array.get( value, 0 );
convertedValueToString( elementType, arrayElement, options, appender );
for ( int i = 1; i < length; i++ ) {
arrayElement = Array.get( value, i );
appender.append( ',' );
convertedValueToString( elementType, arrayElement, options, appender );
}
}
appender.append( ']' );
break;
default:
throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + basicType.getJdbcType() );
}
}
public static X fromString(
EmbeddableMappingType embeddableMappingType,
String string,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
if ( string == null ) {
return null;
}
final int jdbcValueCount = embeddableMappingType.getJdbcValueCount();
final Object[] values = new Object[jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 )];
final int end = fromString( embeddableMappingType, string, 0, string.length(), values, returnEmbeddable, options );
assert string.substring( end ).isBlank();
if ( returnEmbeddable ) {
final StructAttributeValues attributeValues = StructHelper.getAttributeValues(
embeddableMappingType,
values,
options
);
//noinspection unchecked
return (X) instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() );
}
//noinspection unchecked
return (X) values;
}
private static int fromString(
EmbeddableMappingType embeddableMappingType,
String string,
int begin,
int end,
Object[] values,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
boolean hasEscape = false;
assert string.charAt( begin ) == '{';
int start = begin + 1;
State s = State.KEY_START;
int selectableIndex = -1;
// The following parsing logic assumes JSON is well-formed,
// but for the sake of the Java compiler's flow analysis
// and hopefully also for a better understanding, contains throws for some syntax errors
for ( int i = start; i < string.length(); i++ ) {
final char c = string.charAt( i );
switch ( c ) {
case '\\':
assert s == State.KEY_QUOTE || s == State.VALUE_QUOTE;
hasEscape = true;
i++;
break;
case '"':
switch ( s ) {
case KEY_START:
s = State.KEY_QUOTE;
selectableIndex = -1;
start = i + 1;
hasEscape = false;
break;
case KEY_QUOTE:
s = State.KEY_END;
selectableIndex = getSelectableMapping(
embeddableMappingType,
string,
start,
i,
hasEscape
);
start = -1;
hasEscape = false;
break;
case VALUE_START:
s = State.VALUE_QUOTE;
start = i + 1;
hasEscape = false;
break;
case VALUE_QUOTE:
s = State.VALUE_END;
values[selectableIndex] = fromString(
embeddableMappingType.getJdbcValueSelectable( selectableIndex ).getJdbcMapping(),
string,
start,
i,
hasEscape,
returnEmbeddable,
options
);
selectableIndex = -1;
start = -1;
hasEscape = false;
break;
default:
throw syntaxError( string, s, i );
}
break;
case ':':
switch ( s ) {
case KEY_QUOTE:
// I guess it's ok to have a ':' in the key..
case VALUE_QUOTE:
// In the value it's fine
break;
case KEY_END:
s = State.VALUE_START;
break;
default:
throw syntaxError( string, s, i );
}
break;
case ',':
switch ( s ) {
case KEY_QUOTE:
// I guess it's ok to have a ',' in the key..
case VALUE_QUOTE:
// In the value it's fine
break;
case VALUE_END:
s = State.KEY_START;
break;
default:
throw syntaxError( string, s, i );
}
break;
case '{':
switch ( s ) {
case KEY_QUOTE:
// I guess it's ok to have a '{' in the key..
case VALUE_QUOTE:
// In the value it's fine
break;
case VALUE_START:
final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable(
selectableIndex
);
if ( !( selectable.getJdbcMapping().getJdbcType() instanceof AggregateJdbcType ) ) {
throw new IllegalArgumentException(
String.format(
"JSON starts sub-object for a non-aggregate type at index %d. Selectable [%s] is of type [%s]",
i,
selectable.getSelectableName(),
selectable.getJdbcMapping().getJdbcType().getClass().getName()
)
);
}
final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping().getJdbcType();
final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType();
// This encoding is only possible if the JDBC type is JSON again
assert aggregateJdbcType.getJdbcTypeCode() == SqlTypes.JSON
|| aggregateJdbcType.getDefaultSqlTypeCode() == SqlTypes.JSON;
final Object[] subValues = new Object[subMappingType.getJdbcValueCount()];
i = fromString( subMappingType, string, i, end, subValues, returnEmbeddable, options ) - 1;
assert string.charAt( i ) == '}';
if ( returnEmbeddable ) {
final StructAttributeValues attributeValues = StructHelper.getAttributeValues(
subMappingType,
subValues,
options
);
values[selectableIndex] = instantiate( embeddableMappingType, attributeValues, options.getSessionFactory() );
}
else {
values[selectableIndex] = subValues;
}
s = State.VALUE_END;
selectableIndex = -1;
break;
default:
throw syntaxError( string, s, i );
}
break;
case '[':
switch ( s ) {
case KEY_QUOTE:
// I guess it's ok to have a '[' in the key..
case VALUE_QUOTE:
// In the value it's fine
break;
case VALUE_START:
final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable(
selectableIndex
);
final JdbcMapping jdbcMapping = selectable.getJdbcMapping();
if ( !( jdbcMapping instanceof BasicPluralType, ?> ) ) {
throw new IllegalArgumentException(
String.format(
"JSON starts array for a non-plural type at index %d. Selectable [%s] is of type [%s]",
i,
selectable.getSelectableName(),
jdbcMapping.getJdbcType().getClass().getName()
)
);
}
final BasicPluralType, ?> pluralType = (BasicPluralType, ?>) jdbcMapping;
final BasicType> elementType = pluralType.getElementType();
final CustomArrayList arrayList = new CustomArrayList();
i = fromArrayString( string, returnEmbeddable, options, i, arrayList, elementType ) - 1;
assert string.charAt( i ) == ']';
values[selectableIndex] = pluralType.getJdbcJavaType().wrap( arrayList, options );
s = State.VALUE_END;
selectableIndex = -1;
break;
default:
throw syntaxError( string, s, i );
}
break;
case '}':
switch ( s ) {
case KEY_QUOTE:
// I guess it's ok to have a '}' in the key..
case VALUE_QUOTE:
// In the value it's fine
break;
case VALUE_END:
// At this point, we are done
return i + 1;
default:
throw syntaxError( string, s, i );
}
break;
default:
switch ( s ) {
case KEY_QUOTE:
case VALUE_QUOTE:
// In keys and values, all chars are fine
break;
case VALUE_START:
// Skip whitespace
if ( Character.isWhitespace( c ) ) {
break;
}
// Here we also allow certain literals
final int endIdx = consumeLiteral(
string,
i,
values,
embeddableMappingType.getJdbcValueSelectable( selectableIndex ).getJdbcMapping(),
selectableIndex,
returnEmbeddable,
options
);
if ( endIdx != -1 ) {
i = endIdx;
s = State.VALUE_END;
selectableIndex = -1;
start = -1;
break;
}
throw syntaxError( string, s, i );
case KEY_START:
case KEY_END:
case VALUE_END:
// Only whitespace is allowed here
if ( Character.isWhitespace( c ) ) {
break;
}
default:
throw syntaxError( string, s, i );
}
break;
}
}
throw new IllegalArgumentException( "JSON not properly formed: " + string.subSequence( start, end ) );
}
private static int fromArrayString(
String string,
boolean returnEmbeddable,
WrapperOptions options,
int begin,
CustomArrayList arrayList,
BasicType> elementType) throws SQLException {
boolean hasEscape = false;
assert string.charAt( begin ) == '[';
int start = begin + 1;
State s = State.VALUE_START;
// The following parsing logic assumes JSON is well-formed,
// but for the sake of the Java compiler's flow analysis
// and hopefully also for a better understanding, contains throws for some syntax errors
for ( int i = start; i < string.length(); i++ ) {
final char c = string.charAt( i );
switch ( c ) {
case '\\':
assert s == State.VALUE_QUOTE;
hasEscape = true;
i++;
break;
case '"':
switch ( s ) {
case VALUE_START:
s = State.VALUE_QUOTE;
start = i + 1;
hasEscape = false;
break;
case VALUE_QUOTE:
s = State.VALUE_END;
arrayList.add(
fromString(
elementType,
string,
start,
i,
hasEscape,
returnEmbeddable,
options
)
);
start = -1;
hasEscape = false;
break;
default:
throw syntaxError( string, s, i );
}
break;
case ',':
switch ( s ) {
case VALUE_QUOTE:
// In the value it's fine
break;
case VALUE_END:
s = State.VALUE_START;
break;
default:
throw syntaxError( string, s, i );
}
break;
case '{':
switch ( s ) {
case VALUE_QUOTE:
// In the value it's fine
break;
// case VALUE_START:
// final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable(
// selectableIndex
// );
// if ( !( selectable.getJdbcMapping().getJdbcType() instanceof AggregateJdbcType ) ) {
// throw new IllegalArgumentException(
// String.format(
// "JSON starts sub-object for a non-aggregate type at index %d. Selectable [%s] is of type [%s]",
// i,
// selectable.getSelectableName(),
// selectable.getJdbcMapping().getJdbcType().getClass().getName()
// )
// );
// }
// final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping().getJdbcType();
// final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType();
// // This encoding is only possible if the JDBC type is JSON again
// assert aggregateJdbcType.getJdbcTypeCode() == SqlTypes.JSON
// || aggregateJdbcType.getDefaultSqlTypeCode() == SqlTypes.JSON;
// final Object[] subValues = new Object[subMappingType.getJdbcValueCount()];
// i = fromString( subMappingType, string, i, end, subValues, returnEmbeddable, options ) - 1;
// assert string.charAt( i ) == '}';
// if ( returnEmbeddable ) {
// final Object[] attributeValues = StructHelper.getAttributeValues(
// subMappingType,
// subValues,
// options
// );
// values[selectableIndex] = embeddableMappingType.getRepresentationStrategy()
// .getInstantiator()
// .instantiate(
// () -> attributeValues,
// options.getSessionFactory()
// );
// }
// else {
// values[selectableIndex] = subValues;
// }
// s = State.VALUE_END;
// selectableIndex = -1;
// break;
default:
throw syntaxError( string, s, i );
}
break;
case ']':
switch ( s ) {
case VALUE_QUOTE:
// In the value it's fine
break;
case VALUE_END:
// At this point, we are done
return i + 1;
default:
throw syntaxError( string, s, i );
}
break;
default:
switch ( s ) {
case VALUE_QUOTE:
// In keys and values, all chars are fine
break;
case VALUE_START:
// Skip whitespace
if ( Character.isWhitespace( c ) ) {
break;
}
final int elementIndex = arrayList.size();
arrayList.add( null );
// Here we also allow certain literals
final int endIdx = consumeLiteral(
string,
i,
arrayList.getUnderlyingArray(),
elementType,
elementIndex,
returnEmbeddable,
options
);
if ( endIdx != -1 ) {
i = endIdx;
s = State.VALUE_END;
start = -1;
break;
}
throw syntaxError( string, s, i );
case VALUE_END:
// Only whitespace is allowed here
if ( Character.isWhitespace( c ) ) {
break;
}
default:
throw syntaxError( string, s, i );
}
break;
}
}
throw new IllegalArgumentException( "JSON not properly formed: " + string.subSequence( start, string.length() ) );
}
private static int consumeLiteral(
String string,
int start,
Object[] values,
JdbcMapping jdbcMapping,
int selectableIndex,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
final char c = string.charAt( start );
switch ( c ) {
case 'n':
// only null is possible
values[selectableIndex] = null;
return consume(string, start, "null");
case 'f':
// only false is possible
values[selectableIndex] = false;
return consume(string, start, "false");
case 't':
// only false is possible
values[selectableIndex] = true;
return consume(string, start, "true");
case '0':
switch ( string.charAt( start + 1 ) ) {
case '.':
return consumeFractional(
string,
start,
start + 1,
values,
jdbcMapping,
selectableIndex,
returnEmbeddable,
options
);
case 'E':
case 'e':
return consumeExponential(
string,
start,
start + 1,
values,
jdbcMapping,
selectableIndex,
returnEmbeddable,
options
);
}
values[selectableIndex] = fromString(
jdbcMapping,
string,
start,
start + 1,
returnEmbeddable,
options
);
return start;
case '-':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// number = [ minus ] int [ frac ] [ exp ]
// decimal-point = %x2E ; .
// digit1-9 = %x31-39 ; 1-9
// e = %x65 / %x45 ; e E
// exp = e [ minus / plus ] 1*DIGIT
// frac = decimal-point 1*DIGIT
// int = zero / ( digit1-9 *DIGIT )
// minus = %x2D ; -
// plus = %x2B ; +
// zero = %x30 ; 0
for (int i = start + 1; i < string.length(); i++) {
final char digit = string.charAt( i );
switch ( digit ) {
case '.':
return consumeFractional(
string,
start,
i,
values,
jdbcMapping,
selectableIndex,
returnEmbeddable,
options
);
case 'E':
case 'e':
return consumeExponential(
string,
start,
i,
values,
jdbcMapping,
selectableIndex,
returnEmbeddable,
options
);
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
default:
values[selectableIndex] = fromString(
jdbcMapping,
string,
start,
i,
returnEmbeddable,
options
);
return i - 1;
}
}
}
return -1;
}
private static int consumeFractional(
String string,
int start,
int dotIndex,
Object[] values,
JdbcMapping jdbcMapping,
int selectableIndex,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
for (int i = dotIndex + 1; i < string.length(); i++) {
final char digit = string.charAt( i );
switch ( digit ) {
case 'E':
case 'e':
return consumeExponential(
string,
start,
i,
values,
jdbcMapping,
selectableIndex,
returnEmbeddable,
options
);
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
default:
values[selectableIndex] = fromString(
jdbcMapping,
string,
start,
i,
returnEmbeddable,
options
);
return i - 1;
}
}
return start;
}
private static int consumeExponential(
String string,
int start,
int eIndex,
Object[] values,
JdbcMapping jdbcMapping,
int selectableIndex,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
int i = eIndex + 1;
switch ( string.charAt( i ) ) {
case '-':
case '+':
i++;
break;
}
for (; i < string.length(); i++) {
final char digit = string.charAt( i );
switch ( digit ) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
default:
values[selectableIndex] = fromString(
jdbcMapping,
string,
start,
i,
returnEmbeddable,
options
);
return i - 1;
}
}
return start;
}
private static int consume(String string, int start, String text) {
if ( !string.regionMatches( start + 1, text, 1, text.length() - 1 ) ) {
throw new IllegalArgumentException(
String.format(
"Syntax error at position %d. Unexpected char [%s]. Expecting [%s]",
start + 1,
string.charAt( start + 1 ),
text
)
);
}
return start + text.length() - 1;
}
private static IllegalArgumentException syntaxError(String string, State s, int charIndex) {
return new IllegalArgumentException(
String.format(
"Syntax error at position %d. Unexpected char [%s]. Expecting one of [%s]",
charIndex,
string.charAt( charIndex ),
s.expectedChars()
)
);
}
private static int getSelectableMapping(
EmbeddableMappingType embeddableMappingType,
String string,
int start,
int end,
boolean hasEscape) {
final String name = hasEscape
? unescape( string, start, end )
: string.substring( start, end );
final int selectableIndex = embeddableMappingType.getSelectableIndex( name );
if ( selectableIndex == -1 ) {
throw new IllegalArgumentException(
String.format(
"Could not find selectable [%s] in embeddable type [%s] for JSON processing.",
name,
embeddableMappingType.getMappedJavaType().getJavaTypeClass().getName()
)
);
}
return selectableIndex;
}
private static Object fromString(
JdbcMapping jdbcMapping,
String string,
int start,
int end,
boolean hasEscape,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
if ( hasEscape ) {
final String unescaped = unescape( string, start, end );
return fromString(
jdbcMapping,
unescaped,
0,
unescaped.length(),
returnEmbeddable,
options
);
}
return fromString( jdbcMapping, string, start, end, returnEmbeddable, options );
}
private static Object fromString(
JdbcMapping jdbcMapping,
String string,
int start,
int end,
boolean returnEmbeddable,
WrapperOptions options) throws SQLException {
switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) {
case SqlTypes.BINARY:
case SqlTypes.VARBINARY:
case SqlTypes.LONGVARBINARY:
case SqlTypes.LONG32VARBINARY:
return jdbcMapping.getJdbcJavaType().wrap(
PrimitiveByteArrayJavaType.INSTANCE.fromEncodedString(
string,
start,
end
),
options
);
case SqlTypes.DATE:
return jdbcMapping.getJdbcJavaType().wrap(
JdbcDateJavaType.INSTANCE.fromEncodedString(
string,
start,
end
),
options
);
case SqlTypes.TIME:
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
return jdbcMapping.getJdbcJavaType().wrap(
JdbcTimeJavaType.INSTANCE.fromEncodedString(
string,
start,
end
),
options
);
case SqlTypes.TIMESTAMP:
return jdbcMapping.getJdbcJavaType().wrap(
JdbcTimestampJavaType.INSTANCE.fromEncodedString(
string,
start,
end
),
options
);
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
return jdbcMapping.getJdbcJavaType().wrap(
OffsetDateTimeJavaType.INSTANCE.fromEncodedString(
string,
start,
end
),
options
);
case SqlTypes.TINYINT:
case SqlTypes.SMALLINT:
case SqlTypes.INTEGER:
if ( jdbcMapping.getValueConverter() == null ) {
Class> javaTypeClass = jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass();
if ( javaTypeClass == Boolean.class ) {
// BooleanJavaType has this as an implicit conversion
return Integer.parseInt( string, start, end, 10 ) == 1;
}
if ( javaTypeClass.isEnum() ) {
return javaTypeClass.getEnumConstants()[Integer.parseInt( string, start, end, 10 )];
}
}
case SqlTypes.CHAR:
case SqlTypes.NCHAR:
case SqlTypes.VARCHAR:
case SqlTypes.NVARCHAR:
if ( jdbcMapping.getValueConverter() == null
&& jdbcMapping.getJavaTypeDescriptor().getJavaTypeClass() == Boolean.class ) {
// BooleanJavaType has this as an implicit conversion
return end == start + 1 && string.charAt( start ) == 'Y';
}
default:
if ( jdbcMapping.getJdbcType() instanceof AggregateJdbcType ) {
final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) jdbcMapping.getJdbcType();
final Object[] subValues = aggregateJdbcType.extractJdbcValues(
CharSequenceHelper.subSequence(
string,
start,
end
),
options
);
if ( returnEmbeddable ) {
final StructAttributeValues subAttributeValues = StructHelper.getAttributeValues(
aggregateJdbcType.getEmbeddableMappingType(),
subValues,
options
);
final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType();
return instantiate( embeddableMappingType, subAttributeValues, options.getSessionFactory() ) ;
}
return subValues;
}
return jdbcMapping.getJdbcJavaType().fromEncodedString(
string,
start,
end
);
}
}
private static String unescape(String string, int start, int end) {
final StringBuilder sb = new StringBuilder( end - start );
for ( int i = start; i < end; i++ ) {
final char c = string.charAt( i );
if ( c == '\\' ) {
i++;
final char cNext = string.charAt( i );
switch ( cNext ) {
case '\\':
case '"':
case '/':
sb.append( cNext );
break;
case 'b':
sb.append( '\b' );
break;
case 'f':
sb.append( '\f' );
break;
case 'n':
sb.append( '\n' );
break;
case 'r':
sb.append( '\r' );
break;
case 't':
sb.append( '\t' );
break;
case 'u':
sb.append( (char) Integer.parseInt( string, i + 1, i + 5, 16 ) );
i += 4;
break;
}
continue;
}
sb.append( c );
}
return sb.toString();
}
enum State {
KEY_START( "\"\\s" ),
KEY_QUOTE( "" ),
KEY_END( ":\\s" ),
VALUE_START( "\"\\s" ),
VALUE_QUOTE( "" ),
VALUE_END( ",}\\s" );
final String expectedChars;
State(String expectedChars) {
this.expectedChars = expectedChars;
}
String expectedChars() {
return expectedChars;
}
}
private static class JsonAppender extends OutputStream implements SqlAppender {
private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private final StringBuilder sb;
private boolean escape;
public JsonAppender(StringBuilder sb) {
this.sb = sb;
}
@Override
public void appendSql(String fragment) {
append( fragment );
}
@Override
public void appendSql(char fragment) {
append( fragment );
}
@Override
public void appendSql(int value) {
sb.append( value );
}
@Override
public void appendSql(long value) {
sb.append( value );
}
@Override
public void appendSql(boolean value) {
sb.append( value );
}
@Override
public String toString() {
return sb.toString();
}
public void startEscaping() {
assert !escape;
escape = true;
}
public void endEscaping() {
assert escape;
escape = false;
}
@Override
public JsonAppender append(char fragment) {
if ( escape ) {
appendEscaped( fragment );
}
else {
sb.append( fragment );
}
return this;
}
@Override
public JsonAppender append(CharSequence csq) {
return append( csq, 0, csq.length() );
}
@Override
public JsonAppender append(CharSequence csq, int start, int end) {
if ( escape ) {
int len = end - start;
sb.ensureCapacity( sb.length() + len );
for ( int i = start; i < end; i++ ) {
appendEscaped( csq.charAt( i ) );
}
}
else {
sb.append( csq, start, end );
}
return this;
}
@Override
public void write(int v) {
final String hex = Integer.toHexString( v );
sb.ensureCapacity( sb.length() + hex.length() + 1 );
if ( ( hex.length() & 1 ) == 1 ) {
sb.append( '0' );
}
sb.append( hex );
}
@Override
public void write(byte[] bytes) {
write(bytes, 0, bytes.length);
}
@Override
public void write(byte[] bytes, int off, int len) {
sb.ensureCapacity( sb.length() + ( len << 1 ) );
for ( int i = 0; i < len; i++ ) {
final int v = bytes[off + i] & 0xFF;
sb.append( HEX_ARRAY[v >>> 4] );
sb.append( HEX_ARRAY[v & 0x0F] );
}
}
private void appendEscaped(char fragment) {
switch ( fragment ) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// 8 is '\b'
// 9 is '\t'
// 10 is '\n'
case 11:
// 12 is '\f'
// 13 is '\r'
case 14:
case 15:
case 16:
case 17:
case 18:
case 19:
case 20:
case 21:
case 22:
case 23:
case 24:
case 25:
case 26:
case 27:
case 28:
case 29:
case 30:
case 31:
sb.append( "\\u" ).append( Integer.toHexString( fragment ) );
break;
case '\b':
sb.append("\\b");
break;
case '\t':
sb.append("\\t");
break;
case '\n':
sb.append("\\n");
break;
case '\f':
sb.append("\\f");
break;
case '\r':
sb.append("\\r");
break;
case '"':
sb.append( "\\\"" );
break;
case '\\':
sb.append( "\\\\" );
break;
default:
sb.append( fragment );
break;
}
}
}
private static class CustomArrayList extends AbstractCollection implements Collection {
Object[] array = ArrayHelper.EMPTY_OBJECT_ARRAY;
int size;
public void ensureCapacity(int minCapacity) {
int oldCapacity = array.length;
if ( minCapacity > oldCapacity ) {
int newCapacity = oldCapacity + ( oldCapacity >> 1 );
newCapacity = Math.max( Math.max( newCapacity, minCapacity ), 10 );
array = Arrays.copyOf( array, newCapacity );
}
}
public Object[] getUnderlyingArray() {
return array;
}
@Override
public int size() {
return size;
}
@Override
public boolean add(Object o) {
if ( size == array.length ) {
ensureCapacity( size + 1 );
}
array[size++] = o;
return true;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean contains(Object o) {
for ( int i = 0; i < size; i++ ) {
if ( Objects.equals(o, array[i] ) ) {
return true;
}
}
return false;
}
@Override
public Iterator iterator() {
return new Iterator<>() {
int index;
@Override
public boolean hasNext() {
return index != size;
}
@Override
public Object next() {
if ( index == size ) {
throw new NoSuchElementException();
}
return array[index++];
}
};
}
@Override
public Object[] toArray() {
return Arrays.copyOf( array, size );
}
@Override
public T[] toArray(T[] a) {
//noinspection unchecked
final T[] r = a.length >= size
? a
: (T[]) java.lang.reflect.Array.newInstance( a.getClass().getComponentType(), size );
for (int i = 0; i < size; i++) {
//noinspection unchecked
r[i] = (T) array[i];
}
return null;
}
}
}