Вступление
Привет, хабраюзеры! Решил поведать вам о мини-библиотеке JConsole. Я сейчас работаю над большим коммерческим проектом на Groovy/Java. Ну и мне пришло задание — написать консольку для приложения. К сожалению, было поставлено условие: никаких сторонних решений, все только свое. Недолго думая, я сел и написал ее. Подробнее — под катом.
Цели
В принципе, чего-то очень серьезного от меня не требовали. Вот, собственно, параметры:
- Должна быть консолька, принимающая команды с текстовыми аргументами (без заморочек — просто строковый массив).
- Должно быть приглашение которое вводу, которое можно будет менять в коде.
- Можно привязывать методы к командам с помощью Reflection и лямбда-выражений.
В целом довольно простой список. Тем, кому не терпится посмотреть на результат, ссылку на репозиторий даю: kkremen/jconsole.
Обзор кода
Ну а теперь к делу. Немного почитав про Reflection и лямбды, я решил сделать "ход конем".
В первую очередь я создал интерфейс Executable:
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:
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/
Добавить комментарий