001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.q2;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026
027import org.jline.terminal.Terminal;
028import org.jpos.iso.ISOUtil;
029
030/** Dispatches CLI command lines to the appropriate {@link CLICommand} implementation. */
031public class CLICommandInterface {
032    CLIContext ctx;
033    List<String> prefixes = new ArrayList<String>();
034
035    /**
036     * Returns the list of registered command prefixes.
037     * @return command prefix list
038     */
039    public List<String> getPrefixes() {
040        return prefixes;
041    }
042
043    /**
044     * Creates a CLICommandInterface for the given context.
045     * @param ctx the CLI context
046     */
047    public CLICommandInterface(CLIContext ctx) {
048        this.ctx = ctx;
049    }
050
051    /**
052     * Registers a command prefix.
053     * @param prefix the prefix to add
054     */
055    public void addPrefix(String prefix) {
056        prefixes.add(prefix);
057    }
058
059    /**
060     * Executes the given command line.
061     * @param line the full command line
062     * @throws IOException on I/O failure
063     */
064    public void execCommand(String line) throws IOException {
065        String args[] = parseCommand(line);
066        if (args.length == 0) {
067            return;
068        }
069        String verbatimCmd = args[0];
070        String command = args[0].toUpperCase();
071        String className = command;
072
073        for (String prefix : prefixes) {
074            if (!command.contains(".")) {
075                className = prefix + command;
076            }
077            try {
078                Object cmd = getCommand(className);
079                if (cmd != null) {
080                    try {
081                        args[0] = ISOUtil.unPadLeft(line, ' '); // full line
082                        if (cmd instanceof CLISubSystem) {
083                            CLISubSystem ss = (CLISubSystem) cmd;
084                            ctx.getCLI().setPrompt(ss.getPrompt(ctx, args), ss.getCompletionPrefixes(ctx, args));
085                        }
086                        if (cmd instanceof CLICommand) {
087                            ((CLICommand) cmd).exec(ctx, args);
088                        } else if (cmd instanceof Command) {
089                            Terminal t = ctx.getReader().getTerminal();
090                            ((Command) cmd).exec (t.input(), t.output(), t.output(), args);
091                        }
092                        return;
093                    } catch (Exception ex) {
094                        ctx.printThrowable(ex);
095                    }
096                }
097            } catch (ClassNotFoundException ignored) {
098                // NOPMD
099            } catch (Exception ex) {
100                ctx.printThrowable(ex);
101                break;
102            }
103        }
104        ctx.println("Invalid command '" + verbatimCmd + "'");
105    }
106
107    private Object getCommand(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
108        final ClassLoader cl = Thread.currentThread().getContextClassLoader();
109        return cl.loadClass(className).newInstance();
110    }
111
112    /**
113     * Parses a command line into tokens.
114     * @param line the full command line
115     * @return tokens: [prefix, command, args...]
116     * @throws IOException on I/O failure
117     */
118    public String[] parseCommand(String line) throws IOException {
119        if (line == null) {
120            return new String[0];
121        }
122
123        List<String> matchList = new ArrayList<String>();
124        Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
125        Matcher regexMatcher = regex.matcher(line);
126        while (regexMatcher.find()) {
127            if (regexMatcher.group(1) != null) {
128                // Add double-quoted string without the quotes
129                matchList.add(regexMatcher.group(1));
130            } else if (regexMatcher.group(2) != null) {
131                // Add single-quoted string without the quotes
132                matchList.add(regexMatcher.group(2));
133            } else {
134                // Add unquoted word
135                matchList.add(regexMatcher.group());
136            }
137        }
138        String[] args = new String[matchList.size()];
139        matchList.toArray(args);
140        return args;
141    }
142}