JConsole — эмулятор терминала для Java-программ

от автора

Вступление

Привет, хабраюзеры! Решил поведать вам о мини-библиотеке JConsole. Я сейчас работаю над большим коммерческим проектом на Groovy/Java. Ну и мне пришло задание — написать консольку для приложения. К сожалению, было поставлено условие: никаких сторонних решений, все только свое. Недолго думая, я сел и написал ее. Подробнее — под катом.

Цели

В принципе, чего-то очень серьезного от меня не требовали. Вот, собственно, параметры:

  • Должна быть консолька, принимающая команды с текстовыми аргументами (без заморочек — просто строковый массив).
  • Должно быть приглашение которое вводу, которое можно будет менять в коде.
  • Можно привязывать методы к командам с помощью Reflection и лямбда-выражений.

В целом довольно простой список. Тем, кому не терпится посмотреть на результат, ссылку на репозиторий даю: kkremen/jconsole.

Обзор кода

Ну а теперь к делу. Немного почитав про Reflection и лямбды, я решил сделать "ход конем".
В первую очередь я создал интерфейс Executable:

Executable.java

package org.meinkopf.console; import java.lang.reflect.InvocationTargetException;  public interface Executable {     Object invoke(Object[] args) throws InvocationTargetException, IllegalAccessException; }

Если кто не знает, то можно сделать так:

Executable ex = (args) -> {     return null; } ex.invoke(someArgs);

Теперь при вызове ex.invoke(...) выполнится лямбда-выражение. Ну а для поддержки Reflection я создал класс BasicExecutable, наследующий Executable:

BasicExecutable.java

package org.meinkopf.console;  import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays;  @SuppressWarnings({ "WeakerAccess", "unused" }) public class BasicExecutable implements Executable {     protected Method method;     protected Object target;      public BasicExecutable(Method method, Object target) {         this.method = method;         this.target = target;     }      /* Getters and Setters */      public Object invoke(Object[] args) throws InvocationTargetException, IllegalAccessException {          if (args.length < method.getParameterCount()) {             return "Too few arguments!\n"; // если аргументов слишком мало, выводим на консоль предупреждение         }          return method.invoke(target, Arrays.copyOfRange(args, 0, method.getParameterCount())); // если аргументов слишком много, просто обрезаем ненужные     } }

Еще я написал пару интерфейс-класс для списка команд: CommandList и BasicCommandList соответственно. Базовый класс хранит команды в Map < String, Executable > и возвращает основному классу объекты типа Executable.

Скрытый текст

package org.meinkopf.console;  public interface CommandList {     Executable get(String commandName); }

Скрытый текст

package org.meinkopf.console;  import java.util.HashMap; import java.util.Map;  @SuppressWarnings({ "unused", "WeakerAccess" }) public class BasicCommandList implements CommandList {      protected Map < String, Executable > methodMap = new HashMap <>();      @Override     public Executable get(String command) {         return methodMap.get(command);     }      public void register(@SuppressWarnings("SameParameterValue") String name, Executable command) {         methodMap.put(name, command);     } }

Служебный класс Command хранит разобранную парсером команду в виде строки-имени и ArrayList аргументов. Аргументы, кстати, принимаются только строковые, то есть числа тоже передаются в виде строки.

Основной класс реализует Runnable, но пока я это никак не использовал, и вызываю метод run напрямую.

Скрытый текст

package org.meinkopf.console;  import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Scanner;  @SuppressWarnings({ "WeakerAccess", "unused", "SpellCheckingInspection" }) public class JConsole implements Runnable {      public static String PROMPT = " ~ $ ";     public static String HEADER = "JConsole v0.0.0 for Java Apps\nAuthor: Karl Meinkopf\nBuilt especially for Protium project\n";     protected ArrayList < Command > commandHistory = new ArrayList <>();     protected CommandList commandList = null;     private Scanner scanner;      public JConsole(CommandList list) {         commandList = list;         scanner = new Scanner(System.in);     }      protected Command parse(String rawCommand) {         rawCommand = rawCommand.replaceAll("(\"[\\s\\S]*?\"|[\\S]+)\\s*", "$1\u0001");         rawCommand = rawCommand.replaceAll("\"([\\s\\S]*?)\"", "$1");         String[] rawList = rawCommand.split("\u0001");          ArrayList < String > args = new ArrayList <>(Arrays.asList(rawList));          String command = args.remove(0).trim();          return new Command(command, rawCommand, args);     }      protected Object execute(Command command) throws InvocationTargetException, IllegalAccessException {         Executable executable = commandList.get(command.getName());          if (executable == null) {             return "Can't find command: '" + command.getName() + "'";         }          return executable.invoke(command.getArgs().toArray());     }      protected void prompt( ) {         System.err.println();         System.err.print(PROMPT);          Command command = parse(getInputLine());          Object result;         try {             result = execute(command);         } catch (InvocationTargetException | IllegalAccessException e) {             System.err.println("Can not execute command '" + command.getName() + "'!\n\tReason: " + e.getMessage());             return;         }          if (result != null)             System.err.println(result.toString());     }      protected String getInputLine( ) {         return scanner.nextLine();     }      public void run( ) {         System.err.println(HEADER);          //noinspection InfiniteLoopStatement         while (true) {             prompt();         }     } }

Заключение

Ну вот и вся библиотека. Пока маленькая, но я планирую ее развивать и улучшать. Если есть замечания и советы — буду рад услышать.

ссылка на оригинал статьи https://habrahabr.ru/post/327234/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *