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

io.permazen.schema.ReferenceSchemaField Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.schema;

import io.permazen.core.DeleteAction;
import io.permazen.core.FieldType;
import io.permazen.core.InvalidSchemaException;
import io.permazen.util.Diffs;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.TreeSet;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

/**
 * A reference field in a {@link SchemaObjectType}.
 */
public class ReferenceSchemaField extends SimpleSchemaField {

    private DeleteAction onDelete;
    private boolean cascadeDelete;
    private boolean allowDeleted;
    private boolean allowDeletedSnapshot;
    private NavigableSet objectTypes;

    public ReferenceSchemaField() {
        this.setType(FieldType.REFERENCE_TYPE_NAME);
        this.setIndexed(true);
        this.setAllowDeletedSnapshot(true);
    }

    /**
     * Get the desired behavior when an object referred to by this field is deleted.
     *
     * @return desired behavior when a referenced object is deleted
     */
    public DeleteAction getOnDelete() {
        return this.onDelete;
    }
    public void setOnDelete(DeleteAction onDelete) {
        this.verifyNotLockedDown();
        this.onDelete = onDelete;
    }

    /**
     * Determine whether the referred-to object should be deleted when an object containing this field is deleted.
     *
     * @return whether deletion should cascade to the referred-to object
     */
    public boolean isCascadeDelete() {
        return this.cascadeDelete;
    }
    public void setCascadeDelete(boolean cascadeDelete) {
        this.verifyNotLockedDown();
        this.cascadeDelete = cascadeDelete;
    }

    /**
     * Determine whether this field accepts references to deleted objects in normal (non-snapshot) transactions.
     *
     * @return whether deleted objects are allowed in normal transactions
     */
    public boolean isAllowDeleted() {
        return this.allowDeleted;
    }
    public void setAllowDeleted(boolean allowDeleted) {
        this.verifyNotLockedDown();
        this.allowDeleted = allowDeleted;
    }

    /**
     * Determine whether this field accepts references to deleted objects in snapshot transactions.
     *
     * @return whether deleted objects are allowed in snapshot transactions
     */
    public boolean isAllowDeletedSnapshot() {
        return this.allowDeletedSnapshot;
    }
    public void setAllowDeletedSnapshot(boolean allowDeletedSnapshot) {
        this.verifyNotLockedDown();
        this.allowDeletedSnapshot = allowDeletedSnapshot;
    }

    /**
     * Get the object types this field is allowed to reference, if so restricted.
     *
     * @return storage IDs of allowed object types, or null if there is no restriction
     */
    public NavigableSet getObjectTypes() {
        return this.objectTypes;
    }
    public void setObjectTypes(NavigableSet objectTypes) {
        this.verifyNotLockedDown();
        this.objectTypes = objectTypes;
    }

// Lockdown

    @Override
    void lockDownRecurse() {
        super.lockDownRecurse();
        if (this.objectTypes != null)
            this.objectTypes = Collections.unmodifiableNavigableSet(this.objectTypes);
    }

// Validation

    @Override
    void validate() {
        super.validate();
        if (!FieldType.REFERENCE_TYPE_NAME.equals(this.getType())) {
            throw new InvalidSchemaException("invalid " + this + ": reference fields must have type `"
              + FieldType.REFERENCE_TYPE_NAME + "'");
        }
        if (!this.isIndexed())
            throw new IllegalArgumentException("invalid " + this + ": reference fields must always be indexed");
        if (this.getEncodingSignature() != 0)
            throw new IllegalArgumentException("invalid " + this + ": encoding signature must be zero");
        if (this.onDelete == null)
            throw new InvalidSchemaException("invalid " + this + ": no delete action specified");
        if (this.onDelete == DeleteAction.NOTHING && (!this.allowDeleted || !this.allowDeletedSnapshot)) {
            throw new InvalidSchemaException("invalid " + this + ": delete action " + this.onDelete
              + " is incompatible with disallowing assignment to deleted objects");
        }
    }

// SchemaFieldSwitch

    @Override
    public  R visit(SchemaFieldSwitch target) {
        return target.caseReferenceSchemaField(this);
    }

// Compatibility

    // Reference fields are differentiated only by what types they can refer to
    @Override
    boolean isCompatibleType(SimpleSchemaField field) {
        final ReferenceSchemaField that = (ReferenceSchemaField)field;
        return Objects.equals(this.objectTypes, that.objectTypes);
    }

    @Override
    void writeFieldTypeCompatibilityHashData(DataOutputStream output) throws IOException {
        output.writeBoolean(this.objectTypes != null);
        if (this.objectTypes != null) {
            output.writeInt(this.objectTypes.size());
            for (Integer storageId : this.objectTypes)
                output.writeInt(storageId);
        }
    }

// DiffGenerating

    @Override
    public Diffs differencesFrom(SimpleSchemaField other) {
        final Diffs diffs = new Diffs(super.differencesFrom(other));
        if (!(other instanceof ReferenceSchemaField)) {
            diffs.add("change type from " + other.getClass().getSimpleName() + " to " + this.getClass().getSimpleName());
            return diffs;
        }
        final ReferenceSchemaField that = (ReferenceSchemaField)other;
        if (!(this.onDelete != null ? this.onDelete.equals(that.onDelete) : that.onDelete == null))
            diffs.add("changed on-delete action from " + that.onDelete + " to " + this.onDelete);
        if (this.cascadeDelete != that.cascadeDelete)
            diffs.add("changed cascade delete from " + that.cascadeDelete + " to " + this.cascadeDelete);
        if (this.allowDeleted != that.allowDeleted)
            diffs.add("changed allowing assignement of deleted objects from " + that.allowDeleted + " to " + this.allowDeleted);
        if (this.allowDeletedSnapshot != that.allowDeletedSnapshot) {
            diffs.add("changed allowing assignement of deleted objects in snapshot transactions from "
              + that.allowDeletedSnapshot + " to " + this.allowDeletedSnapshot);
        }
        if (!(this.objectTypes != null ? this.objectTypes.equals(that.objectTypes) : that.objectTypes == null))
            diffs.add("changed allowed object type storage IDs from " + that.objectTypes + " to " + this.objectTypes);
        return diffs;
    }

// XML Reading

    @Override
    void readAttributes(XMLStreamReader reader, int formatVersion) throws XMLStreamException {
        super.readAttributes(reader, formatVersion);
        this.setOnDelete(this.readAttr(reader, DeleteAction.class, XMLConstants.ON_DELETE_ATTRIBUTE, DeleteAction.EXCEPTION));
        final Boolean cascadeDeleteAttr = this.getBooleanAttr(reader, XMLConstants.CASCADE_DELETE_ATTRIBUTE, false);
        if (cascadeDeleteAttr != null)
            this.setCascadeDelete(cascadeDeleteAttr);
        this.setAllowDeleted(this.getOnDelete() == DeleteAction.NOTHING);       // defaults to false unless DeleteAction.NOTHING
        final Boolean allowDeletedAttr = this.getBooleanAttr(reader, XMLConstants.ALLOW_DELETED_ATTRIBUTE, false);
        if (allowDeletedAttr != null)
            this.setAllowDeleted(allowDeletedAttr);
        this.setAllowDeletedSnapshot(true);                                     // defaults to true
        final Boolean allowDeletedSnapshotAttr = this.getBooleanAttr(reader, XMLConstants.ALLOW_DELETED_SNAPSHOT_ATTRIBUTE, false);
        if (allowDeletedSnapshotAttr != null)
            this.setAllowDeletedSnapshot(allowDeletedSnapshotAttr);
    }

    @Override
    void readSubElements(XMLStreamReader reader, int formatVersion) throws XMLStreamException {

        // Any restrictions?
        if (!this.expect(reader, true, XMLConstants.OBJECT_TYPES_TAG)) {
            this.objectTypes = null;
            return;
        }

        // Read list of zero or more permitted storage ID
        this.objectTypes = new TreeSet<>();
        while (this.expect(reader, true, XMLConstants.OBJECT_TYPE_TAG)) {
            this.objectTypes.add(this.getIntAttr(reader, XMLConstants.STORAGE_ID_ATTRIBUTE));
            this.expectClose(reader);           // 
        }

        // Read closing 
        this.expectClose(reader);
    }

// XML Writing

    @Override
    void writeXML(XMLStreamWriter writer, boolean includeName) throws XMLStreamException {
        if (this.objectTypes != null) {
            writer.writeStartElement(XMLConstants.REFERENCE_FIELD_TAG.getNamespaceURI(),
              XMLConstants.REFERENCE_FIELD_TAG.getLocalPart());
        } else {
            writer.writeEmptyElement(XMLConstants.REFERENCE_FIELD_TAG.getNamespaceURI(),
              XMLConstants.REFERENCE_FIELD_TAG.getLocalPart());
        }
        this.writeAttributes(writer, includeName);
        if (this.onDelete != null) {
            writer.writeAttribute(XMLConstants.ON_DELETE_ATTRIBUTE.getNamespaceURI(),
              XMLConstants.ON_DELETE_ATTRIBUTE.getLocalPart(), this.onDelete.name());
        }
        if (this.allowDeleted != (this.onDelete == DeleteAction.NOTHING)) {
            writer.writeAttribute(XMLConstants.ALLOW_DELETED_ATTRIBUTE.getNamespaceURI(),
              XMLConstants.ALLOW_DELETED_ATTRIBUTE.getLocalPart(), "" + this.allowDeleted);
        }
        if (!this.allowDeletedSnapshot) {
            writer.writeAttribute(XMLConstants.ALLOW_DELETED_SNAPSHOT_ATTRIBUTE.getNamespaceURI(),
              XMLConstants.ALLOW_DELETED_SNAPSHOT_ATTRIBUTE.getLocalPart(), "" + this.allowDeletedSnapshot);
        }
        if (this.objectTypes != null) {
            writer.writeStartElement(XMLConstants.OBJECT_TYPES_TAG.getNamespaceURI(), XMLConstants.OBJECT_TYPES_TAG.getLocalPart());
            for (int storageId : this.objectTypes) {
                writer.writeEmptyElement(XMLConstants.OBJECT_TYPE_TAG.getNamespaceURI(),
                  XMLConstants.OBJECT_TYPE_TAG.getLocalPart());
                writer.writeAttribute(XMLConstants.STORAGE_ID_ATTRIBUTE.getNamespaceURI(),
                  XMLConstants.STORAGE_ID_ATTRIBUTE.getLocalPart(), "" + storageId);
            }
            writer.writeEndElement();           // 
            writer.writeEndElement();           // 
        }
    }

    @Override
    void writeSimpleAttributes(XMLStreamWriter writer) throws XMLStreamException {
        // don't need to write type or indexed
        if (this.cascadeDelete) {
            writer.writeAttribute(XMLConstants.CASCADE_DELETE_ATTRIBUTE.getNamespaceURI(),
              XMLConstants.CASCADE_DELETE_ATTRIBUTE.getLocalPart(), "" + this.cascadeDelete);
        }
    }

// Object

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!super.equals(obj))
            return false;
        final ReferenceSchemaField that = (ReferenceSchemaField)obj;
        return this.onDelete == that.onDelete
          && this.cascadeDelete == that.cascadeDelete
          && this.allowDeleted == that.allowDeleted
          && this.allowDeletedSnapshot == that.allowDeletedSnapshot
          && Objects.equals(this.objectTypes, that.objectTypes);
    }

    @Override
    public int hashCode() {
        return super.hashCode()
          ^ (this.cascadeDelete ? 1 : 0)
          ^ (this.allowDeleted ? 2 : 0)
          ^ (this.allowDeletedSnapshot ? 4 : 0)
          ^ Objects.hashCode(this.onDelete)
          ^ Objects.hashCode(this.objectTypes);
    }

// Cloneable

    @Override
    public ReferenceSchemaField clone() {
        final ReferenceSchemaField clone = (ReferenceSchemaField)super.clone();
        if (clone.objectTypes != null)
            clone.objectTypes = new TreeSet<>(clone.objectTypes);
        return clone;
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy