Package org.openbel.framework.common.lang

Source Code of org.openbel.framework.common.lang.Signature

/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.common.lang;

import static java.lang.String.valueOf;
import static java.util.regex.Pattern.compile;
import static org.openbel.framework.common.BELUtilities.noItems;
import static org.openbel.framework.common.BELUtilities.noLength;
import static org.openbel.framework.common.enums.FunctionEnum.getFunctionEnum;
import static org.openbel.framework.common.enums.SemanticStatus.*;
import static org.openbel.framework.common.enums.ValueEncoding.getValueEncoding;

import java.io.Serializable;
import java.util.regex.Pattern;

import org.openbel.framework.common.InvalidArgument;
import org.openbel.framework.common.enums.FunctionEnum;
import org.openbel.framework.common.enums.ReturnType;
import org.openbel.framework.common.enums.SemanticStatus;
import org.openbel.framework.common.enums.ValueEncoding;

/**
* Represents a BEL function signature.
* <p>
* A BEL function signature has the following format:
*
* <pre>
* <i>A</i>([<i>arg1</i>,<i>arg2</i>,...,<i>argN</i>{@code [...]}])<i>B</i>
* </pre>
*
* where <i>A</i> is a BEL function enum and <i>B</i> is a BEL return type. The
* following rules concerning arguments are:
* <ol>
* <li>0 or more arguments are required</li>
* <li>The last argument may be a variable length argument where one or more is
* included with a function</li>
* </ol>
* The <i>arg</i> values refer to BEL function parameters and are represented in
* one of two ways:
*
* <pre>
* F:<i>A</i>
* </pre>
*
* where <i>A</i> is a return type. Or in the second format:
*
* <pre>
* E:<i>B</i>
* </pre>
*
* where <i>B</i> is a value encoding. In both cases, what comes after the colon
* defines what is a valid parameter at that position in the function.
* </p>
*
* @see FunctionEnum BEL function definitions
* @see ReturnType BEL function return types
* @see ValueEncoding BEL function parameter encodings
*/
public final class Signature implements Serializable {
    // TODO: provide signature-based support for additional argument types (not
    // a namespace value, not a function) to better support
    // modification/substitution/truncation/fusion
    public final static String WILDCARD_ENCODING = "*";
    private static final long serialVersionUID = -9134224994229618223L;
    private final static String BAD_SIG;
    private final static Pattern ARGS_REGEX;
    private final static Pattern PARAMS_REGEX;
    private final static String ENCODE_PREFIX = "E:";
    private final static String FUNC_PREFIX = "F:";
    private final static String VARARGS_SUFFIX = "...";
    static {
        BAD_SIG = "Bad signature";
        ARGS_REGEX = compile("[()]");
        PARAMS_REGEX = compile("[:]");
    }

    private final String value;
    private final int hash;
    private final String numArgs;

    /**
     * Creates a signature with the provided value.
     *
     * @param value Signature value
     * @throws InvalidArgument Thrown if the signature is not in the correct
     * format
     */
    public Signature(final String value) {
        if (noLength(value) || !validFormat(value)) {
            final String err = BAD_SIG.concat(", " + value);
            throw new InvalidArgument(err);
        }
        this.value = value;
        hash = value.hashCode();

        numArgs = countArgs(value);
    }

    /**
     * Returns the number of arguments for this signature.
     *
     * @return {@link String}
     */
    public String getNumberOfArguments() {
        return numArgs;
    }

    /**
     * Returns this signature's return type.
     *
     * @return {@link ReturnType}
     */
    public ReturnType getReturnType() {
        return returnType(value);
    }

    /**
     * Returns this signature's function enum.
     *
     * @return {@link FunctionEnum}
     */
    public FunctionEnum getFunction() {
        return function(value);
    }

    /**
     * Returns this signature's value.
     *
     * @return {@link String}
     */
    public String getValue() {
        return value;
    }

    /**
     * Returns {@code true} if the string {@code s} adheres to the signature
     * format, {@code false} otherwise.
     *
     * @param s String
     * @return boolean
     */
    private boolean validFormat(final String s) {
        String[] args = ARGS_REGEX.split(s);
        if (noItems(args)) return false;
        if (args.length < 3) return false;

        // Check for the presence of a function name
        String name = args[0];
        if (name.isEmpty()) return false;
        if (!validFunction(name)) return false;

        // Check for the presence of a return type
        String returntype = args[args.length - 1];
        if (returntype.isEmpty()) return false;
        if (!validReturnType(returntype)) return false;

        // Check for zero or more arguments
        String arglist = args[1];
        if (arglist.isEmpty()) return true;

        // Split parameters on comma
        String[] argsplit = arglist.split(",");
        for (final String arg : argsplit) {
            if (!validParameter(arg)) return false;
        }
        return true;
    }

    /**
     * Returns {@code true} if the string {@code s} adheres to the signature
     * parameter format, {@code false} otherwise.
     *
     * @param s String
     * @return boolean
     */
    private boolean validParameter(final String s) {
        final String[] tokens = PARAMS_REGEX.split(s);
        if (tokens.length != 2) {
            return false;
        }

        final char first = s.charAt(0);
        String second = tokens[1];
        if (second.endsWith(VARARGS_SUFFIX)) {
            // value encoding / return type var args, mangle argument
            second = varargsRoot(second);
        }

        if (first == 'E') {
            if (isWildcard(second)) {
                return true;
            }

            // Verify value encoding exists as an enum
            if (getValueEncoding(second) != null) {
                return true;
            }

            // Fallback to verification based on all character discriminators
            for (final Character c : second.toCharArray()) {
                if (getValueEncoding(c) == null) {
                    return false;
                }
            }
            return true;
        } else if (first == 'F') {
            // Verify return type exists as an enum
            if (ReturnType.getReturnType(second) != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns {@code true} if the string {@code s} is a valid function,
     * {@code false} otherwise.
     *
     * @param s String
     * @return boolean
     */
    private boolean validFunction(final String s) {
        FunctionEnum fe = getFunctionEnum(s);
        if (fe != null) return true;
        return false;
    }

    /**
     * Returns {@code true} if the string {@code s} is a valid return type,
     * {@code false} otherwise.
     *
     * @param s String
     * @return boolean
     */
    private boolean validReturnType(final String s) {
        ReturnType rt = ReturnType.getReturnType(s);
        if (rt != null) return true;
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return hash;
    }

    /**
     * Returns the {@link SemanticStatus semantic status} of the supplied
     * signature against this one.
     *
     * @param other {@link Signature}
     * @return SemanticStatus, which may be null
     */
    public SemanticStatus matches(final Signature other) {
        if (other == null) return null;

        // Check simple function/return types first
        if (getFunction() != other.getFunction()) return INVALID_FUNCTION;
        if (getReturnType() != other.getReturnType())
            return INVALID_RETURN_TYPE;

        String[] myargs = this.getArguments();
        String[] otherargs = other.getArguments();

        return argumentsMatch(myargs, otherargs);
    }

    /**
     * Returns {@code true} if the object {@code o} is this object,
     * {@code false} otherwise.
     *
     * @return boolean
     */
    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (!(o instanceof Signature)) return false;

        final Signature s = (Signature) o;
        if (hash == s.hash) return true;

        return value.equals(s.value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(value);
        return builder.toString();
    }

    /**
     * Returns the argument tokens, split by {@code ,}.
     *
     * @return {@link String} array
     */
    private String[] getArguments() {
        String[] args = ARGS_REGEX.split(value);
        return args[1].split(",");
    }

    /**
     * Returns the {@link SemanticStatus semantic status} of the supplied
     * argument lists.
     *
     * @param args1 Argument list
     * @param args2 Argument list
     * @return SemanticStatus, which may be null
     */
    private SemanticStatus argumentsMatch(final String[] myargs,
            final String[] other) {
        String my_arg, arg;
        boolean varargs = false;
        for (int i = 0, j = 0;; i++, j++) {

            boolean my_last = (i == (myargs.length - 1));
            boolean other_last = (j == (other.length - 1));

            if (myargs[i].endsWith(VARARGS_SUFFIX)) {
                my_arg = varargsRoot(myargs[i]);
                // stay at position in argument list
                i = i - 1;
                varargs = true;
            } else {
                my_arg = myargs[i];
            }

            arg = other[j];
            final SemanticStatus status = argumentsMatch(my_arg, arg);
            if (status != VALID) return status;

            if (my_last && other_last) {
                break;
            } else if (my_last && !varargs) {
                // You have arguments left to match, I don't.
                return TOO_MANY_ARGUMENTS;
            } else if (other_last) {
                // I have arguments left to match, you don't.
                return TOO_FEW_ARGUMENTS;
            }
        }
        return VALID;
    }

    /**
     * Returns the {@link SemanticStatus semantic status} of the supplied
     * arguments.
     *
     * @param arg1 Argument list
     * @param arg2 Argument list
     * @return SemanticStatus, which may be null
     */
    private SemanticStatus argumentsMatch(final String arg1, final String arg2) {

        if (arg1.startsWith(ENCODE_PREFIX)) {
            if (arg2.startsWith(FUNC_PREFIX)) {
                return INVALID_RETURN_TYPE_ARGUMENT;
            }
            if (!arg2.startsWith(ENCODE_PREFIX)) {
                return INVALID_ENCODING_ARGUMENT;
            }

            final String[] tokens1 = PARAMS_REGEX.split(arg1);
            if (tokens1.length != 2) {
                return INVALID_ENCODING_ARGUMENT;
            }

            final String[] tokens2 = PARAMS_REGEX.split(arg2);
            if (tokens2.length != 2) {
                return INVALID_ENCODING_ARGUMENT;
            }

            // If either token is the wildcard, any encoding is valid.
            if (isWildcard(tokens1[1]) || isWildcard(tokens2[1])) {
                return VALID;
            }

            final ValueEncoding ve1 = getValueEncoding(tokens1[1]);
            ValueEncoding ve2 = getValueEncoding(tokens2[1]);
            if (ve2 != null) {
                if (ve1.isAssignableFrom(ve2)) {
                    return VALID;
                }
                return INVALID_ENCODING_ARGUMENT;
            }
            // Fallback to verification based on all character discriminators
            for (final Character c : tokens2[1].toCharArray()) {
                ve2 = getValueEncoding(c);
                if (ve1.isAssignableFrom(ve2)) {
                    return VALID;
                }
            }
            return INVALID_ENCODING_ARGUMENT;
        }

        if (arg1.startsWith(FUNC_PREFIX)) {
            if (arg2.startsWith(ENCODE_PREFIX))
                return INVALID_RETURN_TYPE_ARGUMENT;
            if (!arg2.startsWith(FUNC_PREFIX))
                return INVALID_RETURN_TYPE_ARGUMENT;

            final String[] tokens1 = PARAMS_REGEX.split(arg1);
            if (tokens1.length != 2)
                return INVALID_RETURN_TYPE_ARGUMENT;

            final String[] tokens2 = PARAMS_REGEX.split(arg2);
            if (tokens2.length != 2)
                return INVALID_RETURN_TYPE_ARGUMENT;

            final ReturnType re1 = ReturnType.getReturnType(tokens1[1]);
            final ReturnType re2 = ReturnType.getReturnType(tokens2[1]);

            if (re1 != re2 && !re1.isAssignableFrom(re2))
                return INVALID_RETURN_TYPE_ARGUMENT;
            return VALID;
        }

        return null;
    }

    /**
     * Encodes the supplied value encoding as
     *
     * <pre>
     * E:<i>encoding</i>
     * </pre>
     *
     * @param encoding String encoding
     * @return {@link String}
     */
    public static String encode(final String encoding) {
        return ENCODE_PREFIX.concat(encoding);
    }

    /**
     * Encodes the supplied return type encoding as
     *
     * <pre>
     * F:{@link ReturnType#getDisplayValue() DISPLAY_VALUE}
     * </pre>
     *
     * @param rt {@link ReturnType}
     * @return {@link String}
     */
    public static String encode(final ReturnType rt) {
        return FUNC_PREFIX.concat(rt.getDisplayValue());
    }

    /**
     * Returns the function enum for the provided string.
     *
     * @param s String
     * @return {@link FunctionEnum}
     */
    private static FunctionEnum function(final String s) {
        String[] args = ARGS_REGEX.split(s);
        return getFunctionEnum(args[0]);
    }

    /**
     * Returns the return type for the provided string.
     *
     * @param s String
     * @return {@link ReturnType}
     */
    private static ReturnType returnType(final String s) {
        String[] args = ARGS_REGEX.split(s);
        return ReturnType.getReturnType(args[args.length - 1]);
    }

    /**
     * Returns the root string of a varargs argument.
     * <p>
     * For example, for the string {@code F:myFunction...}, this method returns
     * {@code F:myFunction}.
     * </p>
     *
     * @param s {@link String}
     * @return {@link String}
     */
    private static String varargsRoot(final String s) {
        return s.substring(0, s.length() - (VARARGS_SUFFIX.length()));
    }

    /**
     * Returns the number of arguments for the provided string.
     *
     * @param s {@link String}
     * @return {@link String}
     */
    private static String countArgs(final String s) {
        String[] args = ARGS_REGEX.split(s);
        if ("".equals(args[1])) return "0";

        String[] argarray = args[1].split(",");
        for (final String arg : argarray)
            if (arg.contains(VARARGS_SUFFIX)) return "1 or more";
        return valueOf(argarray.length);
    }

    /**
     * Returns {@code true} if {@code s} is the wildcard encoding, {@code false}
     * otherwise.
     *
     * @param s {@link String}
     * @return boolean
     */
    private static boolean isWildcard(final String s) {
        return WILDCARD_ENCODING.equals(s);
    }
}
TOP

Related Classes of org.openbel.framework.common.lang.Signature

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.