Одна из особенностей комплекса — возможность сохранения и восстановления состояния для большинства реализованных алгоритмов. Пара причин, по которым такой механизм действительно нужен:
- Алгоритмы распознавания и некоторые вспомогательные алгоритмы (например, алгоритмы отбора предикатов для иерархических композиций) работают достаточно долго (от 10 минут до нескольких часов). Хотелось бы иметь возможность в любой момент прервать работу алгоритма и вернуться к нему позже. (При прерывании выполнения программы пользователем сохранение состояния алгоритма возможно за счет использования Runtime.addShutdownHook.)
- Во многих случаях имеет ценность информация, связанная с алгоритмом (например, порядок используемой модели для распознавания). Желательно, чтобы эту информацию не требовалось хранить вручную.
Наиболее очевидный способ хранения структурированных данных в Java, который и был использован — сериализация средствами интерфейса java.io.Serializable. У этого метода есть недостатки (например, при изменении структуры наследования класса или его переименовании десериализация перестает работать), но при разумном процессе разработки количество встреч с ними минимально. Более того, разработка с оглядкой на сериализацию побуждает с самого начала создавать правильную иерархию классов и подбирать для классов подходящие поля.
Понятие алгоритма сосредоточено в интерфейсе Launchable:
public interface Launchable extends Serializable {
/**
* Выполняет алгоритм или задание.
*
* @param env
* окружение, в котором выполняется алгоритм или задание
*/
public void run(Env env);
/**
* Возвращает окружение, в котором выполняется алгоритм или задание.
*
* @return
* окружение
*/
public Env getEnv();
}
Env — среда, в которой выполняется алгоритм; она, в частности, предоставляет доступ к параллельному выполнению кода (за счет ExecutorService) и выводу информации для пользователя.
Пример
Сохраняемый алгоритм, который производит некоторую операцию над целыми числами от 0 до max - 1, может выглядеть так:
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. Если прервать работу алгоритма и затем загрузить его заново, вычисления начнутся с того же числа, на котором они закончились при прерывании.
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) выполняется дважды. В первый раз во время ее выполнения происходит прерывание, так что при запуске алгоритма заново вычисления надо проводить заново.