blob: 3d1536c7bee4d4cb0e310ce49d4e45eabb880ec7 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.fineract.infrastructure.core.service.database;
import jakarta.validation.constraints.NotNull;
import java.io.InputStream;
import java.io.Reader;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.springframework.data.domain.Persistable;
public enum JavaType {
BOOLEAN(boolean.class), //
BYTE(byte.class), //
SHORT(short.class), //
INT(int.class), //
LONG(long.class), //
CHAR(char.class), //
FLOAT(float.class), //
DOUBLE(double.class), //
// non-primitive types
BOOLEAN_OBJ(Boolean.class), //
BYTE_OBJ(Byte.class), //
SHORT_OBJ(Short.class), //
INT_OBJ(Integer.class), //
LONG_OBJ(Long.class), //
CHAR_OBJ(Character.class), //
FLOAT_OBJ(Float.class), //
DOUBLE_OBJ(Double.class), //
BIGDECIMAL(BigDecimal.class), //
BIGINTEGER(BigInteger.class), //
NUMBER(Number.class), //
STRING(String.class), //
DATETIME(Timestamp.class), //
TIME(Time.class), //
DATE(Date.class), //
// Java8 time API
LOCAL_DATE(LocalDate.class), //
LOCAL_TIME(LocalTime.class), //
LOCAL_DATETIME(LocalDateTime.class), //
OFFSET_TIME(OffsetTime.class), //
OFFSET_DATETIME(OffsetDateTime.class), //
BINARY(byte[].class), //
ARRAY(null), //
COLLECTION(Collection.class), //
MAP(Map.class), //
PC(null), //
LOCALE(Locale.class), //
PC_UNTYPED(Persistable.class), //
CALENDAR(Calendar.class), //
OID(null), //
INPUT_STREAM(InputStream.class), //
INPUT_READER(Reader.class), //
ENUM(Enum.class), //
OBJECT(Object.class), //
;
private static final Map<Class<?>, JavaType> BY_TYPE = Arrays.stream(values()).filter(e -> e.type != null)
.collect(Collectors.toMap(JavaType::getTypeClass, v -> v));
private final Class<?> type;
JavaType(Class<?> type) {
this.type = type;
}
public Class<?> getTypeClass() {
return type;
}
public boolean isPrimitive() {
return ordinal() <= DOUBLE.ordinal();
}
public boolean isBooleanType() {
return this == BOOLEAN || this == BOOLEAN_OBJ;
}
public boolean isByteType() {
return this == BYTE || this == BYTE_OBJ;
}
public boolean isShortType() {
return this == SHORT || this == SHORT_OBJ;
}
public boolean isIntegerType() {
return this == INT || this == INT_OBJ;
}
public boolean isLongType() {
return this == LONG || this == LONG_OBJ;
}
public boolean isAnyIntegerType() {
return isByteType() || isShortType() || isIntegerType() || isLongType() || this == BIGINTEGER;
}
public boolean isFloatType() {
return this == FLOAT || this == FLOAT_OBJ;
}
public boolean isDoubleType() {
return this == DOUBLE || this == DOUBLE_OBJ;
}
public boolean isAnyFloatType() {
return isFloatType() || isDoubleType();
}
public boolean isDecimalType() {
return this == BIGDECIMAL;
}
public boolean isObjectNumericType() {
return Number.class.isAssignableFrom(getTypeClass());
}
public boolean isPrimitiveNumericType() {
return isPrimitive() && this != BOOLEAN && this != CHAR;
}
public boolean isNumericType() {
return isPrimitiveNumericType() || isObjectNumericType();
}
public boolean isStringType() {
return this == STRING;
}
public boolean isCharacterType() {
return this == CHAR || this == CHAR_OBJ;
}
public boolean isDateType() {
return this == DATE || this == LOCAL_DATE;
}
public boolean isTimeType() {
return this == TIME || this == LOCAL_TIME || this == OFFSET_TIME;
}
public boolean isDateTimeType() {
return this == DATETIME || this == LOCAL_DATETIME || this == OFFSET_DATETIME;
}
public boolean isAnyDateType() {
return isDateType() || isTimeType() || isDateTimeType();
}
public boolean isBinaryType() {
return this == BINARY;
}
public JavaType getPrimitiveType() {
if (isPrimitive()) {
return this;
}
return switch (this) {
case BOOLEAN_OBJ -> BOOLEAN;
case BYTE_OBJ -> BYTE;
case SHORT_OBJ -> SHORT;
case INT_OBJ -> INT;
case LONG_OBJ -> LONG;
case CHAR_OBJ -> CHAR;
case DOUBLE_OBJ -> DOUBLE;
case FLOAT_OBJ -> FLOAT;
default -> null;
};
}
@NotNull
public JavaType getObjectType() {
if (!isPrimitive()) {
return this;
}
return switch (this) {
case BOOLEAN -> BOOLEAN_OBJ;
case BYTE -> BYTE_OBJ;
case SHORT -> SHORT_OBJ;
case INT -> INT_OBJ;
case LONG -> LONG_OBJ;
case CHAR -> CHAR_OBJ;
case DOUBLE -> DOUBLE_OBJ;
case FLOAT -> FLOAT_OBJ;
default ->
throw new PlatformServiceUnavailableException("error.msg.database.type.not.allowed", "Unknown primitive type " + this);
};
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
@NotNull
public static JavaType forType(Class<?> type) {
if (type == null) {
return OBJECT;
}
JavaType javaType = BY_TYPE.get(type);
if (javaType != null) {
return javaType;
}
if (type.isArray()) {
return ARRAY;
}
// have to do this first to catch custom collection and map types;
// on resolve we figure out if these custom types are
// persistence-capable
for (Map.Entry<Class<?>, JavaType> entry : BY_TYPE.entrySet()) {
if (entry.getKey().isAssignableFrom(type)) {
return entry.getValue();
}
}
if (type.isInterface()) {
return Serializable.class.isAssignableFrom(type) ? OBJECT : PC_UNTYPED;
}
if (type.isAssignableFrom(Reader.class)) {
return INPUT_READER;
}
if (type.isAssignableFrom(InputStream.class)) {
return INPUT_STREAM;
}
return OBJECT;
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
public boolean matchType(Class<?> type) {
return matchType(type, true, false);
}
/**
* @return the field metadata type for the given class. First class objects are not recognized in this method.
*/
public boolean matchType(Class<?> type, boolean strict) {
return matchType(type, strict, false);
}
private boolean matchType(Class<?> type, boolean strict, boolean embedded) {
if (type == null) {
return this == OBJECT;
}
if (this.type == null) {
return false;
}
if (type.equals(this.type)) {
return true;
}
if (type.isArray()) {
return this.type.isArray();
}
if (this.type.isAssignableFrom(type)) {
return true;
}
if (type.isInterface()) {
return Serializable.class.isAssignableFrom(type) ? this == OBJECT : this == PC_UNTYPED;
}
if (type.isAssignableFrom(Reader.class)) {
return this == INPUT_READER;
}
if (type.isAssignableFrom(InputStream.class)) {
return this == INPUT_STREAM;
}
if (!strict && !embedded) {
JavaType thisType = this;
if (isPrimitive() && !type.isPrimitive()) {
thisType = getObjectType();
} else if (type.isPrimitive()) {
JavaType primitiveType = getPrimitiveType();
if (primitiveType != null) {
thisType = primitiveType;
}
}
if (this != thisType && thisType.matchType(type, false, true)) {
return true;
}
if (Number.class.isAssignableFrom(type) && isNumericType()) {
JavaType thatType = forType(type);
if (thisType.isAnyIntegerType() && thatType.isAnyIntegerType()) {
return thatType.ordinal() <= thisType.ordinal();
}
if (thisType.isAnyFloatType() && thatType.isAnyFloatType()) {
return thatType.ordinal() <= thisType.ordinal();
}
}
}
return this == OBJECT;
}
/**
* Determine whether the provided Object value is the default for this java type. For example: If o = Integer(0) and
* typeCode = JavaTypes.INT, this method will return true.
*/
public boolean isDefault(Object o) {
return switch (this) {
case BOOLEAN, BOOLEAN_OBJ -> Boolean.FALSE.equals(o);
case BYTE, BYTE_OBJ -> o != null && ((Byte) o) == 0;
case SHORT, SHORT_OBJ -> o != null && ((Short) o) == 0;
case INT, INT_OBJ -> o != null && ((Integer) o) == 0;
case LONG, LONG_OBJ -> o != null && ((Long) o) == 0L;
case CHAR, CHAR_OBJ -> o != null && ((Character) o) == '\u0000';
case FLOAT, FLOAT_OBJ -> o != null && ((Float) o) == 0.0F;
case DOUBLE, DOUBLE_OBJ -> o != null && ((Double) o) == 0.0d;
default -> false;
};
}
public Object parse(String s, String format, Locale locale) {
if (s == null) {
return null;
}
return switch (this) {
case BOOLEAN, BOOLEAN_OBJ -> Boolean.valueOf(s);
case BYTE, BYTE_OBJ -> Byte.parseByte(s);
case SHORT, SHORT_OBJ -> Short.parseShort(s);
case INT, INT_OBJ -> Integer.parseInt(s);
case LONG, LONG_OBJ -> Long.parseLong(s);
case CHAR, CHAR_OBJ -> StringUtils.isEmpty(s) ? null : s.charAt(0);
case FLOAT, FLOAT_OBJ -> Float.parseFloat(s);
case DOUBLE, DOUBLE_OBJ -> Double.parseDouble(s);
case BIGDECIMAL -> new BigDecimal(s, MoneyHelper.getMathContext());
case BIGINTEGER -> new BigInteger(s);
case STRING -> s;
case DATETIME -> Timestamp.valueOf(s);
case TIME -> Time.valueOf(s);
case DATE -> new Date(s);
default -> throw new PlatformServiceUnavailableException("error.msg.database.type.parse",
"Parse string representation is not supported " + this);
};
}
}