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

io.mindmaps.graql.internal.query.VarImpl Maven / Gradle / Ivy

/*
 * MindmapsDB - A Distributed Semantic Database
 * Copyright (C) 2016  Mindmaps Research Ltd
 *
 * MindmapsDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MindmapsDB 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MindmapsDB. If not, see .
 */

package io.mindmaps.graql.internal.query;

import com.google.common.collect.Maps;
import io.mindmaps.concept.ResourceType;
import io.mindmaps.graql.ValuePredicate;
import io.mindmaps.graql.Var;
import io.mindmaps.graql.admin.*;
import io.mindmaps.graql.internal.util.StringConverter;
import io.mindmaps.graql.internal.gremlin.MultiTraversal;
import io.mindmaps.graql.internal.gremlin.VarTraversals;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Stream;

import static io.mindmaps.graql.Graql.eq;
import static io.mindmaps.graql.Graql.var;
import static io.mindmaps.util.ErrorMessage.MULTIPLE_IDS;
import static io.mindmaps.util.ErrorMessage.MULTIPLE_TYPES;
import static io.mindmaps.util.ErrorMessage.SET_GENERATED_VARIABLE_NAME;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;

/**
 * Implementation of Var interface
 */
class VarImpl implements VarInternal {

    private String name;
    private final boolean userDefinedName;

    private boolean abstractFlag = false;
    private Optional> datatype = Optional.empty();

    private Optional id = Optional.empty();

    private boolean valueFlag = false;
    private final Set values = new HashSet<>();

    private Optional lhs = Optional.empty();
    private Optional rhs = Optional.empty();

    private Optional isa = Optional.empty();
    private Optional ako = Optional.empty();

    private final Set hasRole = new HashSet<>();
    private final Set playsRole = new HashSet<>();
    private final Set hasScope = new HashSet<>();
    private final Set hasResourceTypes = new HashSet<>();

    private final Map> resources = new HashMap<>();

    private final Set castings = new HashSet<>();

    private Optional varPattern = Optional.empty();

    /**
     * Create a variable with a random variable name
     */
    VarImpl() {
        this.name = UUID.randomUUID().toString();
        this.userDefinedName = false;
    }

    /**
     * @param name the variable name of the variable
     */
    VarImpl(String name) {
        this.name = name;
        this.userDefinedName = true;
    }

    /**
     * Create a variable by combining a collection of other variables
     * @param vars a collection of variables to combine
     */
    VarImpl(Collection vars) {
        VarAdmin first = vars.iterator().next();
        this.name = first.getName();
        this.userDefinedName = first.isUserDefinedName();

        valueFlag = false;

        for (VarAdmin var : vars) {
            if (var.isUserDefinedName()) {
                this.name = var.getName();
            }

            valueFlag |= var.hasValue();
            abstractFlag |= var.getAbstract();

            var.getDatatype().ifPresent(this::datatype);
            var.getType().ifPresent(this::isa);
            var.getAko().ifPresent(this::ako);

            var.getId().ifPresent(this::id);
            var.getLhs().ifPresent(this::lhs);
            var.getRhs().ifPresent(this::rhs);
            values.addAll(var.getValuePredicates());

            hasRole.addAll(var.getHasRoles());
            playsRole.addAll(var.getPlaysRoles());
            hasScope.addAll(var.getScopes());

            // Currently it is guaranteed that resource types are specified with an ID
            //noinspection OptionalGetWithoutIsPresent
            var.getResourcePredicates().forEach(
                    (type, values) -> values.forEach(value -> has(type.getId().get(), value))
            );

            castings.addAll(var.getCastings());
        }
    }

    @Override
    public Var id(String id) {
        this.id.ifPresent(
                prevId -> {
                    if (!prevId.equals(id)) throw new IllegalStateException(MULTIPLE_IDS.getMessage(id, prevId));
                }
        );
        this.id = Optional.of(id);
        return this;
    }

    @Override
    public Var value() {
        valueFlag = true;
        return this;
    }

    @Override
    public Var value(Object value) {
        return value(eq(value));
    }

    @Override
    public Var value(ValuePredicate predicate) {
        values.add(predicate.admin());
        return this;
    }

    @Override
    public Var has(String type) {
        VarAdmin resourceVar = var().id(Objects.requireNonNull(type)).admin();
        resources.putIfAbsent(resourceVar, new HashSet<>());
        return this;
    }

    @Override
    public Var has(String type, Object value) {
        return has(type, eq(value));
    }

    @Override
    public Var has(String type, ValuePredicate predicate) {
        VarAdmin resourceVar = var().id(Objects.requireNonNull(type)).admin();
        resources.computeIfAbsent(resourceVar, k -> new HashSet<>()).add(predicate.admin());
        return this;
    }

    @Override
    public Var isa(String type) {
        return isa(var().id(type));
    }

    @Override
    public Var isa(Var type) {
        VarAdmin var = type.admin();

        isa.ifPresent(
                other -> {
                    if (!var.getName().equals(other.getName()) && !var.getIdOnly().equals(other.getIdOnly())) {
                        throw new IllegalStateException(
                                MULTIPLE_TYPES.getMessage(
                                        getPrintableName(), var.getPrintableName(), other.getPrintableName()
                                )
                        );
                    }
                }
        );
        isa = Optional.of(var);
        return this;
    }

    @Override
    public Var ako(String type) {
        return ako(var().id(type));
    }

    @Override
    public Var ako(Var type) {
        ako = Optional.of(type.admin());
        return this;
    }

    @Override
    public Var hasRole(String type) {
        return hasRole(var().id(type));
    }

    @Override
    public Var hasRole(Var type) {
        hasRole.add(type.admin());
        return this;
    }

    @Override
    public Var playsRole(String type) {
        return playsRole(var().id(type));
    }

    @Override
    public Var playsRole(Var type) {
        playsRole.add(type.admin());
        return this;
    }

    @Override
    public Var hasScope(Var type) {
        hasScope.add(type.admin());
        return this;
    }

    @Override
    public Var hasResource(String type) {
        return hasResource(var().id(type));
    }

    @Override
    public Var hasResource(Var type) {
        hasResourceTypes.add(type.admin());
        return this;
    }

    @Override
    public Var rel(String roleplayer) {
        return rel(var(roleplayer));
    }

    @Override
    public Var rel(Var roleplayer) {
        castings.add(new Casting(roleplayer.admin()));
        return this;
    }

    @Override
    public Var rel(String roletype, String roleplayer) {
        return rel(var().id(roletype), var(roleplayer));
    }

    @Override
    public Var rel(Var roletype, String roleplayer) {
        return rel(roletype, var(roleplayer));
    }

    @Override
    public Var rel(String roletype, Var roleplayer) {
        return rel(var().id(roletype), roleplayer);
    }

    @Override
    public Var rel(Var roletype, Var roleplayer) {
        castings.add(new Casting(roletype.admin(), roleplayer.admin()));
        return this;
    }

    @Override
    public Var isAbstract() {
        abstractFlag = true;
        return this;
    }

    @Override
    public Var datatype(ResourceType.DataType datatype) {
        this.datatype = Optional.of(datatype);
        return this;
    }

    @Override
    public Var lhs(String lhs) {
        this.lhs = Optional.of(lhs);
        return this;
    }

    @Override
    public Var rhs(String rhs) {
        this.rhs = Optional.of(rhs);
        return this;
    }

    @Override
    public VarInternal admin() {
        return this;
    }


    @Override
    public Optional getType() {
        return isa;
    }

    @Override
    public boolean isRelation() {
        return !castings.isEmpty();
    }

    @Override
    public boolean isUserDefinedName() {
        return userDefinedName;
    }

    @Override
    public boolean usesNonEqualPredicate() {
        Stream predicates = Stream.of(
                values.stream(),
                resources.values().stream().flatMap(Collection::stream)
        ).flatMap(Function.identity());

        return predicates.anyMatch(id -> !id.equalsValue().isPresent());
    }

    @Override
    public boolean getAbstract() {
        return abstractFlag;
    }

    @Override
    public Optional> getDatatype() {
        return datatype;
    }

    @Override
    public boolean hasValue() {
        return valueFlag;
    }

    @Override
    public Optional getAko() {
        return ako;
    }

    @Override
    public Set getHasRoles() {
        return hasRole;
    }

    @Override
    public Set getPlaysRoles() {
        return playsRole;
    }

    @Override
    public Set getScopes() {
        return hasScope;
    }

    @Override
    public Set getHasResourceTypes() {
        return hasResourceTypes;
    }

    @Override
    public Set getRoleTypes() {
        return getIdNames(castings.stream().map(VarAdmin.Casting::getRoleType).flatMap(this::optionalToStream));
    }

    @Override
    public Optional getId() {
        return id;
    }

    @Override
    public Set getResourceTypes() {
        // Currently it is guaranteed that resource types are specified with an ID
        //noinspection OptionalGetWithoutIsPresent
        return resources.keySet().stream().map(var -> var.getId().get()).collect(toSet());
    }

    @Override
    public boolean hasNoProperties() {
        // return true if this variable has any properties set
        return !id.isPresent() && !valueFlag && values.isEmpty() && !isa.isPresent() && !ako.isPresent() &&
                hasRole.isEmpty() && playsRole.isEmpty() && hasScope.isEmpty() && resources.isEmpty() &&
                castings.isEmpty();
    }

    @Override
    public Optional getIdOnly() {
        if (id.isPresent() && !valueFlag && values.isEmpty() && !isa.isPresent() && !ako.isPresent() &&
                hasRole.isEmpty() && playsRole.isEmpty() && hasScope.isEmpty() && resources.isEmpty() &&
                castings.isEmpty() && !userDefinedName) {
            return getId();
        } else {
            return Optional.empty();
        }
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        if (!userDefinedName) throw new RuntimeException(SET_GENERATED_VARIABLE_NAME.getMessage(name));
        this.name = name;
    }

    @Override
    public String getPrintableName() {
        if (userDefinedName) {
            return "$" + name;
        } else {
            return getId().map(StringConverter::idToString).orElse("$" + name);
        }
    }

    @Override
    public Set getValueEqualsPredicates() {
        return getEqualsPredicatesUnknownType(values);
    }

    @Override
    public Set getValuePredicates() {
        return values;
    }

    @Override
    public Optional getLhs() {
        return lhs;
    }

    @Override
    public Optional getRhs() {
        return rhs;
    }

    @Override
    public Map> getResourceEqualsPredicates() {
        return Maps.transformValues(resources, this::getEqualsPredicatesUnknownType);
    }

    @Override
    public Map> getResourcePredicates() {
        return resources;
    }

    public Set getCastings() {
        return castings;
    }

    @Override
    public Set getMultiTraversals() {
        return getVarTraversals().getTraversals().collect(toSet());
    }

    @Override
    public Set getInnerVars() {
        Stack newVars = new Stack<>();
        Set vars = new HashSet<>();

        newVars.add(this);

        while (!newVars.isEmpty()) {
            VarAdmin var = newVars.pop();
            vars.add(var);

            var.getType().ifPresent(newVars::add);
            var.getAko().ifPresent(newVars::add);
            var.getHasRoles().forEach(newVars::add);
            var.getPlaysRoles().forEach(newVars::add);
            var.getScopes().forEach(newVars::add);
            var.getHasResourceTypes().forEach(newVars::add);
            var.getResourcePredicates().keySet().forEach(newVars::add);

            var.getCastings().forEach(casting -> {
                casting.getRoleType().ifPresent(newVars::add);
                newVars.add(casting.getRolePlayer());
            });
        }

        return vars;
    }

    @Override
    public Set getTypeIds() {
        Set results = new HashSet<>();
        getId().ifPresent(results::add);
        getResourceTypes().forEach(results::add);
        id.ifPresent(results::add);
        return results;
    }

    @Override
    public String toString() {
        Set properties = new HashSet<>();

        Set innerVars = getInnerVars();
        innerVars.remove(this);

        if (!innerVars.stream().allMatch(v -> v.getIdOnly().isPresent() || v.hasNoProperties())) {
            throw new UnsupportedOperationException("Graql strings cannot represent a query with inner variables");
        }

        id.ifPresent(i -> properties.add("id " + StringConverter.valueToString(i)));

        if (isRelation()) {
            properties.add("(" + castings.stream().map(Object::toString).collect(joining(", ")) + ")");
        }

        isa.ifPresent(v -> properties.add("isa " + v.getPrintableName()));
        ako.ifPresent(v -> properties.add("ako " + v.getPrintableName()));
        playsRole.forEach(v -> properties.add("plays-role " + v.getPrintableName()));
        hasRole.forEach(v -> properties.add("has-role " + v.getPrintableName()));
        hasScope.forEach(v -> properties.add("has-scope " + v.getPrintableName()));
        hasResourceTypes.forEach(v -> properties.add("has-resource " + v.getPrintableName()));

        getDatatypeName().ifPresent(d -> properties.add("datatype " + d));

        if (getAbstract()) properties.add("is-abstract");

        values.forEach(v -> properties.add("value " + v));

        // Currently it is guaranteed that resource types are specified with an ID
        //noinspection OptionalGetWithoutIsPresent
        resources.forEach(
                (type, predicates) -> predicates.forEach(p -> properties.add("has " + type.getId().get() + " " + p))
        );

        lhs.ifPresent(s -> properties.add("lhs {" + s + "}"));
        rhs.ifPresent(s -> properties.add("rhs {" + s + "}"));

        String name = isUserDefinedName() ? getPrintableName() + " " : "";

        return name + properties.stream().collect(joining(", "));
    }

    /**
     * @return the datatype's name (as referred to in native Graql), if one is specified
     */
    private Optional getDatatypeName() {
        return datatype.map(
                d -> {
                    if (d == ResourceType.DataType.BOOLEAN) {
                        return "boolean";
                    } else if (d == ResourceType.DataType.DOUBLE) {
                        return "double";
                    } else if (d == ResourceType.DataType.LONG) {
                        return "long";
                    } else if (d == ResourceType.DataType.STRING) {
                        return "string";
                    } else {
                        throw new RuntimeException("Unknown data type: " + d.getName());
                    }
                }
        );
    }

    /**
     * @param vars a stream of variables
     * @return the IDs of all variables that refer to things by id in the graph
     */
    private Set getIdNames(Stream vars) {
        return vars.map(VarAdmin::getId).flatMap(this::optionalToStream).collect(toSet());
    }

    /**
     * @param optional the optional to change into a stream
     * @param  the type in the optional
     * @return a stream of one item if the optional has an element, else an empty stream
     */
    private  Stream optionalToStream(Optional optional) {
        return optional.map(Stream::of).orElseGet(Stream::empty);
    }

    /**
     * @param predicates a collection of predicates of an unknown type
     * @return all values of predicates in the collection which are simple 'equals' predicates
     */
    private Set getEqualsPredicatesUnknownType(Collection predicates) {
        return predicates.stream()
                .map(ValuePredicateAdmin::equalsValue)
                .flatMap(this::optionalToStream)
                .collect(toSet());
    }

    /**
     * @return the VarTraversals object representing this Var as gremlin traversals
     */
    private VarTraversals getVarTraversals() {
        VarTraversals varTraversals = this.varPattern.orElseGet(() -> new VarTraversals(this));
        this.varPattern = Optional.of(varTraversals);
        return varTraversals;
    }

    @Override
    public Disjunction> getDisjunctiveNormalForm() {
        // a disjunction containing only one option
        Conjunction conjunction = Patterns.conjunction(Collections.singleton(this));
        return Patterns.disjunction(Collections.singleton(conjunction));
    }

    /**
     * A casting is the pairing of roletype and roleplayer in a relation, where the roletype may be unknown
     */
    public class Casting implements VarAdmin.Casting {
        private final Optional roleType;
        private final VarAdmin rolePlayer;

        /**
         * A casting without a role type specified
         * @param rolePlayer the role player of the casting
         */
        Casting(VarAdmin rolePlayer) {
            this.roleType = Optional.empty();
            this.rolePlayer = rolePlayer;
        }

        /**
         * @param roletype the role type of the casting
         * @param rolePlayer the role player of the casting
         */
        Casting(VarAdmin roletype, VarAdmin rolePlayer) {
            this.roleType = Optional.of(roletype);
            this.rolePlayer = rolePlayer;
        }

        @Override
        public Optional getRoleType() {
            return roleType;
        }

        @Override
        public VarAdmin getRolePlayer() {
            return rolePlayer;
        }

        @Override
        public String toString() {
            return getRoleType().map(r -> r.getPrintableName() + " ").orElse("") + getRolePlayer().getPrintableName();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy