com.cinchapi.concourse.shell.SyntaxTools.java Source code

Java tutorial

Introduction

Here is the source code for com.cinchapi.concourse.shell.SyntaxTools.java

Source

/*
 * Copyright (c) 2013-2016 Cinchapi Inc.
 * 
 * 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 com.cinchapi.concourse.shell;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import com.google.common.base.CaseFormat;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * A collection of tools for dealing with syntax in the {@link ConcourseShell}.
 * 
 * @author Jeff Nelson
 */
public final class SyntaxTools {

    /**
     * Check {@code line} to see if it is a function call that is missing any
     * commas among arguments.
     * 
     * @param line
     * @param methods
     * @return the line with appropriate argument commas
     */
    public static String handleMissingArgCommas(String line, List<String> methods) {
        int hashCode = methods.hashCode();
        Set<String> hashedMethods = CACHED_METHODS.get(hashCode);
        if (hashedMethods == null) {
            hashedMethods = Sets.newHashSetWithExpectedSize(methods.size());
            hashedMethods.addAll(methods);
            CACHED_METHODS.put(hashCode, hashedMethods);
        }
        char[] chars = line.toCharArray();
        StringBuilder transformed = new StringBuilder();
        StringBuilder gather = new StringBuilder();
        boolean foundMethod = false;
        boolean inSingleQuotes = false;
        boolean inDoubleQuotes = false;
        int parenCount = 0;
        for (char c : chars) {
            if (Character.isWhitespace(c) && !foundMethod) {
                transformed.append(gather);
                transformed.append(c);
                foundMethod = hashedMethods.contains(gather.toString());
                gather.setLength(0);
            } else if (Character.isWhitespace(c) && foundMethod) {
                if (transformed.charAt(transformed.length() - 1) != ',' && !inSingleQuotes && !inDoubleQuotes
                        && c != '\n') {
                    transformed.append(",");
                }
                transformed.append(c);
            } else if (c == '(' && !foundMethod) {
                parenCount++;
                transformed.append(gather);
                transformed.append(c);
                foundMethod = hashedMethods.contains(gather.toString());
                gather.setLength(0);
            } else if (c == '(' && foundMethod) {
                parenCount++;
                transformed.append(c);
            } else if (c == ';') {
                transformed.append(c);
                foundMethod = false;
                parenCount = 0;
            } else if (c == ')') {
                parenCount--;
                transformed.append(c);
                foundMethod = parenCount == 0 ? false : foundMethod;
            } else if (c == '"') {
                transformed.append(c);
                inSingleQuotes = !inSingleQuotes;
            } else if (c == '\'') {
                transformed.append(c);
                inDoubleQuotes = !inDoubleQuotes;
            } else if (foundMethod) {
                transformed.append(c);
            } else {
                gather.append(c);
            }
        }
        transformed.append(gather);
        return transformed.toString();
    }

    /**
     * Check to see if {@code line} is a command that uses short syntax. Short
     * syntax allows the user to call an API method without starting the command
     * with {@code concourse.}. This method compares the line to the list of
     * {@code options} to see if it should be "expanded" from short syntax.
     * Otherwise, the original line is returned.
     * 
     * @param line
     * @param options
     * @return the expanded line, if it is using short syntax, otherwise the
     *         original line
     */
    public static String handleShortSyntax(String line, List<String> options) {
        final String prepend = "concourse.";
        if (line.equalsIgnoreCase("time") || line.equalsIgnoreCase("date")) {
            return line + " \"now\"";
        } else if (!line.contains("(")) {
            // If there are no parens in the line, then we assume that this is a
            // single(e.g non-nested) function invocation.
            if (line.startsWith(prepend)) {
                boolean hasArgs = line.split("\\s+").length > 1;
                if (!hasArgs) {
                    line += "()";
                }
                return line;
            } else {
                String[] query = line.split("\\s+");
                String cmd = query[0];
                if (cmd.contains("_")) { // CON-457,GH-182
                    String replacement = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, cmd);
                    line = line.replaceFirst(cmd, replacement);
                }
                String expanded = prepend + line.trim();
                Pattern pattern = Pattern.compile(expanded.split("\\s|\\(")[0]);
                for (String option : options) {
                    if (pattern.matcher(option).matches()) {
                        boolean hasArgs = expanded.split("\\s+").length > 1;
                        if (!hasArgs) {
                            expanded += "()";
                        }
                        return expanded;
                    }
                }
            }
        } else {
            Set<String> shortInvokedMethods = parseShortInvokedMethods(line);
            for (String method : shortInvokedMethods) {
                if (options.contains(prepend + method)) {
                    line = line.replaceAll("(?<!\\_)" + method + "\\(", prepend + method + "\\(");
                }
            }
        }
        return line;
    }

    /**
     * Examine a line and parse out the names of all the methods that are being
     * invoked using short syntax.
     * <p>
     * e.g. methodA(methodB(x), time(y), methodC(concourse.methodD())) -->
     * methodA, methodB, methodC
     * </p>
     * 
     * @param line
     * @return the set of all the methods which are being invoked using short
     *         syntax
     */
    protected static Set<String> parseShortInvokedMethods(String line) { // visible
                                                                         // for
                                                                         // testing
        Set<String> methods = Sets.newHashSet();
        Set<String> blacklist = Sets.newHashSet("time", "date");
        String regex = "\\b(?!" + StringUtils.join(blacklist, "|") + ")[\\w\\.]+\\("; // match any word followed by an paren except
                                                                                      // for the blacklist
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(line);
        while (matcher.find()) {
            if (!matcher.group().startsWith("concourse.")) {
                methods.add(matcher.group().replace("(", ""));
            }
        }
        return methods;
    }

    /**
     * For the methods in this class that take a list of callable methods, this
     * collection will map the hashcode of that list to a hashset with the same
     * methods. The hashset can be used for more efficient O(1) lookups as
     * opposed to always iterating through the list.
     */
    private static Map<Integer, Set<String>> CACHED_METHODS = Maps.newHashMapWithExpectedSize(1);

    private SyntaxTools() {
        /* noop */}

}