All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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