SolverVariable.java

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.constraintlayout.core;

import java.util.Arrays;
import java.util.HashSet;

import static androidx.constraintlayout.core.LinearSystem.FULL_DEBUG;

/**
 * Represents a given variable used in the {@link LinearSystem linear expression solver}.
 */
public class SolverVariable implements Comparable<SolverVariable> {

    private static final boolean INTERNAL_DEBUG = FULL_DEBUG;
    private static final boolean VAR_USE_HASH = false;

    @SuppressWarnings("WeakerAccess")
    public static final int STRENGTH_NONE = 0;
    public static final int STRENGTH_LOW = 1;
    public static final int STRENGTH_MEDIUM = 2;
    public static final int STRENGTH_HIGH = 3;
    @SuppressWarnings("WeakerAccess")
    public static final int STRENGTH_HIGHEST = 4;
    public static final int STRENGTH_EQUALITY = 5;
    public static final int STRENGTH_BARRIER = 6;
    public static final int STRENGTH_CENTERING = 7;
    public static final int STRENGTH_FIXED = 8;

    private static int uniqueSlackId = 1;
    private static int uniqueErrorId = 1;
    private static int uniqueUnrestrictedId = 1;
    private static int uniqueConstantId = 1;
    private static int uniqueId = 1;
    public boolean inGoal;

    private String mName;

    public int id = -1;
    int definitionId = -1;
    public int strength = 0;
    public float computedValue;
    public boolean isFinalValue = false;

    final static int MAX_STRENGTH = 9;
    float[] strengthVector = new float[MAX_STRENGTH];
    float[] goalStrengthVector = new float[MAX_STRENGTH];

    Type mType;

    ArrayRow[] mClientEquations = new ArrayRow[16];
    int mClientEquationsCount = 0;
    public int usageInRowCount = 0;
    boolean isSynonym = false;
    int synonym = -1;
    float synonymDelta = 0;

    /**
     * Type of variables
     */
    public enum Type {
        /**
         * The variable can take negative or positive values
         */
        UNRESTRICTED,
        /**
         * The variable is actually not a variable :) , but a constant number
         */
        CONSTANT,
        /**
         * The variable is restricted to positive values and represents a slack
         */
        SLACK,
        /**
         * The variable is restricted to positive values and represents an error
         */
        ERROR,
        /**
         * Unknown (invalid) type.
         */
        UNKNOWN
    }

    static void increaseErrorId() {
        uniqueErrorId++;
    }

    private static String getUniqueName(Type type, String prefix) {
        if (prefix != null) {
            return prefix + uniqueErrorId;
        }
        switch (type) {
            case UNRESTRICTED: return "U" + ++uniqueUnrestrictedId;
            case CONSTANT: return "C" + ++uniqueConstantId;
            case SLACK: return "S" + ++uniqueSlackId;
            case ERROR: {
                return "e" + ++uniqueErrorId;
            }
            case UNKNOWN:
                return "V" + ++uniqueId;
        }
        throw new AssertionError(type.name());
    }

    /**
     * Base constructor
     *  @param name the variable name
     * @param type the type of the variable
     */
    public SolverVariable(String name, Type type) {
        mName = name;
        mType = type;
    }

    public SolverVariable(Type type, String prefix) {
        mType = type;
        if (INTERNAL_DEBUG) {
            //mName = getUniqueName(type, prefix);
        }
    }

    void clearStrengths() {
        for (int i = 0; i < MAX_STRENGTH; i++) {
            strengthVector[i] = 0;
        }
    }

    String strengthsToString() {
        String representation = this + "[";
        boolean negative = false;
        boolean empty = true;
        for (int j = 0; j < strengthVector.length; j++) {
            representation += strengthVector[j];
            if (strengthVector[j] > 0) {
                negative = false;
            } else if (strengthVector[j] < 0) {
                negative = true;
            }
            if (strengthVector[j] != 0) {
                empty = false;
            }
            if (j < strengthVector.length - 1) {
                representation += ", ";
            } else {
                representation += "] ";
            }
        }
        if (negative) {
            representation += " (-)";
        }
        if (empty) {
            representation += " (*)";
        }
        // representation += " {id: " + id + "}";
        return representation;
    }

    HashSet<ArrayRow> inRows = VAR_USE_HASH ? new HashSet<ArrayRow>() : null;

    public final void addToRow(ArrayRow row) {
        if (VAR_USE_HASH) {
            inRows.add(row);
        } else {
            for (int i = 0; i < mClientEquationsCount; i++) {
                if (mClientEquations[i] == row) {
                    return;
                }
            }
            if (mClientEquationsCount >= mClientEquations.length) {
                mClientEquations = Arrays.copyOf(mClientEquations, mClientEquations.length * 2);
            }
            mClientEquations[mClientEquationsCount] = row;
            mClientEquationsCount++;
        }
    }

    public final void removeFromRow(ArrayRow row) {
        if (VAR_USE_HASH) {
            inRows.remove(row);
        } else {
            final int count = mClientEquationsCount;
            for (int i = 0; i < count; i++) {
                if (mClientEquations[i] == row) {
                    for (int j = i; j < count - 1; j++) {
                        mClientEquations[j] = mClientEquations[j + 1];
                    }
                    mClientEquationsCount--;
                    return;
                }
            }
        }
    }

    public final void updateReferencesWithNewDefinition(LinearSystem system, ArrayRow definition) {
        if (VAR_USE_HASH) {
            for (ArrayRow row : inRows) {
                row.updateFromRow(system, definition, false);
            }
            inRows.clear();
        } else {
            final int count = mClientEquationsCount;
            for (int i = 0; i < count; i++) {
                mClientEquations[i].updateFromRow(system, definition, false);
            }
            mClientEquationsCount = 0;
        }
    }

    public void setFinalValue(LinearSystem system, float value) {
        if (false && INTERNAL_DEBUG) {
            System.out.println("Set final value for " + this + " of "+ value);
        }
        computedValue = value;
        isFinalValue = true;
        isSynonym = false;
        synonym = -1;
        synonymDelta = 0;
        final int count = mClientEquationsCount;
        definitionId = -1;
        for (int i = 0; i < count; i++) {
            mClientEquations[i].updateFromFinalVariable(system,this, false);
        }
        mClientEquationsCount = 0;
    }

    public void setSynonym(LinearSystem system, SolverVariable synonymVariable, float value) {
        if (INTERNAL_DEBUG) {
            System.out.println("Set synonym for " + this + " = " + synonymVariable + " + " + value);
        }
        isSynonym = true;
        synonym = synonymVariable.id;
        synonymDelta = value;
        final int count = mClientEquationsCount;
        definitionId = -1;
        for (int i = 0; i < count; i++) {
            mClientEquations[i].updateFromSynonymVariable(system,this, false);
        }
        mClientEquationsCount = 0;
        system.displayReadableRows();
    }

    public void reset() {
        mName = null;
        mType = Type.UNKNOWN;
        strength = SolverVariable.STRENGTH_NONE;
        id = -1;
        definitionId = -1;
        computedValue = 0;
        isFinalValue = false;
        isSynonym = false;
        synonym = -1;
        synonymDelta = 0;
        if (VAR_USE_HASH) {
            inRows.clear();
        } else {
            final int count = mClientEquationsCount;
            for (int i = 0; i < count; i++) {
                mClientEquations[i] = null;
            }
            mClientEquationsCount = 0;
        }
        usageInRowCount = 0;
        inGoal = false;
        Arrays.fill(goalStrengthVector, 0);
    }

    /**
     * Accessor for the name
     *
     * @return the name of the variable
     */
    public String getName() {
        return mName;
    }

    public void setName(String name) { mName = name; }
    public void setType(Type type, String prefix) {
        mType = type;
        if (INTERNAL_DEBUG && mName == null) {
            mName = getUniqueName(type, prefix);
        }
    }

    @Override
    public int compareTo(SolverVariable v) {
        return this.id - v.id;
    }

    /**
     * Override the toString() method to display the variable
     */
    @Override
    public String toString() {
        String result = "";
        if (INTERNAL_DEBUG) {
            result += mName + "(" + id + "):" + strength;
            if (isSynonym) {
                result += ":S(" + synonym + ")";
            }
            if (isFinalValue) {
                result += ":F(" + computedValue + ")";
            }
        } else {
            if (mName != null) {
                result += mName;
            } else {
                result += id;
            }
        }
        return result;
    }

}