65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Cliché Shell 瞬间创建极简 UI

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (3投票s)

2010年1月28日

CPOL

4分钟阅读

viewsIcon

40595

downloadIcon

142

命令行 UI 自动生成,轻松使代码可运行。

引言:用例

我们经常需要试验代码:也许是为了学习新算法,或者玩弄一个库来发现它的特点。或者,我们只是为自己开发一个工具,不想花时间设计复杂的软件 UI,只想让它尽快工作。

这里描述的工具就是为此目的而服务的:通过提供极其简单但功能强大 UI 自动生成,让 Java 代码在零时间内可运行。

当我在开发一个 Web 应用的数据库管理后台控制台时,想到了创建这个工具的创意。我已经有了包含所有我需要的方法的数据库类,但方法很多,所以创建一个‘正常’的 UI 会很困难。我决定创建一个交互式的命令行 UI,一种 shell。为了避免编写大量相似的代码来处理命令行,我采用了基于反射的方法映射到命令。就这样,Cliché Shell 诞生了。

“你好,世界!”

让我们编写一个方法来加两个数字。因为是 Java,你需要一个类和其他东西。

public class HelloWorld {
    public int add(int a, int b) {
        return a + b;
    }
}

现在我们想让代码可运行。

最简单的传统方法是添加一个 main() 方法,然后将 params[] 转换为 int。这并不难,但这种方法无法扩展:如果有很多方法具有不同的参数,你将不得不编写一个巨大的 switch 语句。我希望你能理解其中的弊端。

Cliché 的方法

import asg.cliche.ShellFactory;
import asg.cliche.Command;
import java.io.IOException;
public class HelloWorld {
    @Command
    public int add(int a, int b) {
        return a + b;
    }
    public static void main(String[] params) throws IOException {
        ShellFactory.createConsoleShell(“hello”, null, new HelloWorld())
                .commandLoop();
    }
}

运行代码,然后在提示符“hello>”下输入add 4 6。输入exit退出 shell。

它很简单,而且可扩展:对于每个新方法,你只需要添加一个 @Command 注释。

这种简单性的主要限制在于类型转换:如果你的方法需要 String 或基本类型(及其 Object 对应项,如 Integer)以外的类型,那么你需要一个包装方法或一个 Converter。Converters 是 Cliché Shell 的扩展,稍后会讨论。

Shell 语言

这是语言基础。

命名约定与 Java 不同。即,commandName 通过自动名称翻译变为 command-name

命令名称的缩写由命令名称中单词的首字母组成,例如,command-name 的默认缩写是 cn

如果命令方法名以 cmdcli 开头,则前缀不包含在命令名称中。例如,cmdSomeMethod 变为 some-method

引用也不同:单引号和双引号等效,除了引号外没有转义字符。

  • "a string"'a string' 相同,并转换为 a string
  • 'a "string" 2'"a ""string"" 2' 相同,并转换为 "string" 2
  • 你可以写 a" "string,这与 a string 相同。

以下是最重要的内置命令。

  • exit 是退出 shell 的命令。
  • ?list (或 ?l) 列出所有用户定义的命令。
  • ?list-all (或 ?la) 列出所有可用命令,包括内置命令。
  • ?help command-name 输出有关命令的详细信息(如果存在)。

请参见 ?la 获取完整列表。

Shell 功能

记录命令

虽然默认命令名称基于方法名称且相当具有描述性,但 Java 不保留方法参数名称的信息(如果我错了,请告诉我)。Cliché Shell 通过 @Param 注释支持参数命名。您还可以使用 @Command 注释重命名命令或添加详细信息。

@Command(description="Varargs example")
public Integer add(
        @Param(name="numbers", description="some numbers to add")
        Integer... numbers) {
    int result = 0;
    for (int i : numbers) {
        result += i;
    }
    return result;
}

在这里,您还看到 Cliché 支持 varargs 方法。(注意 Integer 类的使用:Cliché 支持基本类型,如 int,除非您要覆盖类型转换)。

输入转换

您可以为新类定义转换器,或覆盖内置转换,如下面的代码所示。

public static final InputConverter[] CLI_INPUT_CONVERTERS = {
    // You can use Input Converters to support named constants
    new InputConverter() {
        public Integer convertInput(String original, Class toClass) throws Exception {
            if (toClass.equals(Integer.class)) {
                if (original.equals("one")) return 1;
                if (original.equals("two")) return 2;
                if (original.equals("three")) return 3;
            }
            return null;
        }
    }
};

现在,您可以编写 add one two 并得到 3

CLI_INPUT_CONVERTERS 是一个特殊字段,Shell 会检查它来查找输入转换器。

三点考虑

  • CLI_INPUT_CONVERTERS 中的转换器优先于内置转换规则。
  • 如果您不知道如何处理给定的类型,请返回 null:Shell 将尝试查找更合适的转换器。
  • 由于它处理的是对象,所以您不能转换为基本类型,因此也不能覆盖基本类型的转换。

输出转换

您还可以覆盖 Shell 将命令方法结果转换为字符串的方式(默认是调用 toString())。

public static final OutputConverter[] CLI_OUTPUT_CONVERTERS = {
    new OutputConverter() {
        public Object convertOutput(Object o) {
            if (o.getClass().equals(Integer.class)) {
                int num = (Integer) o;
                if (num == 1) return "one";
                if (num == 2) return "two";
                if (num == 3) return "three";
            }
            return null;
        }
    }
};

现在,add one two 返回 three。当然,还有更好的用途。我曾经为 double[] 编写了一个转换器,该转换器显示了一个带有图形的 Swing 框架。

而且,由于不需要使 CLI_OUTPUT_CONVERTERS 字段 static final,您可以通过其他命令来控制转换。

private boolean displayResults = false;
@Command(description="Turns on table function display")
public void enableResults() {
    displayResults = true;
}
public OutputConverter[] CLI_OUTPUT_CONVERTERS = {
    new OutputConverter() {
        public Object convertOutput(Object toBeFormatted) {
            if (toBeFormatted instanceof U[]) {
                return displayResults ? toBeFormatted : String.format(
                        "[%d rows skipped; see '?h er']", ((U[])toBeFormatted).length);
            } else {
                return null;
            }
        }
    }
};

还有一些我在这里没有描述的其他功能。请查看示例代码,Shell 非常简单且有用!

© . All rights reserved.