Введение в проблему
В этой главе я хочу обсудить проблему сохранения настроек программы, которые в процессе работы может выполнить пользователь, для того, чтобы при следующем сеансе работы программа выглядела так, как будто бы ее не закрывали совсем и она продолжает с того места, на котором закончила в прошлый раз. Казалось бы это не очень большая проблема, достаточно просто записать в файлы всю необходимую информацию, а потом прочитать записи при следующем открытии программы. Проблема однако в том, что речь идет о программе, сделанной одним jar-файлом, которая может находиться где угодно и к ней не полагается никаких дополнительных файлов, то есть записывать фактически некуда. Сложность записи в файлы состоит в том, что надо знать путь к файлу, то есть программа либо должна записывать в файлы в собственной папке, либо должна записывать файлы в такую папку, которая на любом компьютере имеет известный адрес. В частности такой папкой может быть папка виртуальной машины Java (JRE), но JRE устанавливается и при установке требует права Администратора. В операционной системе Виндовс обычно ее папка находится на системном диске внутри стандартной папки с названием Program Files". А в новых версиях системы без прав Администратора в эту папку не пускают. Другими словами, использование файлов требует установки программы, записи адресов файлов в регистры либо кое-что еще. Либо файлы можно записывать в ту же папку, что и jar-файл и переносить их вместе в jar-файлом программы в новое место. Однако есть возможность этого не делать, а создать программу, которая запоминает установки внутри своего jar-файла. При этом программа делается одним файлом, но запоминает все настройки и всю необходимую информацию.
Такая возможность существует благодаря тому, что jar-файл -- это реально zip-архив, а java программы легко умеют работать с zip-архивами. Во-первых, есть возможность прочитать любой файл внутри zip-архива программы. Во-вторых, программа может переписать сам jar-файл, который ее представляет. Это довольно любопытная возможность, которая вряд-ли существует в программах, написанных на других языках программирования. К сожалению я не сумел научиться переписывать zip-файлы другим способом, кроме такого, где все файлы копируются из архива на компьютер, переписываются нужные файлы, и потом все файлы на компьютере снова архивируются в один zip-архив, который заменяет исходный файл архива, после чего все скопированные на компьютер файлы уничтожаются. Если файлов не аномально много и они не аномально большие, то эта операция делается мгновенно. Самое разумное -- это делать такую операцию при выходе из программы. Тогда в процессе работы никаких файлов не видно, а после выхода из программы они возникают на мгновение и снова исчезают. Впрочем следы от них в менеджере файлов могут оставаться, это зависит от того, как часто обновляется каталог, однако при любом обновлении каталога они исчезают.
Как это работает
Для того, чтобы показать как это работает, я приготовил вариант демонстрационной программы, которая также, как и программа предыдущей главы, прячет свое окно на экране компьютера и реагирует на клавишу [Esc] для выхода из программы. При запуске она показывает окно стандартного простого редактора текстов, который однако чуть модернизирован по сравнению с тем, который описан в 12-й главе и сделан в виде независимого окна, наследника класса JDialog
. В этом окне уже есть какой-то текст. Текст можно переписать, но его некуда спасти, так как редактор операции спасения текста не имеет, он вспомогательный, то есть является дополнительным редактором к тем программам, которые сами используют текст по назначению. Точнее спасти его все же можно методом копирования в буфер обмена и переносом в другой редактор, который имеет операцию спасения в файл. Но дело не в этом. В нашем случае он является источником настроек программы. Пользователь может изменить текст, выйти из редактора и выйти из программы. При повторном запуске программы редактор покажет измененный текст. То есть он его запомнил. Вот это и демонстрирует программа. Хотя сама программа редактора имеет самостоятельный интерес и я собираюсь описать ее код, но главное не в этом, а в том как пользоваться программами деархивации, архивации и чтения файлов из архива.
По стандартной методике весь код программы, а также саму программу в готовом виде (файл vkZT-jar) можно получить скачивая zip-архив если кликнуть ЗДЕСЬ. Вынимаем (копируем) все файлы из архива в какую-нибудь папку на компьютере. Затем надо отредактировать файл _compile.bat
и указать в нем точный путь к папке вашего компилятора java. После этого запускаем bat-файл на исполнение и получаем все нужные классы. При компиляции выдается сообщение, что код содержит deprecated методы, то есть устаревшие и не рекомендуемые к использованию, но пока все работает, а перестанет работать и появятся ошибки, тогда можно будет и исправить. Запустить программу на исполнение нельзя, она работает только в виде jar-файла. Этот jar-файл надо перенести в другую папку, отдельно от кода. Вместе с ним я даю два bat-файла, которые переименовывают его в zip и обратно. Это удобно для тех, кто работает с программами менеджеров файлов типа Total Commander
. В таких программах кликом на zip архиве можно его открыть и записать в него файлы или уничтожить те, что в нем находятся. Расширение jar для этой цели не годится, так как при клике просто запускается программа на исполнение.
Разбор кода программы
Код начинается с файла vk.java
, в котором записана цепочка стандартных классов. Сам класс vk
имеет только головной метод main
в котором просто запускается конструктор другого класса. Тот, в свою очередь, организует новую нить и запускает главный класс MainFrame
, в котором и делается вся нехитрая работа. Он расширяет стандартный класс JFrame
и реализует интерфейс KeyListener
. Он импортирует некоторые методы стандартных классов и использует разные переменные и объекты классов, которые тоже играют роль переменных. Далее, как и в предыдущей главе, я буду ссылаться на номера строк файла. Итак, начнем разбирать конструктор этого класса. Сначала все знакомо по предыдущим программам. В строках 25 и 26 задается внешний вид окна программы в варианте, разработанном авторами языка Java. Здесь иконка программы из файла не задается, то есть используется стандартная. Далее опять все то же. В строках 27-30 задается титульная строка окна и к окну присоединяется детектор сигналов с клавиатуры, буквально "слушатель клавиатуры". Эта операция означает, что объявленный нами интерфейс будет реализован и все события клавиатуры будут обрабатываться должным образом. Далее присоединяется дополнительно детектор сигналов от иконок окна. Но тут делается по другому. Вместо того, чтобы объявить интерфейс окна, записать волшебное слово this
и расписать интерфейс, мы записываем его сразу в аргументе команды. А именно, в аргументе мы записываем конструктор класса WindowAdapter
и сразу записываем код этого конструктора, то есть сам интерфейс. В нем мы используем только один метод windowClosing(WindowEvent e)
, в котором задаем команду dispose()
чтобы принудительно убрать окно и очистить в памяти все ресурсы, связанные с ним. Иначе память засоряется и ее может не хватить. Этот код обеспечивает пользователю возможность закрыть окно кликом крестика в правом верхнем углу окна. В нашем случае это уже не используется, так как окна не видно, но пусть лучше стоит на всякий случай. Далее определяются размеры экрана монитора, задаются размеры и положение окна и окно активируется (делается видимым).
В строках 31-33 делается новая работа. Главная цель -- это запустить текстовый редактор через конструктор класса textEd
. Но этот редактор сделан так, что ему в аргумент передается только текст титульной строки. Все остальные параметры он получает через общие статические переменные класса MyPro
. Такой метод программирования не является единственно возможным, можно и по другому делать, но это просто мой стиль. Класс MyPro
аккумулирует все параметры, которые должны быть общими между различными классами. Редактор использует переменные x,y,nx,ny,ver,fs,fk,klw
, а также строку txt
. Вообще говоря, все переменные можно изменить в самом редакторе и их надо бы запоминать, а потом считывать. Но в этой программе, просто для уменьшения кода, они фиксированы. А вот сам текст, то есть строка txt
и является той настройкой, которая запоминается. Поэтому ее нельзя задавать просто так. Ее надо прочитать из файла, который находится в самом jar-файле (zip-архиве) программы. И такое мы раньше не делали. Делается это так. Java может работать не только с файлами по адресу на компьютере, но и с URL (uniform resource locator) адресами файлов в интернете. А файлы внутри zip-архива программы не имеют обычного адреса, поэтому их формально отнесли к интернету. Но и файлы на компьютере тоже можно описать через URL, это просто более общая форма. Итак, как получить URL адрес файла внутри jar-файла? А вот как. Есть стандартный метод getClass()
, у класса Object
, который наследуют все остальные классы. Он возвращает класс Class
, к которому принадлежит объект, использующий метод и работающий в данный момент. А у этого класса есть свой метод getResource()
, который возвращает URL, а аргументом у него является имя файла (ресурса). Если задано просто имя файла, то поиск начинается прямо с содержания jar-файла, а если там ничего не найдено, то продолжается в текущей папке. В нашем случае файл с названием info.txt будет найден внутри jar-файла и мы получаем его адрес. А далее мы переписываем текст из этого файла в переменную txt класса MyPro. Для этого используется стандартная функция fileToText
нового стандартного класса MyRW
. В этом стандартном классе я собрал все общие функции (методы), реализующие чтение и запись файлов. Об этом будет разговор дальше. Если операция прошла удачно и строка не пуская, то запускаем редактор. Вот и все.
Далее в этом классе просто обрабатываются события клавиатуры. Точнее, только одно событие -- нажатие клавиши [Esc]. И здесь снова используются методы нового класса MyRW
. Первый метод unzipFiles("vkZT.jar",1)
. Этот метод имеет два аргумента. Первый указывает на имя zip-архива, второй -- что делать с файлами, которые копируются из архива. Если значение 0, то файлы остаются после выхода из программы, если 1, то файлы автоматически уничтожаются, то есть они существуют только на время работы программы. Мы ставим 1. После этого метода надо поставить команду остановки нити данной программы. Дело в том, что копирование файлов может происходить в другой нити, а мы не можем продолжать, пока эта операция не закончится. Вот мы и блокируем программу до тех пор пока не выполнятся все другие операции. Затем мы создаем объект File с именем нашего файла и записываем в него переменную MyPro.txt методом MyRW.textToFile()
. Снова блокируем программу. Затем мы в строку txt записываем каталог архива нашей программы с помощью метода MyRW.zipCat()
. Метод написан таким образом, что имена файлов разделяются символом "|". Используя этот символ как разделитель, мы создаем массив строк имен файлов. Наконец, метод MyRW.zipFiles()
создает новый zip-архив с тем же именем по массиву (списку) имен файлов. Снова ждем окончания работы и выходим из программы.
Если посмотреть на файлы, то данная программа использует классы inpForm, MyPro, MyRW и textED
. Класс inpForm
обсуждался в 15-й главе. Здесь он используется без изменений. Это вполне универсальный класс, который конечно можно видоизменять либо создавать ему аналоги, но я его использую как он написан. Класс MyPro
обсуждался в 10-й главе, но здесь он представлен в очень укороченном виде, только те методы, которые используются в данной программе. В принципе есть максимально широкая версия этого класса, но его можно изменять в зависимости от потребностей. Основное назначение этого класса при этом не меняется. Он содержит общие методы и общие переменные для всей программы. Остальные два класса новые и их назначение разное. Класс MyRW
аккумулирует методы чтения и записи файлов, а класс textEd
-- это вариант готового текстового редактора в простой его форме, которой часто достаточно для многих программ, нуждающихся в получении от пользователя каких-то текстов или, наоборот, при необходимости показать сгенерированный программой текст. Это особенно полезно в тех случаях, когда чтение и запись файлов нежелательны. Для удобства ссылок я опишу эти два класса в следующих главах.