com.quinsoft.zeidon.objectdefinition.AttributeDef.java Source code

Java tutorial

Introduction

Here is the source code for com.quinsoft.zeidon.objectdefinition.AttributeDef.java

Source

/**
Zeidon JOE is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Zeidon JOE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.
    
You should have received a copy of the GNU Lesser General Public License
along with Zeidon JOE.  If not, see <http://www.gnu.org/licenses/>.
    
Copyright 2009-2015 QuinSoft
 */
package com.quinsoft.zeidon.objectdefinition;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;

import com.quinsoft.zeidon.Application;
import com.quinsoft.zeidon.AttributeInstance;
import com.quinsoft.zeidon.ObjectEngine;
import com.quinsoft.zeidon.Task;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.domains.Domain;
import com.quinsoft.zeidon.standardoe.ScalaHelper;
import com.quinsoft.zeidon.utils.PortableFileReader;
import com.quinsoft.zeidon.utils.PortableFileReader.PortableFileAttributeHandler;

/**
 * @author DG
 *
 */
public class AttributeDef implements PortableFileAttributeHandler, Serializable {
    private static final long serialVersionUID = 1L;
    private final EntityDef entityDef;
    private String name;
    private String erAttributeToken;
    private InternalType type = InternalType.STRING;
    private Integer length;

    /**
     * Used to match up DATARECORDS with CHILDENTITY lines in the XOD.
     */
    private int xvaAttrToken;
    private String initialValue;

    // Derived operation fields.
    public static final int DERIVED_SET = 1;
    public static final int DERIVED_GET = 2;

    // Following fields are protected instead of private to allow for quicker access when
    // reference by inner classes.
    protected String derivedOperationName;
    protected String derivedOperationClassName;
    private SourceFileType derivedOperationsourceFileType = SourceFileType.VML;

    private AttributeHashKeyType hashKeyType = AttributeHashKeyType.NONE;

    /**
     * The unique number for the entity which is the index of the attribute for all
     * attributes.
     */
    private final int attributeNumber;
    private boolean hidden;
    private boolean persistent;
    private boolean activate;
    private boolean key;
    private boolean foreignKey;
    private boolean autoSeq;
    private boolean genKey;
    private boolean update;
    private boolean required;
    private boolean debugChange;
    private Domain domain;
    private EntityDef hashKeyParent;
    private Boolean isSequencingAscending = Boolean.TRUE;

    private boolean isCaseSensitive = false;

    /**
     * If true then this attribute was created at runtime via EntityDef.createDynamicAttributeDef.
     */
    private boolean isDynamicAttribute = false;
    private DerivedOper derivedOperation;

    public AttributeDef(EntityDef entityDef) {
        super();
        this.entityDef = entityDef;
        attributeNumber = entityDef.getAttributeCount();
    }

    /**
     * Create a dynamic attribute.
     *
     * @param entityDef
     * @param config
     */
    public AttributeDef(EntityDef entityDef, DynamicAttributeDefConfiguration config) {
        this(entityDef);
        setName(config.getAttributeName());
        setDomain(config.getDomainName());
        setDynamicAttribute(true);

        erAttributeToken = (entityDef.toString() + "." + getName()).intern();
    }

    @Override
    public void setAttribute(PortableFileReader reader) {
        String attributeName = reader.getAttributeName();

        switch (attributeName.charAt(0)) {
        case 'A':
            if (reader.getAttributeName().equals("AUTO_SEQ")) {
                autoSeq = true;
                entityDef.setAutoSeq(this);
            }
            break;

        case 'C':
            if (reader.getAttributeName().equals("CASESENS")) {
                isCaseSensitive = StringUtils.startsWithIgnoreCase(reader.getAttributeValue(), "Y");
            }
            break;

        case 'D':
            // DERIVEDC is the name of the Java class that declares the derived function.
            // Java only (e.g. not in the C OE).
            if (reader.getAttributeName().equals("DERIVEDC")) {
                derivedOperationClassName = reader.getAttributeValue().intern();
                if (!derivedOperationClassName.contains("."))
                    derivedOperationClassName = getApplication().getPackage() + "." + derivedOperationClassName;
            } else if (reader.getAttributeName().equals("DERIVEDF")) {
                derivedOperationName = reader.getAttributeValue().intern();
            } else if (reader.getAttributeName().equals("DRSRCTYPE")) {
                derivedOperationsourceFileType = SourceFileType.parse(reader.getAttributeValue());
            } else if (reader.getAttributeName().equals("DOMAIN")) {
                setDomain(reader.getAttributeValue().intern());
            } else if (reader.getAttributeName().equals("DEBUGCHG")) {
                debugChange = StringUtils.startsWithIgnoreCase(reader.getAttributeValue(), "Y");
            }
            break;

        case 'E':
            if (reader.getAttributeName().equals("ERATT_TOK")) {
                erAttributeToken = reader.getAttributeValue().intern();
            }
            break;

        case 'F':
            if (reader.getAttributeName().equals("FORKEY")) {
                foreignKey = reader.getAttributeValue().toUpperCase().startsWith("Y");
            }
            break;

        case 'G':
            if (reader.getAttributeName().equals("GENKEY")) {
                genKey = true;
                entityDef.setGenKey(this);
                entityDef.getLodDef().setHasGenKey(true);
            }
            break;

        case 'H':
            if (reader.getAttributeName().equals("HASHKEY")) {
                if (hashKeyParent == null)
                    hashKeyParent = getEntityDef().getParent();

                hashKeyType = AttributeHashKeyType.valueOf(reader.getAttributeValue());
                if (hashKeyType != AttributeHashKeyType.NONE)
                    entityDef.addHashKeyAttribute(this);
            } else if (reader.getAttributeName().equals("HASHKEY_PARENT")) {
                String entityName = reader.getAttributeValue();
                for (hashKeyParent = getEntityDef()
                        .getParent(); hashKeyParent != null; hashKeyParent = hashKeyParent.getParent()) {
                    if (hashKeyParent.getName().equals(entityName))
                        break;
                }

                if (hashKeyParent == null)
                    throw new ZeidonException("Unknown hashkey parent %s", entityName);
            } else if (reader.getAttributeName().equals("HIDDEN")) {
                hidden = reader.getAttributeValue().toUpperCase().startsWith("Y");
            }
            break;

        case 'I':
            if (reader.getAttributeName().equals("INIT")) {
                initialValue = reader.getAttributeValue();
                entityDef.setHasInitializedAttributes(true);
            }

        case 'K':
            if (reader.getAttributeName().equals("KEY")) {
                key = true;
                entityDef.addKey(this);
            }
            break;

        case 'L':
            if (reader.getAttributeName().equals("LTH")) {
                length = Integer.parseInt(reader.getAttributeValue());
            }
            break;

        case 'N':
            if (reader.getAttributeName().equals("NAME")) {
                setName(reader.getAttributeValue().intern());
            }
            break;

        case 'P':
            if (reader.getAttributeName().equals("PERSIST")) {
                persistent = reader.getAttributeValue().startsWith("Y");
            }
            break;

        case 'R':
            if (reader.getAttributeName().equals("REQUIRED")) {
                required = reader.getAttributeValue().startsWith("Y");
            }
            break;

        case 'S':
            if (reader.getAttributeName().equals("SEQUENCING")) {
                int position = Integer.parseInt(reader.getAttributeValue());

                // Find the first parent that can have multiple children.  If a parent has
                // max cardinality of 1 then it can't be ordered.
                EntityDef search = entityDef;
                while (search.getMaxCardinality() == 1)
                    search = search.getParent();

                search.addSequencingAttribute(this, position);
            } else if (reader.getAttributeName().equals("SEQ_AD")) {
                isSequencingAscending = reader.getAttributeValue().toUpperCase().startsWith("A");
            }
            break;

        case 'T':
            if (reader.getAttributeName().equals("TYPE")) {
                type = InternalType.mapCode(reader.getAttributeValue());
            }
            break;

        case 'U':
            if (reader.getAttributeName().equals("UPDATE")) {
                update = reader.getAttributeValue().toUpperCase().startsWith("Y");
            }
            break;

        case 'X':
            if (reader.getAttributeName().equals("XVAATT_TOK")) {
                xvaAttrToken = Integer.parseInt(reader.getAttributeValue());
            }
            break;
        }
    }

    /**
     * Perform any final initializing that can only be done after the attribute
     * has been loaded.
     */
    void finishAttributeLoading() {
        // Persistent, non-hidden attributes should always be activated.
        activate = persistent && (!hidden || isKey() || isForeignKey() || isAutoSeq());
    }

    private Application getApplication() {
        return getEntityDef().getLodDef().getApplication();
    }

    AttributeDef setDomain(String domainName) {
        Application app = entityDef.getLodDef().getApplication();
        domain = app.getDomain(domainName);
        return this;
    }

    public EntityDef getEntityDef() {
        return entityDef;
    }

    AttributeDef setName(String name) {
        this.name = name;
        return this;
    }

    public String getName() {
        return name;
    }

    public String getErAttributeToken() {
        return erAttributeToken;
    }

    public InternalType getType() {
        return type;
    }

    /**
     * @return Returns max length of this attribute (if applicable). May return null
     *          in which case the max length defaults to the domain definition.
     */
    public Integer getLength() {
        return length;
    }

    AttributeDef setLength(int length) {
        this.length = length;
        return this;
    }

    @Override
    public String toString() {
        return entityDef.toString() + "." + name;
    }

    int getXvaAttrToken() {
        return xvaAttrToken;
    }

    public boolean isHidden() {
        return hidden;
    }

    public boolean isPersistent() {
        return persistent;
    }

    /**
     * Return true if this attribute should be loaded from the DB.  This should
     * be true for all persistent, non-hidden attributes.  It may also be true
     * for hidden attributes if this entity is expected to be linked to other
     * instances.  This allows the DB handler to load attributes that might
     * be hidden in this entity but non-hidden in the linked instance.
     *
     * @return
     */
    public boolean isActivate() {
        return activate;
    }

    void setActivate(boolean act) {
        assert persistent : "Internal error: attributes with activate flag must be persistent.";
        activate = act;
    }

    public boolean isRequired() {
        return required;
    }

    public int getAttributeNumber() {
        return attributeNumber;
    }

    public String getDerivedOperationClassName() {
        return derivedOperationClassName;
    }

    public String getDerivedOperationName() {
        return derivedOperationName;
    }

    private synchronized DerivedOper getDerivedOperation(Task task) {
        if (derivedOperation != null)
            return derivedOperation;

        try {
            switch (derivedOperationsourceFileType) {
            case VML:
                derivedOperation = new VmlDerivedOper(task);
                break;

            case JAVA:
                derivedOperation = new JavaDerivedOper(task);
                break;

            case SCALA:
                derivedOperation = new ScalaDerivedOper(task);
                break;

            default:
                throw new ZeidonException("Unsupported source file type for derived operations: %s",
                        derivedOperationsourceFileType);
            }

            return derivedOperation;
        } catch (Exception e) {
            throw ZeidonException.prependMessage(e, "Error loading Derived operation '%s.%s' for %s",
                    derivedOperationClassName, derivedOperationName, toString());
        }
    }

    public boolean isKey() {
        return key;
    }

    public boolean isForeignKey() {
        return foreignKey;
    }

    public boolean isAutoSeq() {
        return autoSeq;
    }

    public boolean isGenKey() {
        return genKey;
    }

    public boolean isUpdate() {
        return update;
    }

    public boolean isDerived() {
        return derivedOperationClassName != null;
    }

    public boolean isDebugChange() {
        return debugChange;
    }

    public Domain getDomain() {
        return domain;
    }

    /**
     * This calls the derived attribute function to set the internal attribute value.
     * The attribute value when then be retrieved by a get-attr method.
     *
     * @param attributeInstance
     */
    public void executeDerivedAttributeForGet(AttributeInstance attributeInstance) {
        getDerivedOperation(attributeInstance.getTask()).getDerivedValue(attributeInstance);
    }

    static private final Class<?>[] ARGUMENT_TYPES_VML = new Class<?>[] { View.class, String.class, String.class,
            Integer.class };
    static private final Class<?>[] ARGUMENT_TYPES_JAVA = new Class<?>[] { AttributeInstance.class };
    static private final Class<?>[] CONSTRUCTOR_ARG_VML = new Class<?>[] { View.class };
    static private final Class<?>[] CONSTRUCTOR_ARG_JAVA = new Class<?>[] {};

    /**
     * Call a derived operation.
     *
     * @author dg
     *
     */
    private abstract class DerivedOper {
        protected final Method method;
        protected final Constructor<? extends Object> constructor;

        private DerivedOper(Task task, Class<?>[] constructorArgs, Class<?>[] argumentTypes) throws Exception {
            if (constructorArgs != null) {
                ObjectEngine oe = task.getObjectEngine();
                ClassLoader classLoader = oe.getClassLoader(derivedOperationClassName);
                Class<? extends Object> operationsClass;
                operationsClass = classLoader.loadClass(derivedOperationClassName);

                constructor = operationsClass.getConstructor(constructorArgs);
                method = operationsClass.getMethod(derivedOperationName, argumentTypes);
            } else {
                constructor = null;
                method = null;
            }
        }

        private void getDerivedValue(AttributeInstance attributeInstance) {
            callDerivedOperation(attributeInstance);
        }

        abstract void callDerivedOperation(AttributeInstance attributeInstance);

        @Override
        public String toString() {
            return derivedOperationClassName + "." + derivedOperationName;
        }
    }

    /**
     * Call a derived operation as it's defined in Java generated from VML.
     */
    private class VmlDerivedOper extends DerivedOper {
        private VmlDerivedOper(Task task) throws Exception {
            super(task, CONSTRUCTOR_ARG_VML, ARGUMENT_TYPES_VML);
        }

        @Override
        void callDerivedOperation(AttributeInstance attributeInstance) {
            try {
                View view = attributeInstance.getView();
                Object oper = constructor.newInstance(view);
                Object[] argList = new Object[] { view, getEntityDef().getName(), getName(), DERIVED_GET };
                method.invoke(oper, argList);
            } catch (InvocationTargetException e) {
                Throwable target = e.getTargetException();
                if (target instanceof ZeidonException)
                    throw (ZeidonException) target;

                target = e.getCause();
                if (target instanceof ZeidonException)
                    throw (ZeidonException) target;

                throw ZeidonException.prependMessage(target, "Oper: %s", this)
                        .prependAttributeDef(AttributeDef.this);
            } catch (Throwable e) {
                throw ZeidonException.prependMessage(e, "Oper: %s", this).prependAttributeDef(AttributeDef.this);
            }
        }
    }

    /**
     * Call a derived operation as it's defined in hand-coded Java.
     */
    private class JavaDerivedOper extends DerivedOper {
        private JavaDerivedOper(Task task) throws Exception {
            super(task, CONSTRUCTOR_ARG_JAVA, ARGUMENT_TYPES_JAVA);
        }

        @Override
        void callDerivedOperation(AttributeInstance attributeInstance) {
            try {
                Object oper = constructor.newInstance();
                Object[] argList = new Object[] { attributeInstance };
                method.invoke(oper, argList);
            } catch (Throwable e) {
                throw ZeidonException.prependMessage(e, "Oper: %s", this).prependAttributeDef(AttributeDef.this);
            }
        }
    }

    /**
     * Call a derived operation using the ScalaHelper.
     *
     */
    private class ScalaDerivedOper extends DerivedOper {
        private ScalaDerivedOper(Task task) throws Exception {
            super(task, null, null);
        }

        @Override
        void callDerivedOperation(AttributeInstance attributeInstance) {
            try {
                ScalaHelper scalaHelper = attributeInstance.getTask().getScalaHelper();
                scalaHelper.calculateDerivedAttribute(attributeInstance);
            } catch (Throwable e) {
                throw ZeidonException.prependMessage(e, "Oper: %s", this).prependAttributeDef(AttributeDef.this);
            }
        }
    }

    public AttributeDef getNextAttributeDef() {
        int idx = getAttributeNumber() + 1;
        if (idx == entityDef.getAttributeCount())
            return null;

        return entityDef.getAttribute(idx);
    }

    /**
     * @return the initialValue
     */
    public String getInitialValue() {
        return initialValue;
    }

    /**
     * @return the hashKeyType
     */
    public AttributeHashKeyType getHashKeyType() {
        return hashKeyType;
    }

    /**
     * For attributes that are a hashkey under a parent, this returns the parent.
     * Otherwise returns null.
     *
     * @return
     */
    public EntityDef getHashKeyParent() {
        return hashKeyParent;
    }

    /**
     * Returns true if this AttributeDef is a sequencing attribute in ascending order, false
     * if it's descending, and null if it's not a sequencing attribute.  See
     * EntityDef.getSequencingAttributes() for more.
     *
     * @return
     */
    public Boolean isSequencingAscending() {
        return isSequencingAscending;
    }

    public boolean isDynamicAttribute() {
        return isDynamicAttribute;
    }

    void setDynamicAttribute(boolean isDynamicAttribute) {
        this.isDynamicAttribute = isDynamicAttribute;
    }

    public boolean isCaseSensitive() {
        return isCaseSensitive;
    }
}