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

com.flagwind.commands.CommandExpressionParser Maven / Gradle / Ivy

package com.flagwind.commands;

import com.flagwind.commons.CharUtils;
import com.flagwind.commons.StringUtils;
import com.flagwind.io.PathAnchor;
import org.apache.commons.lang3.tuple.MutableTriple;

import java.io.IOException;
import java.io.StringReader;
import java.util.*;

import static com.flagwind.io.PathAnchor.*;

/**
 * 命令表达式解析类
 * @author chendb
 */
public class CommandExpressionParser {
    //#region 单例字段
    public static CommandExpressionParser Instance = new CommandExpressionParser();
    //#endregion

    //#region 私有枚举
    private enum CommandPathState {
        /**
         * 无
         */
        None,
        /**
         * 单点
         */
        Dot,
        /**
         * 双点
         */
        DoubleDot,
        /**
         * 斜杠
         */
        Slash,
        /**
         * 部分
         */
        Part,
    }

    private enum CommandPairState {
        None,
        Slash,
        Assign,
        Part,
    }
    //#endregion

    //#region 私有构造
    private CommandExpressionParser() {
    }
    //#endregion

    //#region 解析方法

    public CommandExpression parse(String text) throws IOException {
        if (StringUtils.isBlank(text)) {
            return null;
        }

        CommandExpression result = null;
        CommandExpression current = null;

        StringReader reader = new StringReader(text);

        while (reader.read() > 0) {
            current = parse(reader);

            if (result != null) {
                CommandExpression previous = result;

                while (previous.getNext() != null) {
                    previous = previous.getNext();
                }

                previous.setNext(current);
            }  //线性查找命令表达式的管道链,并更新其指向
            else {
                result = current;
            }

        }



        return result;
    }

    private CommandExpression parse(StringReader reader) throws IOException {
        PathAnchor anchor;
        String name, path;
        List arguments = new ArrayList<>();
        HashMap options = new HashMap<>();

        //解析命令表达式中的路径部分,如果表达式有误则该解析方法内部抛出异常
        MutableTriple result = parsePath(reader);
        anchor = result.getLeft();
        name = result.getMiddle();
        path = result.getRight();
        Map.Entry pair;

        //依次解析命令表达式中的选项和参数
        while ((pair = parsePair(reader)) != null) {
            if (pair != null) {
                if (StringUtils.isBlank(pair.getKey())) {
                    arguments.add(pair.getValue());
                } else {
                    options.put(pair.getKey(), pair.getValue());
                }
            }
        }
        String[] args = arguments.toArray(new String[arguments.size()]);
        //返回一个命令表达式
        return new CommandExpression(anchor, name, path, options.entrySet(), args);
    }
    //#endregion

    //#region 私有方法

    /**
     * 路径解析
     * @param reader
     * @return
     * @throws CommandExpressionException
     * @throws IOException
     */
    private static MutableTriple parsePath(StringReader reader) throws  IOException {
        if (reader == null) {
            throw new NullPointerException("reader");
        }
        MutableTriple result = new MutableTriple<>();
        CommandPathState state = CommandPathState.None;
        List parts = new ArrayList();
        int valueRead = 0;

        //设置输出参数的默认值
        PathAnchor anchor = None;
        String name = "";
        String path = "";

        while ((valueRead = reader.read()) > 0) {
            char chr = (char) valueRead;

            //首先对位于路径中间的点号进行转换,以方便后续的处理
            if (chr == '.' && state == CommandPathState.Part) {
                chr = '/';
            }

            if (chr == '.') {
                switch (state) {
                    case None:
                        state = CommandPathState.Dot;
                        anchor = Current;
                        break;
                    case Dot:
                        state = CommandPathState.DoubleDot;
                        anchor = Parent;
                        break;
                    case DoubleDot:
                        break;
                    case Slash:
                        break;
                    case Part:
                        break;
                    default:
                        throw new CommandExpressionException("Invalid anchor of command path.");
                }
            } else if (chr == '/') {
                if (state == CommandPathState.Slash) {
                    throw new CommandExpressionException("Duplicate '/' slash characters.");
                }

                if (state == CommandPathState.None) {
                    anchor = Root;
                } else if (state == CommandPathState.Part) {
                    parts.add(name);
                    name = "";
                }

                state = CommandPathState.Slash;
            } else if (Character.isLetterOrDigit(chr) || chr == '_') {
                if (state == CommandPathState.Dot || state == CommandPathState.DoubleDot) {
                    throw new CommandExpressionException("Missing '/' slash character between dot and letter or digit.");
                }

                name += chr;
                state = CommandPathState.Part;
            } else if (CharUtils.isWhiteSpace(chr)) {
                if (state == CommandPathState.None) {
                    continue;
                } else {
                    break;
                }
            } else {
                throw new CommandExpressionException("Contains '{chr}' illegal character(s) in the command path.");
            }
        }
        reader.close();

        //如果路径以斜杠符结尾,即为非法路径格式
        if (state == CommandPathState.Slash && ((parts != null && parts.size() > 0) || anchor != Root)) {
            throw new CommandExpressionException("The command path can not at the end of '/' character.");
        }

        if (parts != null && parts.size() > 0) {
            path = String.join(".", parts);
        } else if (StringUtils.isBlank(name)) {
            switch (anchor) {
                case Root:
                    name = "/";
                    break;
                case Current:
                    name = ".";
                    break;
                case Parent:
                    name = "..";
                    break;
            }

            anchor = None;
        }
        result.setLeft(anchor);
        result.setMiddle(name);
        result.setRight(path);
        return result;
    }

    private static Collection> parsePairs(String text) throws IOException {
        List> result = new ArrayList<>();
        if (text == null) {
            return null;
        }

        StringReader reader = new StringReader(text);

        do {
            result.add(parsePair(reader));
        } while (reader.read() > 0);
        reader.close();
        return result;
    }

    private static Map.Entry parsePair(StringReader reader) throws IOException {
        if (reader == null) {
            throw new NullPointerException("reader");
        }

        char quote = '\0';
        boolean isEscaping = false;
        String key = StringUtils.EMPTY;
        String value = StringUtils.EMPTY;
        CommandPairState state = CommandPairState.None;
        int valueRead;

        while ((valueRead = reader.read()) > 0) {
            char chr = (char) valueRead;

            if (chr == '-' || chr == '/') {
                if (state == CommandPairState.Slash) {
                    throw new CommandExpressionException("Duplicate '{chr}' option indicator of command expression.");
                }

                if (state == CommandPairState.None && quote == '\0') {
                    state = CommandPairState.Slash;
                    continue;
                }
            } else if (chr == ':' || chr == '=') {
                if (state == CommandPairState.Part
                        && !StringUtils.isBlank(key)
                        && (quote == '\0' && !isEscaping)) {
                    state = CommandPairState.Assign;
                    continue;
                }
            } else if (chr == '|') {
                if (quote == '\0') {
                    if (StringUtils.isBlank(key) && StringUtils.isBlank(value)) {
                        return null;
                    }

                    return new AbstractMap.SimpleEntry(key, value);
                }
            } else if (CharUtils.isWhiteSpace(chr)) {
                if (state == CommandPairState.Slash) {
                    throw new CommandExpressionException("A white-space character at the back of the option indicator.");
                }

                if (state == CommandPairState.None) {
                    continue;
                } else if (quote == '\0') {
                    return new AbstractMap.SimpleEntry(key, value);
                }
            } else if (isQuote(chr) && !isEscaping) {
                if (quote != '\0') {
                    quote = '\0';
                    continue;
                } else if (state != CommandPairState.Part) {
                    quote = chr;
                    continue;
                }
            }

            //设置转义状态:即当前字符为转义符并且当前状态不为转义状态
            isEscaping = chr == '\\' && (!isEscaping);

            if (isEscaping) {
                continue;
            }

            switch (state) {
                case Slash:
                    key += chr;
                    break;
                case None:
                case Assign:
                    value += chr;
                    break;
                default:
                    if (StringUtils.isBlank(value)) {
                        key += chr;
                    } else {
                        value += chr;
                    }
                    break;
            }

            state = CommandPairState.Part;
        }

        if (StringUtils.isBlank(key) && StringUtils.isBlank(value)) {
            return null;
        }

        return new AbstractMap.SimpleEntry(key, value);
    }

    private static boolean isQuote(char chr) {
        return (chr == '"' || chr == '\'');
    }

    private static char escapeChar(char chr) {
        switch (chr) {
            case 's':
                return ' ';
            case 't':
                return '\t';
            case '\\':
                return '\\';
            default:
                return '\0';
        }
    }
    //#endregion
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy