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

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

Go to download

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

The newest version!

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

package io.permazen.schema;

import io.permazen.core.DeleteAction;
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 {

    /**
     * The {@link ItemType} that this class represents.
     */
    public static final ItemType ITEM_TYPE = ItemType.REFERENCE_FIELD;

    private DeleteAction inverseDelete;
    private boolean forwardDelete;
    private boolean allowDeleted;
    private NavigableSet objectTypes;

// Properties

    /**
     * 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 getInverseDelete() {
        return this.inverseDelete;
    }

    /**
     * Set the desired behavior when an object referred to by this field is deleted.
     *
     * @param inverseDelete action on deletion of target object
     * @throws UnsupportedOperationException if this instance is locked down
     */
    public void setInverseDelete(DeleteAction inverseDelete) {
        this.verifyNotLockedDown(false);
        this.inverseDelete = inverseDelete;
    }

    /**
     * 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 isForwardDelete() {
        return this.forwardDelete;
    }

    /**
     * Set the whether to forward cascade delete operations.
     *
     * @param forwardDelete true to forward cascade delete operations, false to do nothing
     * @throws UnsupportedOperationException if this instance is locked down
     */
    public void setForwardDelete(boolean forwardDelete) {
        this.verifyNotLockedDown(false);
        this.forwardDelete = forwardDelete;
    }

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

    /**
     * Set the whether this field may reference non-existent objects in normal (non-detached) transactions.
     *
     * @param allowDeleted true to allow dangling references, otherwise false
     * @throws UnsupportedOperationException if this instance is locked down
     */
    public void setAllowDeleted(boolean allowDeleted) {
        this.verifyNotLockedDown(false);
        this.allowDeleted = allowDeleted;
    }

    /**
     * Get the object types this field is allowed to reference, if so restricted.
     *
     * 

* If not null, the returned set will be unmodifiable if this instance is locked down. * * @return names allowed object types, or null if there is no restriction */ public NavigableSet getObjectTypes() { return this.objectTypes; } /** * Set the object types this field is allowed to reference. * * @param objectTypes names of the allowed object types, or null if there is no restriction */ public void setObjectTypes(NavigableSet objectTypes) { this.verifyNotLockedDown(false); this.objectTypes = objectTypes != null ? new TreeSet<>(objectTypes) : null; } @Override public boolean hasFixedEncoding() { return true; } @Override public boolean isAlwaysIndexed() { return true; } // Lockdown @Override void lockDown1() { super.lockDown1(); if (this.objectTypes != null) this.objectTypes = Collections.unmodifiableNavigableSet(this.objectTypes); } // Validation @Override void validate() { super.validate(); if (this.inverseDelete == null) throw new InvalidSchemaException(String.format("invalid %s: no inverse delete action specified", this)); switch (this.inverseDelete) { case IGNORE: if (!this.allowDeleted) { throw new InvalidSchemaException(String.format( "invalid %s: inverse delete %s is incompatible with disallowing dangling references", this, this.inverseDelete)); } break; case REMOVE: if (this.getParent() == null) { throw new InvalidSchemaException(String.format( "invalid %s: inverse delete %s is only appropriate for complex sub-fields", this, this.inverseDelete)); } break; default: break; } if (this.objectTypes != null && this.objectTypes.stream().anyMatch(Objects::isNull)) throw new InvalidSchemaException(String.format("invalid %s: object types contains null", this)); } // SchemaFieldSwitch @Override public R visit(SchemaFieldSwitch target) { return target.caseReferenceSchemaField(this); } // Schema ID @Override public final ItemType getItemType() { return ITEM_TYPE; } @Override void writeSchemaIdHashData(DataOutputStream output, boolean forSchemaModel) throws IOException { super.writeSchemaIdHashData(output, forSchemaModel); output.writeBoolean(forSchemaModel); if (forSchemaModel) { output.writeUTF(this.inverseDelete.name()); output.writeBoolean(this.forwardDelete); output.writeBoolean(this.allowDeleted); output.writeBoolean(this.objectTypes != null); if (this.objectTypes != null) { output.writeInt(this.objectTypes.size()); for (String typeName : this.objectTypes) output.writeUTF(typeName); } } } // DiffGenerating @Override public Diffs differencesFrom(SimpleSchemaField other) { final Diffs diffs = new Diffs(super.differencesFrom(other)); if (!(other instanceof ReferenceSchemaField)) { diffs.add(String.format("changed %s from %s to %s", "type", other.getClass().getSimpleName(), this.getClass().getSimpleName())); return diffs; } final ReferenceSchemaField that = (ReferenceSchemaField)other; if (!Objects.equals(this.inverseDelete, that.inverseDelete)) diffs.add(String.format("changed %s from %s to %s", "inverse delete", that.inverseDelete, this.inverseDelete)); if (this.forwardDelete != that.forwardDelete) diffs.add(String.format("changed %s from %s to %s", "forward delete", that.forwardDelete, this.forwardDelete)); if (this.allowDeleted != that.allowDeleted) diffs.add(String.format("changed %s from %s to %s", "allow deleted objects", that.allowDeleted, this.allowDeleted)); if (!Objects.equals(this.objectTypes, that.objectTypes)) diffs.add(String.format("changed %s from %s to %s", "object types", that.objectTypes, this.objectTypes)); return diffs; } // XML Reading @Override void readAttributes(XMLStreamReader reader, int formatVersion, boolean requireName) throws XMLStreamException { super.readAttributes(reader, formatVersion, requireName); this.setInverseDelete(this.readAttr(reader, DeleteAction.class, XMLConstants.INVERSE_DELETE_ATTRIBUTE, DeleteAction.EXCEPTION)); final Boolean forwardDeleteAttr = this.getBooleanAttr(reader, XMLConstants.FORWARD_DELETE_ATTRIBUTE, false); if (forwardDeleteAttr != null) this.setForwardDelete(forwardDeleteAttr); this.setAllowDeleted(this.getInverseDelete() == DeleteAction.IGNORE); // defaults to false unless DeleteAction.IGNORE final Boolean allowDeletedAttr = this.getBooleanAttr(reader, XMLConstants.ALLOW_DELETED_ATTRIBUTE, false); if (allowDeletedAttr != null) this.setAllowDeleted(allowDeletedAttr); } @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 type names this.objectTypes = new TreeSet<>(); while (this.expect(reader, true, XMLConstants.OBJECT_TYPE_TAG)) { this.objectTypes.add(this.getAttr(reader, XMLConstants.NAME_ATTRIBUTE)); this.expectClose(reader); // } // Read closing this.expectClose(reader); } // XML Writing @Override void writeXML(XMLStreamWriter writer, boolean includeStorageIds, boolean prettyPrint, boolean includeName) throws XMLStreamException { if (this.objectTypes != null) this.writeStartElement(writer, XMLConstants.REFERENCE_FIELD_TAG); else this.writeEmptyElement(writer, XMLConstants.REFERENCE_FIELD_TAG); this.writeAttributes(writer, includeStorageIds, includeName); if (this.inverseDelete != null) this.writeAttr(writer, XMLConstants.INVERSE_DELETE_ATTRIBUTE, this.inverseDelete.name()); if (this.allowDeleted != (this.inverseDelete == DeleteAction.IGNORE)) this.writeAttr(writer, XMLConstants.ALLOW_DELETED_ATTRIBUTE, this.allowDeleted); if (prettyPrint) this.writeSchemaIdComment(writer); if (this.objectTypes != null) { this.writeStartElement(writer, XMLConstants.OBJECT_TYPES_TAG); for (String typeName : this.objectTypes) { this.writeEmptyElement(writer, XMLConstants.OBJECT_TYPE_TAG); this.writeAttr(writer, XMLConstants.NAME_ATTRIBUTE, typeName); } writer.writeEndElement(); // writer.writeEndElement(); // } } @Override void writeSimpleAttributes(XMLStreamWriter writer) throws XMLStreamException { super.writeSimpleAttributes(writer); if (this.forwardDelete) this.writeAttr(writer, XMLConstants.FORWARD_DELETE_ATTRIBUTE, this.forwardDelete); } // Object @Override public boolean equals(Object obj) { if (obj == this) return true; if (!super.equals(obj)) return false; final ReferenceSchemaField that = (ReferenceSchemaField)obj; return Objects.equals(this.inverseDelete, that.inverseDelete) && this.forwardDelete == that.forwardDelete && this.allowDeleted == that.allowDeleted && Objects.equals(this.objectTypes, that.objectTypes); } @Override public int hashCode() { return super.hashCode() ^ Objects.hashCode(this.inverseDelete) ^ (this.forwardDelete ? 2 : 0) ^ (this.allowDeleted ? 4 : 0) ^ 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