io.github.pustike.persist.metadata.FieldData Maven / Gradle / Ivy
/*
* Copyright (C) 2016-2019 the original author or authors.
*
* 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
*
* https://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 io.github.pustike.persist.metadata;
import java.lang.reflect.Field;
import java.lang.reflect.InaccessibleObjectException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Types;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import io.github.pustike.persist.Column;
import io.github.pustike.persist.utils.PersistUtils;
/**
* The field metadata within an entity.
*/
public final class FieldData {
private static final List> idClassList = Arrays.asList(Short.class, Integer.class, Long.class);// UUID?
private static final List> versionClassList = Arrays.asList(Short.class, Integer.class, Long.class);
private static final List> lobClassList = Arrays.asList(byte[].class, Byte[].class, String.class);
private static final List> defaultClassList = Arrays.asList(Boolean.class, Byte.class, Character.class,
Short.class, Integer.class, Long.class, Float.class, Double.class, BigInteger.class, BigDecimal.class,
LocalDate.class, LocalTime.class, LocalDateTime.class, String.class);
private final Field field;
private final Class> fieldType;
private String columnName;
private boolean optional = true;
private int length = 255;
private int scale;
private boolean fetch = true;
private ColumnType columnType;
private int jdbcType;
FieldData(Field field) {
this.field = field;
Class> type = field.getType();
this.fieldType = type.isPrimitive() ? PersistUtils.getWrapperClass(type) : type;
}
/**
* Get the {@link Field} for which this metadata is created.
* @return the field
*/
public Field getField() {
return field;
}
/**
* Get the name of the field.
* @see Field#getName()
* @return the field name
*/
public String getName() {
return field.getName();
}
/**
* Get the name of the column as specified or derived from the field name.
* @return the column name
*/
public String getColumnName() {
return columnName;
}
void setColumnName(String columnName) {
this.columnName = columnName;
}
/**
* Get the scale of a numeric field. Currently unused!
* @return the scale to be used for this numeric field
*/
public int getScale() {
return scale;
}
private void setScale(int scale) {
this.scale = scale;
}
/**
* Check if this field is to be fetched by default.
* @return {@code true} if this field is to be fetched.
*/
public boolean isFetch() {
return fetch;
}
private void setFetch(boolean fetch) {
this.fetch = fetch;
}
void setColumn(Column column) {
this.setColumnName(column.name());
this.setOptional(column.optional());
this.setLength(column.length());
this.setScale(column.scale());
this.setFetch(column.fetch());
}
/**
* Get the column definition to use in the mapping tool.
* @return the database table column definition
*/
public String getColumnDefinition() {
StringBuilder columnDefinition = new StringBuilder(columnName).append(" ").append(getSqlType());
if (columnType == ColumnType.Id) {
columnDefinition.append(" NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY");
} else {
if (!isOptional()) {
columnDefinition.append(" NOT NULL");
}
}
return columnDefinition.toString();
}
private String getSqlType() {
if (columnType == ColumnType.ForeignKey) {
return "bigint";
} else if (columnType == ColumnType.Enum) {
return "varchar(" + getLength() + ")";
}
if (Byte.class == fieldType) {
return "tinyint";
} else if (Short.class == fieldType) {
return "smallint";
} else if (Integer.class == fieldType) {
return "integer";
} else if (Long.class == fieldType) {
return "bigint";
} else if (String.class == fieldType) {
if (columnType == ColumnType.Lob) {
return "text";
}
return "varchar(" + getLength() + ")";
} else if (byte[].class == fieldType || Byte[].class == fieldType) {
return "bytea";
} else if (Boolean.class == fieldType) {
return "boolean";
} else if (Character.class == fieldType) {
return "char(" + getLength() + ")";
} else if (Float.class == fieldType || Double.class == fieldType
|| BigInteger.class == fieldType || BigDecimal.class == fieldType) {
return "numeric";
} else if (LocalDate.class == fieldType) {
return "date";
} else if (LocalTime.class == fieldType) {
return "time";
} else if (LocalDateTime.class == fieldType) {
return "timestamp";
}
throw new RuntimeException("field type is not supported: " + fieldType);
}
/**
* Check if this field is optional/nullable when inserting the data. Used in the column definition.
* @return {@code true} if this field is optional.
*/
public boolean isOptional() {
return optional;
}
void setOptional(boolean optional) {
this.optional = optional;
}
/**
* Get the length to be applied on this varchar field. Used in the column definition.
* @return the field length in the table column
*/
public int getLength() {
return length;
}
private void setLength(int length) {
if (length <= 0) {
throw new IllegalArgumentException("length must be at least 1");
}
this.length = length;
}
/**
* Get the JDBC type to be used in queries for this field.
* @see Types
* @return the jdbc type
*/
public int getJdbcType() {
return jdbcType;
}
private void setJdbcType(int jdbcType) {
this.jdbcType = jdbcType;
}
void validate() {
Class> type = getFieldType();
if (getColumnType() == ColumnType.Id) {
if (!idClassList.contains(type)) {//
throw new IllegalStateException("invalid type used for the @Id column: " + field);
}
} else if (getColumnType() == ColumnType.Version) {
if (!versionClassList.contains(type)) {//
throw new IllegalStateException("invalid type used for the @Version column: " + field);
}
} else if (getColumnType() == ColumnType.Lob) {
if (!lobClassList.contains(type)) {//
throw new IllegalStateException("invalid type used for the @Lob column: " + field);
}
} else if (getColumnType() == null) {
if (byte[].class.isAssignableFrom(type) || Byte[].class.isAssignableFrom(type)) {
setColumnType(ColumnType.Lob);
} else if (type.isEnum()) {
setColumnType(ColumnType.Enum);
setLength(255);
} else if (!defaultClassList.contains(type)) {//
throw new IllegalStateException("invalid type used for the @Column: " + field);
} else {
setColumnType(ColumnType.Default);
}
}
determineJdbcType();
if (!field.trySetAccessible()) {
throw new InaccessibleObjectException("couldn't enable access to field: " + field);
}
}
/**
* Get the class of this field.
* @see Field#getType()
* @return the field type
*/
public Class> getFieldType() {
return fieldType;
}
/**
* Get the column type.
* @see ColumnType
* @return the column type
*/
public ColumnType getColumnType() {
return columnType;
}
void setColumnType(ColumnType columnType) {
if (this.columnType != null) {
throw new IllegalStateException("Only one of the @Id / @Version / @Lob can be applied on a field: " + field);
}
this.columnType = columnType;
}
private void determineJdbcType() {
if (columnType == ColumnType.ForeignKey) {
setJdbcType(Types.BIGINT);
} else if (columnType == ColumnType.Enum) {
setJdbcType(Types.VARCHAR);
} else if (Byte.class == fieldType) {
setJdbcType(Types.TINYINT);
} else if (Short.class == fieldType) {
setJdbcType(Types.SMALLINT);
} else if (Integer.class == fieldType) {
setJdbcType(Types.INTEGER);
} else if (Long.class == fieldType) {
setJdbcType(Types.BIGINT);
} else if (String.class == fieldType) {
if (columnType == ColumnType.Lob) {
setJdbcType(Types.LONGVARCHAR);
} else {
setJdbcType(Types.VARCHAR);
}
} else if (byte[].class == fieldType || Byte[].class == fieldType) {
setJdbcType(Types.LONGVARBINARY);
} else if (Boolean.class == fieldType) {
setJdbcType(Types.BOOLEAN);
} else if (Character.class == fieldType) {
setJdbcType(Types.CHAR);
} else if (Float.class == fieldType || Double.class == fieldType
|| BigInteger.class == fieldType || BigDecimal.class == fieldType) {
setJdbcType(Types.NUMERIC);
} else if (LocalDate.class == fieldType) {
setJdbcType(Types.DATE);
} else if (LocalTime.class == fieldType) {
setJdbcType(Types.TIME);
} else if (LocalDateTime.class == fieldType) {
setJdbcType(Types.TIMESTAMP);
}
}
/**
* Get the class, for this field, to be used to map in the jdbc result data.
* @return the result type class
*/
public Class> getResultType() {
Class> fieldType = getFieldType();
if (fieldType.isEnum() || !defaultClassList.contains(fieldType)) {
return null;
}
return fieldType == Character.class ? String.class : fieldType;
}
/**
* Get the value in this field from the given entity.
* @param entity the instance of an entity
* @return the value in this field
*/
public Object getValue(Object entity) {
Objects.requireNonNull(entity);
try {
return field.get(entity);
} catch (IllegalAccessException e) {
throw new RuntimeException("Couldn't get field value for: " + field, e);
}
}
/**
* Set the value to this field in the given entity instance.
* @param entity the instance of an entity
* @param value the value to be set to the field
*/
public void setValue(Object entity, Object value) {
if (value == null) {
return;
}
Objects.requireNonNull(entity);
try {
Class> fieldType = getFieldType(), valueType = value.getClass();
if (!fieldType.equals(valueType) && !valueType.isAssignableFrom(fieldType)) {
value = convertToFieldType(value, fieldType);
}
field.set(entity, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("Couldn't get field value for: " + field, e);
}
}
private Object convertToFieldType(Object value, Class> fieldType) {
if (value instanceof String) {
String stringValue = (String) value;
if (fieldType.isEnum()) {
value = convertStringToEnum(stringValue, fieldType);
} else if (fieldType == Character.class) {
value = stringValue.length() > 0 ? stringValue.charAt(0) : null;
}
}
return value;
}
@SuppressWarnings("unchecked")
private static T convertStringToEnum(String text, Class> fieldType) {
text = text.trim();
if (text.length() == 0) {
return null;
}
Class> enumType = fieldType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
Objects.requireNonNull(enumType, "The target type " + fieldType + " does not refer to an enum");
return (T) Enum.valueOf((Class extends Enum>) enumType, text);
}
/**
* A string representation of this field metadata
* @return a string with fieldType, columnName and jdbcType
*/
@Override
public String toString() {
return "FieldData{fieldType=" + fieldType + ", columnName='"
+ columnName + '\'' + ", jdbcType=" + jdbcType + '}';
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy