OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 132 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.sql.element;

import org.openconcerto.sql.element.SQLElement.ReferenceAction;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.graph.Link;
import org.openconcerto.sql.model.graph.Link.Direction;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.sql.model.graph.Step;

import java.util.List;

/**
 * A logical link between two elements. It can be a direct foreign {@link Link} or two links through
 * a {@link JoinSQLElement join}. The {@link #getOwner()} needs the {@link #getOwned()}, i.e. if the
 * owner is unarchived the owned will also be unarchived. The owner is responsible for
 * {@link SQLElement#setupLinks(org.openconcerto.sql.element.SQLElement.LinksSetup) setting up} the properties of
 * the link.
 * 
 * @author Sylvain
 */
public final class SQLElementLink {

    public static enum LinkType {
        /** One element is the parent of the other */
        PARENT,
        /** One element is part of the other */
        COMPOSITION,
        /** One element references the other */
        ASSOCIATION
    }

    private final SQLElement owner, owned;
    private final Path path;
    private final LinkType type;
    private final String name;
    // TODO final (see #setAction())
    private ReferenceAction action;

    protected SQLElementLink(SQLElement owner, Path path, SQLElement owned, final LinkType type, final String name, final ReferenceAction action) {
        super();
        final int length = path.length();
        if (length == 0)
            throw new IllegalArgumentException("Empty path");
        if (owner != null && owner.getTable() != path.getFirst() || owned.getTable() != path.getLast())
            throw new IllegalArgumentException("Wrong path : " + path + " not from owner : " + owner + " to owned : " + owned);
        if (!path.isSingleLink())
            throw new IllegalArgumentException("Isn't single link : " + path);
        // either foreign key or join
        if (length > 2)
            throw new IllegalArgumentException("Path too long : " + path);
        final Step lastStep = path.getStep(-1);
        final boolean endsWithForeign = lastStep.getDirection().equals(Direction.FOREIGN);
        if (length == 1) {
            if (!endsWithForeign)
                throw new IllegalArgumentException("Single step path isn't foreign : " + path);
        } else {
            assert length == 2;
            if (!endsWithForeign || !path.getStep(0).getDirection().equals(Direction.REFERENT))
                throw new IllegalArgumentException("Two steps path isn't a join : " + path);
        }
        if (lastStep.getSingleField() == null)
            throw new IllegalArgumentException("Multi-field not yet supported : " + lastStep);
        this.path = path;
        this.owner = owner;
        this.owned = owned;
        if (type == null || action == null)
            throw new NullPointerException();
        this.type = type;
        if (name != null) {
            this.name = name;
        } else if (length == 1) {
            final SQLField singleField = lastStep.getSingleField();
            this.name = singleField != null ? singleField.getName() : lastStep.getSingleLink().getName();
        } else {
            this.name = lastStep.getFrom().getName();
        }
        assert this.getName() != null;
        this.action = action;
    }

    /**
     * The path from the {@link #getOwner() owner} to the {@link #getOwned() owned}. NOTE : the last
     * step is always {@link Direction#FOREIGN}.
     * 
     * @return the path of this link, its {@link Path#length() length} is 1 or 2 for a join.
     */
    public final Path getPath() {
        return this.path;
    }

    public final boolean isJoin() {
        return this.path.length() == 2;
    }

    /**
     * Return the single link.
     * 
     * @return the single foreign link, <code>null</code> if and only if this is a {@link #isJoin()
     *         join} as multi-link paths are invalid.
     */
    public final Link getSingleLink() {
        if (this.isJoin())
            return null;
        final Link res = this.path.getStep(0).getSingleLink();
        // checked in the constructor
        assert res != null;
        return res;
    }

    /**
     * Return the single field of this link.
     * 
     * @return the foreign field of this link, <code>null</code> if and only if this is a
     *         {@link #isJoin() join} as multi-field link are not yet supported.
     */
    public final SQLField getSingleField() {
        final Link l = this.getSingleLink();
        if (l == null)
            return null;
        final SQLField res = l.getSingleField();
        // checked in the constructor
        assert res != null;
        return res;
    }

    public final SQLElement getOwner() {
        return this.owner;
    }

    public final SQLElement getOwned() {
        return this.owned;
    }

    public final JoinSQLElement getJoinElement() {
        if (!this.isJoin())
            return null;
        return (JoinSQLElement) this.getOwner().getElement(this.getPath().getTable(1));
    }

    public final boolean isOwnerTheParent() {
        final boolean owner;
        if (this.getLinkType().equals(LinkType.COMPOSITION))
            owner = true;
        else if (this.getLinkType().equals(LinkType.PARENT))
            owner = false;
        else
            throw new IllegalStateException("Invalid type : " + this.getLinkType());
        return owner;
    }

    public final SQLElement getParent() {
        return this.getParentOrChild(true);
    }

    private final SQLElement getParentOrChild(final boolean parent) {
        return parent == isOwnerTheParent() ? this.getOwner() : this.getOwned();
    }

    public final SQLElement getChild() {
        return this.getParentOrChild(false);
    }

    public final Path getPathToParent() {
        return this.getPathToParentOrChild(true);
    }

    public final Step getStepToParent() {
        return this.getPathToParent().getStep(-1);
    }

    private final Path getPathToParentOrChild(final boolean toParent) {
        return toParent == isOwnerTheParent() ? this.getPath().reverse() : this.getPath();
    }

    public final Path getPathToChild() {
        return this.getPathToParentOrChild(false);
    }

    public final Step getStepToChild() {
        return this.getPathToChild().getStep(-1);
    }

    public final LinkType getLinkType() {
        return this.type;
    }

    public final String getName() {
        return this.name;
    }

    public final ReferenceAction getAction() {
        return this.action;
    }

    // use SQLElementLinkSetup
    @Deprecated
    public final void setAction(ReferenceAction action) {
        final List<ReferenceAction> possibleActions = getOwner().getPossibleActions(this.getLinkType(), this.getOwned());
        if (!possibleActions.contains(action))
            throw new IllegalArgumentException("Not allowed : " + action);
        this.action = action;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + this.action.hashCode();
        result = prime * result + this.path.hashCode();
        result = prime * result + this.type.hashCode();
        return result;
    }

    // don't use SQLElement to avoid walking the graph
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final SQLElementLink other = (SQLElementLink) obj;
        return this.action.equals(other.action) && this.path.equals(other.path) && this.type.equals(other.type);
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + " '" + this.getName() + "' " + this.getLinkType() + " " + this.getPath();
    }
}