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

org.jsimpledb.schema.ReferenceSchemaField Maven / Gradle / Ivy

Go to download

JSimpleDB core API classes which provide objects, fields, indexes, queries, and schema management on top of a key/value store.

There is a newer version: 3.6.1
Show newest version

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

package org.jsimpledb.schema;

import java.util.SortedSet;
import java.util.TreeSet;

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

import org.jsimpledb.core.DeleteAction;
import org.jsimpledb.core.FieldType;
import org.jsimpledb.core.InvalidSchemaException;
import org.jsimpledb.util.Diffs;

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

    private DeleteAction onDelete;
    private boolean cascadeDelete;
    private SortedSet objectTypes;

    public ReferenceSchemaField() {
        this.setType(FieldType.REFERENCE_TYPE_NAME);
        this.setIndexed(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.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.cascadeDelete = cascadeDelete;
    }

    /**
     * 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 SortedSet getObjectTypes() {
        return this.objectTypes;
    }
    public void setObjectTypes(SortedSet objectTypes) {
        this.objectTypes = objectTypes;
    }

    @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.onDelete == null)
            throw new InvalidSchemaException("invalid " + this + ": no delete action specified");
    }

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

    @Override
    boolean isCompatibleWithInternal(AbstractSchemaItem that0) {
        final ReferenceSchemaField that = (ReferenceSchemaField)that0;
        if (!super.isCompatibleWithInternal(that))
            return false;
        if (!this.onDelete.equals(that.onDelete))
            return false;
        if (this.cascadeDelete != that.cascadeDelete)
            return false;
        if (!(this.objectTypes != null ? this.objectTypes.equals(that.objectTypes) : that.objectTypes == null))
            return false;
        return true;
    }

// 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.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);
        final String text = this.getAttr(reader, ON_DELETE_ATTRIBUTE, false);
        final DeleteAction action;
        if (text != null) {
            if ((action = Enum.valueOf(DeleteAction.class, text)) == null) {
                throw new XMLStreamException("invalid value `" + text
                  + " for \"" + ON_DELETE_ATTRIBUTE.getLocalPart() + "\" attribute in " + this, reader.getLocation());
            }
        } else
            action = DeleteAction.EXCEPTION;
        this.setOnDelete(action);
        final Boolean cascadeDeleteAttr = this.getBooleanAttr(reader, CASCADE_DELETE_ATTRIBUTE, false);
        if (cascadeDeleteAttr != null)
            this.setCascadeDelete(cascadeDeleteAttr);
    }

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

        // Any restrictions?
        if (!this.expect(reader, true, 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, OBJECT_TYPE_TAG)) {
            this.objectTypes.add(this.getIntAttr(reader, 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(REFERENCE_FIELD_TAG.getNamespaceURI(), REFERENCE_FIELD_TAG.getLocalPart());
        else
            writer.writeEmptyElement(REFERENCE_FIELD_TAG.getNamespaceURI(), REFERENCE_FIELD_TAG.getLocalPart());
        this.writeAttributes(writer, includeName);
        if (this.onDelete != null)
            writer.writeAttribute(ON_DELETE_ATTRIBUTE.getNamespaceURI(), ON_DELETE_ATTRIBUTE.getLocalPart(), this.onDelete.name());
        if (this.objectTypes != null) {
            writer.writeStartElement(OBJECT_TYPES_TAG.getNamespaceURI(), OBJECT_TYPES_TAG.getLocalPart());
            for (int storageId : this.objectTypes) {
                writer.writeEmptyElement(OBJECT_TYPE_TAG.getNamespaceURI(), OBJECT_TYPE_TAG.getLocalPart());
                writer.writeAttribute(STORAGE_ID_ATTRIBUTE.getNamespaceURI(), 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(CASCADE_DELETE_ATTRIBUTE.getNamespaceURI(), 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.objectTypes != null ? this.objectTypes.equals(that.objectTypes) : that.objectTypes == null);
    }

    @Override
    public int hashCode() {
        return super.hashCode()
          ^ (this.cascadeDelete ? 1 : 0)
          ^ (this.onDelete != null ? this.onDelete.hashCode() : 0)
          ^ (this.objectTypes != null ? this.objectTypes.hashCode() : 0);
    }

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