Одна из особенностей комплекса — возможность сохранения и восстановления состояния для большинства реализованных алгоритмов. Пара причин, по которым такой механизм действительно нужен:
- Алгоритмы распознавания и некоторые вспомогательные алгоритмы (например, алгоритмы отбора предикатов для иерархических композиций) работают достаточно долго (от 10 минут до нескольких часов). Хотелось бы иметь возможность в любой момент прервать работу алгоритма и вернуться к нему позже. (При прерывании выполнения программы пользователем сохранение состояния алгоритма возможно за счет использования Runtime.addShutdownHook.)
- Во многих случаях имеет ценность информация, связанная с алгоритмом (например, порядок используемой модели для распознавания). Желательно, чтобы эту информацию не требовалось хранить вручную.
Наиболее очевидный способ хранения структурированных данных в Java, который и был использован — сериализация средствами интерфейса java.io.Serializable. У этого метода есть недостатки (например, при изменении структуры наследования класса или его переименовании десериализация перестает работать), но при разумном процессе разработки количество встреч с ними минимально. Более того, разработка с оглядкой на сериализацию побуждает с самого начала создавать правильную иерархию классов и подбирать для классов подходящие поля.
Понятие алгоритма сосредоточено в интерфейсе Launchable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public interface Launchable extends Serializable { /** * Выполняет алгоритм или задание. * * @param env * окружение, в котором выполняется алгоритм или задание */ public void run(Env env); /** * Возвращает окружение, в котором выполняется алгоритм или задание. * * @return * окружение */ public Env getEnv(); } |
Env — среда, в которой выполняется алгоритм; она, в частности, предоставляет доступ к параллельному выполнению кода (за счет ExecutorService) и выводу информации для пользователя.
Пример
Сохраняемый алгоритм, который производит некоторую операцию над целыми числами от 0
до max - 1
, может выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
import java.io.File; import java.io.IOException; import ua.kiev.icyb.bio.Env; import ua.kiev.icyb.bio.Launchable; public class Enumerator implements Launchable { /** * Версия для сериализации (требуется согласно * спецификации интерфейса Serializable). */ private static final long serialVersionUID = 1L; /** Среда, в которой выполняется задание. */ private transient Env env; /** Верхняя граница перечисления. */ private final int max; /** Текущее обрабатываемое число. */ private int last; public Enumerator(int max) { this.max = max; this.last = 0; } private void eval(int number) { getEnv().debug(1, "eval(" + number + ")"); // Эмуляция трудоемких вычислений try { Thread.sleep(1000); } catch (InterruptedException e) { } getEnv().debug(1, "Finished eval(" + number + ")"); } @Override public void run(Env env) { this.env = env; for (int i = this.last; i < this.max; ) { eval(i); this.last = ++i; // Сохранить прогресс задания getEnv().saveProgress(); } } @Override public Env getEnv() { return this.env; } } |
У алгоритма есть два параметра, которые надо сохранять — max
и последнее обработанное число last
. Если прервать работу алгоритма и затем загрузить его заново, вычисления начнутся с того же числа, на котором они закончились при прерывании.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Enumerator implements Launchable { // код пропущен /** * Создает задание по перечислению чисел от 0 до 9. * Прогресс задания сохраняется в файл. */ public static void main(String[] args) throws IOException { // Имя файла, в который сохраняется задание final String filename = "enumerator.run"; Env env = new Env(); env.setDebugLevel(2); Enumerator enumerator = new Enumerator(10); if (new File(filename).isFile()) { enumerator = env.load(filename); } env.run(enumerator, filename); } } |
Приблизительный результат выполнения:
% java Enumerator Уровень отладки: 2 eval(0) Finished eval(0) eval(1) Finished eval(1) eval(2) ^C % java Enumerator Уровень отладки: 2 eval(2) Finished eval(2) eval(3) Finished eval(3) ...
Видно, что функция eval(2)
выполняется дважды. В первый раз во время ее выполнения происходит прерывание, так что при запуске алгоритма заново вычисления надо проводить заново.