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

com.landawn.abacus.metadata.sql.SQLProperty Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 HaiYang Li
 *
 * 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.
 */

package com.landawn.abacus.metadata.sql;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.landawn.abacus.core.NameUtil;
import com.landawn.abacus.idGenerator.AutoIncrementIdGenerator;
import com.landawn.abacus.idGenerator.IdGenerator;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.metadata.Association;
import com.landawn.abacus.metadata.ColumnType;
import com.landawn.abacus.metadata.EntityDefXmlEle.EntityDefEle;
import com.landawn.abacus.metadata.EntityDefXmlEle.EntityDefEle.EntityEle.PropertyEle;
import com.landawn.abacus.metadata.EntityDefinition;
import com.landawn.abacus.metadata.EntityDefinitionFactory;
import com.landawn.abacus.metadata.OnDeleteAction;
import com.landawn.abacus.metadata.OnUpdateAction;
import com.landawn.abacus.metadata.Property;
import com.landawn.abacus.type.ObjectType;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.type.TypeFactory;
import com.landawn.abacus.util.ClassUtil;
import com.landawn.abacus.util.ImmutableList;
import com.landawn.abacus.util.ImmutableMap;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.StringUtil;
import com.landawn.abacus.util.WD;
import com.landawn.abacus.validator.Validator;
import com.landawn.abacus.validator.ValidatorFactory;

// TODO: Auto-generated Javadoc
/**
 *
 * @author Haiyang Li
 * @since 0.8
 */
public class SQLProperty implements Property {

    private static final Logger logger = LoggerFactory.getLogger(SQLProperty.class);

    private final String name;

    private final String canonicalName;

    private final Map attrs;

    private Type type;

    private boolean isSysTimeDefaultOnInsert = false;

    private boolean isSysTimeDefaultOnUpdate = false;

    private Object defaultOnInsert;

    private Object defaultOnUpdate;

    private final String columnName;

    private final String canonicalColumnName;

    private final ColumnType columnType;

    private EntityDefinition columnEntityDef;

    private final OnUpdateAction onUpdateAction;

    private final OnDeleteAction onDeleteAction;

    private final String orderBy;

    private List> validatorList;

    private Association association;

    private final boolean isId;

    private final boolean isUID;

    private final boolean isReadable;

    private final boolean isUpdatable;

    private final boolean isInsertable;

    private final boolean isList;

    private final boolean isSet;

    private Map subPropMap;

    private List subPropNameList;

    private List subPropList;

    private final Map, Method> setMethodMap = new ConcurrentHashMap<>();

    private final Map, Method> getMethodMap = new ConcurrentHashMap<>();

    private IdGenerator idGenerator;

    private boolean isAutoIncrement = false;

    volatile EntityDefinition entityDef;

    private volatile boolean isInit = false;

    @SuppressWarnings("null")
    protected SQLProperty(String entityName, String tableName, String name, Map attrs, boolean isId, boolean isUID) {
        this.name = NameUtil.getCachedName(name);
        attrs.put(PropertyEle.NAME, this.name);

        this.canonicalName = NameUtil.getCachedName(entityName + "." + name);

        columnType = N.notNullOrEmpty(attrs.get(PropertyEle.JOIN_ON)) ? ColumnType.ENTITY : ColumnType.TABLE_COLUMN;

        if (columnType.isEntity()) {
            columnName = NameUtil.getCachedName(attrs.get(PropertyEle.TYPE));
            canonicalColumnName = columnName;
        } else {
            if (N.isNullOrEmpty(attrs.get(PropertyEle.COLUMN))) {
                columnName = this.name;
            } else {
                columnName = NameUtil.getCachedName(attrs.get(PropertyEle.COLUMN));
            }

            canonicalColumnName = NameUtil.getCachedName(tableName + "." + columnName);
        }

        attrs.put(PropertyEle.COLUMN, columnName);
        this.attrs = ImmutableMap.of(new LinkedHashMap<>(attrs));

        String actionOnUpdateAttr = getAttribute(PropertyEle.ACTION_ON_UPDATE);
        String actionOnDeleteAttr = getAttribute(PropertyEle.ACTION_ON_DELETE);

        if (((actionOnUpdateAttr != null) || (actionOnDeleteAttr != null)) && (columnType != ColumnType.ENTITY)) {
            throw new RuntimeException("Can't set 'constraint' attribute for non-entity property(" + getName() + "). ");
        }

        if (actionOnUpdateAttr != null) {
            throw new RuntimeException("Constraint on update is not supported currently.");
        }

        onUpdateAction = columnType.isEntity() ? ((actionOnUpdateAttr == null) ? OnUpdateAction.NO_ACTION : OnUpdateAction.get(actionOnUpdateAttr)) : null;

        onDeleteAction = columnType.isEntity() ? ((actionOnDeleteAttr == null) ? OnDeleteAction.NO_ACTION : OnDeleteAction.get(actionOnDeleteAttr)) : null;

        this.isId = isId;
        this.isUID = isUID;

        String attr = this.attrs.get(PropertyEle.READABLE);
        isReadable = (N.isNullOrEmpty(attr)) ? true : Boolean.valueOf(attr);

        boolean isReadOnly = Boolean.valueOf(this.attrs.get(PropertyEle.READ_ONLY));

        attr = this.attrs.get(PropertyEle.INSERTABLE);
        isInsertable = (attr == null) ? (isReadOnly ? false : true) : Boolean.valueOf(attr);

        if (isReadOnly && isInsertable) {
            throw new RuntimeException("Can't set both 'insertable=true' and 'readOnly=true' attributes");
        }

        attr = this.attrs.get(PropertyEle.UPDATABLE);
        isUpdatable = (attr == null) ? (isReadOnly ? false : true) : Boolean.valueOf(attr);

        if (isReadOnly && isUpdatable) {
            throw new RuntimeException("Can't set both 'isUpdatable=true' and 'readOnly=true' attributes");
        }

        String collection = this.attrs.get(PropertyEle.COLLECTION);

        if (PropertyEle.LIST.equals(collection)) {
            isList = true;
            isSet = false;
        } else if (PropertyEle.SET.equals(collection)) {
            isSet = true;
            isList = false;
        } else {
            isList = false;
            isSet = false;
        }

        orderBy = this.attrs.get(PropertyEle.ORDER_BY);

        if (orderBy != null) {
            if (!isCollection()) {
                throw new RuntimeException("Only list or set property supports sort attribute.");
            }
        }
    }

    /**
     * Gets the name.
     *
     * @return
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Gets the canonical name.
     *
     * @return
     */
    @Override
    public String getCanonicalName() {
        return canonicalName;
    }

    /**
     * Gets the type.
     *
     * @return
     */
    @Override
    public Type getType() {
        init();

        return type;
    }

    /**
     * Gets the default on insert.
     *
     * @param 
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  T getDefaultOnInsert() {
        init();

        if (isSysTimeDefaultOnInsert) {
            return (T) type.valueOf(PropertyEle.SYS_TIME);
        } else {
            return (T) defaultOnInsert;
        }
    }

    /**
     * Gets the default on update.
     *
     * @param 
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  T getDefaultOnUpdate() {
        init();

        if (isSysTimeDefaultOnUpdate) {
            return (T) type.valueOf(PropertyEle.SYS_TIME);
        } else {
            return (T) defaultOnUpdate;
        }
    }

    /**
     * Gets the column name.
     *
     * @return
     */
    @Override
    public String getColumnName() {
        return columnName;
    }

    /**
     * Gets the canonical column name.
     *
     * @return
     */
    @Override
    public String getCanonicalColumnName() {
        return canonicalColumnName;
    }

    /**
     * Gets the column type.
     *
     * @return
     */
    @Override
    public ColumnType getColumnType() {
        return columnType;
    }

    /**
     * Gets the column entity def.
     *
     * @return
     */
    @Override
    public EntityDefinition getColumnEntityDef() {
        init();

        return columnEntityDef;
    }

    /**
     * Gets the on update action.
     *
     * @return
     */
    @Override
    public OnUpdateAction getOnUpdateAction() {
        return onUpdateAction;
    }

    /**
     * Gets the on delete action.
     *
     * @return
     */
    @Override
    public OnDeleteAction getOnDeleteAction() {
        return onDeleteAction;
    }

    /**
     * Gets the order by.
     *
     * @return
     */
    @Override
    public String getOrderBy() {
        return orderBy;
    }

    /**
     * Gets the association.
     *
     * @return
     */
    @Override
    public Association getAssociation() {
        init();

        return association;
    }

    /**
     * Gets the validator list.
     *
     * @return
     */
    @Override
    public List> getValidatorList() {
        init();

        return validatorList;
    }

    /**
     * Gets the sub property list.
     *
     * @return
     */
    @Override
    public List getSubPropertyList() {
        init();

        return subPropList;
    }

    /**
     * Gets the sub property name list.
     *
     * @return
     */
    @Override
    public List getSubPropertyNameList() {
        init();

        return subPropNameList;
    }

    /**
     * Gets the sub property.
     *
     * @param propName
     * @return
     */
    @Override
    public Property getSubProperty(String propName) {
        init();

        return subPropMap.get(propName);
    }

    /**
     * Checks if is id.
     *
     * @return true, if is id
     */
    @Override
    public boolean isId() {
        return isId;
    }

    /**
     * Checks if is uid.
     *
     * @return true, if is uid
     */
    @Override
    public boolean isUID() {
        return isUID;
    }

    /**
     * Checks if is auto increment.
     *
     * @return true, if is auto increment
     */
    @Override
    public boolean isAutoIncrement() {
        return isAutoIncrement;
    }

    /**
     * Checks if is readable.
     *
     * @return true, if is readable
     */
    @Override
    public boolean isReadable() {
        return isReadable;
    }

    /**
     * Checks if is updatable.
     *
     * @return true, if is updatable
     */
    @Override
    public boolean isUpdatable() {
        return isUpdatable;
    }

    /**
     * Checks if is insertable.
     *
     * @return true, if is insertable
     */
    @Override
    public boolean isInsertable() {
        return isInsertable;
    }

    /**
     * Checks if is read only.
     *
     * @return true, if is read only
     */
    @Override
    public boolean isReadOnly() {
        return !(isInsertable || isUpdatable);
    }

    /**
     * Checks if is list.
     *
     * @return true, if is list
     */
    @Override
    public boolean isList() {
        return isList;
    }

    /**
     * Checks if is sets the.
     *
     * @return true, if is sets the
     */
    @Override
    public boolean isSet() {
        return isSet;
    }

    /**
     * Checks if is collection.
     *
     * @return true, if is collection
     */
    @Override
    public boolean isCollection() {
        return isList || isSet;
    }

    /**
     *
     * @param 
     * @param entities
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    @SafeVarargs
    public final  Collection asCollection(T... entities) {
        if (isSet()) {
            return N.asLinkedHashSet(entities);
        } else {
            return N.asList(entities);
        }
    }

    /**
     *
     * @param 
     * @param entities
     * @return
     */
    @Override
    public  Collection asCollection(Collection entities) {
        if (isSet()) {
            return N.newLinkedHashSet(entities);
        } else {
            return new ArrayList<>(entities);
        }
    }

    /**
     * Gets the gets the method.
     *
     * @param clazz
     * @return
     */
    @Override
    public Method getGetMethod(Class clazz) {
        Method getMethod = getMethodMap.get(clazz);

        if (getMethod == null) {
            getMethod = ClassUtil.getPropGetMethod(clazz, name);

            getMethod.setAccessible(true);
            getMethodMap.put(clazz, getMethod);
        }

        return getMethod;
    }

    /**
     * Gets the sets the method.
     *
     * @param clazz
     * @return
     */
    @Override
    public Method getSetMethod(Class clazz) {
        Method setMethod = setMethodMap.get(clazz);

        if (setMethod == null) {
            setMethod = ClassUtil.getPropSetMethod(clazz, name);

            // Class parameterType = getType().getTypeClass();
            //
            // if (MapEntity.class.isAssignableFrom(parameterType)) {
            // parameterType = getGetMethod(clazz).getReturnType();
            // }
            setMethod.setAccessible(true);
            setMethodMap.put(clazz, setMethod);
        }

        return setMethod;
    }

    /**
     *
     * @param 
     * @param st
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  T valueOf(String st) {
        return (T) getType().valueOf(st);
    }

    /**
     *
     * @param propVlaue
     * @return
     */
    @Override
    public String stringOf(Object propVlaue) {
        return getType().stringOf(propVlaue);
    }

    /**
     * Gets the attributes.
     *
     * @return
     */
    @Override
    public Map getAttributes() {
        return attrs;
    }

    /**
     * Gets the attribute.
     *
     * @param attrName
     * @return
     */
    @Override
    public String getAttribute(String attrName) {
        return attrs.get(attrName);
    }

    /**
     * Gets the id generator.
     *
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Override
    public IdGenerator getIdGenerator() {
        return idGenerator;
    }

    /**
     * Sets the id generator.
     *
     * @param idGenerator the new id generator
     */
    void setIdGenerator(IdGenerator idGenerator) {
        this.idGenerator = idGenerator;
        this.isAutoIncrement = idGenerator instanceof AutoIncrementIdGenerator;
    }

    /**
     * Gets the entity definition.
     *
     * @return
     */
    @Override
    public EntityDefinition getEntityDefinition() {
        return entityDef;
    }

    /**
     * Sets the entity definition.
     *
     * @param entityDef the new entity definition
     */
    void setEntityDefinition(EntityDefinition entityDef) {
        this.entityDef = entityDef;
    }

    @Override
    public int hashCode() {
        return canonicalName.hashCode();
    }

    /**
     *
     * @param obj
     * @return true, if successful
     */
    @Override
    public boolean equals(Object obj) {
        return this == obj || (obj instanceof SQLProperty && N.equals(((SQLProperty) obj).canonicalName, canonicalName));
    }

    @Override
    public String toString() {
        return attrs.toString();
    }

    /**
     * Inits the.
     */
    protected void init() {
        if (!isInit) {
            synchronized (this) {
                if (!isInit) {
                    // initialize type
                    String typeName = attrs.get(PropertyEle.TYPE);

                    if (columnType.isEntity()) {
                        EntityDefinitionFactory entityDefFactory = entityDef.getFactory();

                        if (entityDefFactory != null) {
                            columnEntityDef = entityDefFactory.getDefinition(typeName);
                        }

                        typeName = columnEntityDef.getJavaType();
                    }

                    if (isList) {
                        typeName = List.class.getName() + WD._LESS_THAN + typeName + WD._GREATER_THAN;
                    } else if (isSet) {
                        typeName = Set.class.getName() + WD._LESS_THAN + typeName + WD._GREATER_THAN;
                    }

                    final String pkgName = entityDef.getAttribute(EntityDefEle.PACKAGE);
                    type = getRealType(typeName, pkgName);

                    // initialize defaultOnInsert
                    String defaultOnInsertAttr = attrs.get(PropertyEle.DEFAULT_ON_INSERT);

                    if (defaultOnInsertAttr != null) {
                        if (PropertyEle.SYS_TIME.equals(defaultOnInsertAttr) && Date.class.isAssignableFrom(type.clazz())) {
                            isSysTimeDefaultOnInsert = true;
                            defaultOnInsert = null;
                        } else {
                            defaultOnInsert = type.valueOf(defaultOnInsertAttr);
                        }
                    } else {
                        defaultOnInsert = type.defaultValue();
                    }

                    // initialize defaultOnUpdate
                    String defaultOnUpdateAttr = attrs.get(PropertyEle.DEFAULT_ON_UPDATE);

                    if (defaultOnUpdateAttr != null) {
                        if (PropertyEle.SYS_TIME.equals(defaultOnUpdateAttr) && Date.class.isAssignableFrom(type.clazz())) {
                            isSysTimeDefaultOnUpdate = true;
                            defaultOnUpdate = null;
                        } else {
                            defaultOnUpdate = type.valueOf(defaultOnUpdateAttr);
                        }
                    } else {
                        defaultOnUpdate = type.defaultValue();
                    }

                    // initialize validatorList
                    List> validatorList = new ArrayList<>();
                    String validatorAttr = attrs.get(PropertyEle.VALIDATOR);

                    if (validatorAttr != null) {
                        List validatorElementList = parseValidatorAttr(validatorAttr);

                        for (String validatorElement : validatorElementList) {
                            validatorList.add(ValidatorFactory.create(name, type, validatorElement));
                        }
                    }

                    this.validatorList = ImmutableList.of(validatorList);

                    // init association.
                    String joinOnAttr = attrs.get(PropertyEle.JOIN_ON);

                    if (joinOnAttr != null) {
                        association = new Association(this, joinOnAttr);
                    }

                    Map subPropMap = new HashMap<>();
                    List subPropNameList = new ArrayList<>();
                    List subPropList = new ArrayList<>();

                    if (columnType.isEntity()) {
                        for (Property subProp : columnEntityDef.getDefaultLoadPropertyList()) {
                            if (subProp.getColumnType() != ColumnType.ENTITY) {
                                final String fullName = NameUtil.getCachedName(columnEntityDef.getName() + WD.PERIOD + subProp.getName());

                                subPropMap.put(subProp.getName(), subProp);
                                subPropMap.put(fullName, subProp);
                                subPropNameList.add(fullName);
                                subPropList.add(subProp);
                            }
                        }
                    }

                    this.subPropMap = ImmutableMap.of(subPropMap);
                    this.subPropNameList = ImmutableList.of(subPropNameList);
                    this.subPropList = ImmutableList.of(subPropList);

                    isInit = true;
                }
            }
        }
    }

    /**
     * Gets the real type.
     *
     * @param typeName
     * @param pkgName
     * @return
     */
    private Type getRealType(String typeName, String pkgName) {
        Type type = TypeFactory.getType(typeName);

        if (N.isNullOrEmpty(pkgName)) {
            return type;
        }

        if (Object.class.equals(type.clazz()) && !typeName.equals(ObjectType.OBJECT) && typeName.indexOf('.') < 0) {
            String className = typeName;

            try {
                try {
                    ClassUtil.forClass(className);
                } catch (Exception e) {
                    // ignore
                    className = pkgName + "." + typeName;
                }

                Class cls = ClassUtil.forClass(className);

                if (cls != null) {
                    return TypeFactory.getType(cls);
                }
            } catch (Exception e) {
                if (logger.isWarnEnabled()) {
                    logger.warn("No class found by name: " + className + ". Please run it again after the generated codes are compiled");
                }
            }
        } else if (N.notNullOrEmpty(type.getParameterTypes())) {
            final Type[] parameterTypes = type.getParameterTypes();

            for (int i = 0; i < parameterTypes.length; i++) {
                final Type pt = parameterTypes[i];

                if (Object.class.equals(pt.clazz()) || N.notNullOrEmpty(pt.getParameterTypes())) {
                    final int beginIndex = typeName.indexOf('<');
                    final int endIndex = typeName.lastIndexOf('>');

                    String newTypeName = typeName.substring(0, beginIndex + 1);
                    boolean isFirstParameterType = true;
                    int bracketNum = 0;

                    for (int idx = beginIndex + 1, previousIndex = idx; idx < endIndex; idx++) {
                        final char ch = typeName.charAt(idx);

                        if (ch == '<') {
                            bracketNum++;

                            continue;
                        }

                        if (bracketNum > 0) {
                            if (ch == '>') {
                                bracketNum--;
                            }
                        }

                        if (bracketNum == 0 && (ch == ',' || idx == endIndex - 1)) {
                            String paraTypeName = StringUtil
                                    .trim(ch == ',' ? typeName.substring(previousIndex, idx) : typeName.substring(previousIndex, idx + 1));

                            if (isFirstParameterType) {
                                newTypeName += getRealType(paraTypeName, pkgName).name();

                                isFirstParameterType = false;
                            } else {
                                newTypeName += ", " + getRealType(paraTypeName, pkgName).name();
                            }

                            previousIndex = idx + 1;
                        }
                    }

                    newTypeName += ">";

                    return TypeFactory.getType(newTypeName);
                }
            }
        }

        return type;
    }

    /**
     * Parses the validator attr.
     *
     * @param validatorAttr
     * @return
     */
    private List parseValidatorAttr(String validatorAttr) {
        List validatorElementList = new ArrayList<>();
        boolean inQuotes = false;

        for (int beginIndex = 0, index = 0; index < validatorAttr.length(); index++) {
            char ch = validatorAttr.charAt(index);

            if (ch == WD._PARENTHESES_L) {
                inQuotes = true;
            } else if (ch == WD._PARENTHESES_R) {
                inQuotes = false;

                if (index == (validatorAttr.length() - 1)) {
                    validatorElementList.add(validatorAttr.substring(beginIndex, index + 1).trim());
                }
            } else if ((ch == WD._SEMICOLON) && !inQuotes) {
                validatorElementList.add(validatorAttr.substring(beginIndex, index).trim());
                beginIndex = index + 1;
            } else if (index == (validatorAttr.length() - 1)) {
                validatorElementList.add(validatorAttr.substring(beginIndex, index + 1).trim());
            }
        }

        return validatorElementList;
    }
}