Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад] [Содержание] [Дальше]

Создание Web-приложений: Практическое руководство

© Александр Фролов, Григорий Фролов
М.: Русская Редакция, 2001, 1040 стр.

13. Применение аплетов Java

13. Применение аплетов Java.. 1

Система Layout Manager.. 3

Режим FlowLayout 3

Режим GridLayout 4

Режим BorderLayout 4

Режим CardLayout 4

Режим GridBagLayout 5

Поля gridx и gridy. 6

Поля gridwidth и gridheight 6

Поле fill 6

Поле anchor 7

Поля weightx и weighty. 7

Поля ipadx и ipady. 7

Поле insets. 7

Аплет с формой регистрации посетителей.. 8

Главный класс аплета GridBag. 9

Метод init 10

Поле First name. 10

Метка для поля First name. 11

Кнопка OK.. 11

Поле Middle name и метка этого поля. 11

Кнопка Cancel 11

Поле Last name. 11

Метка поля Last name. 12

Поле ZIP. 12

Метка поля ZIP. 12

Поле Country. 12

Метка поля Country. 12

Метод actionPerformed. 12

Класс AppletMsgBox. 13

Конструктор класса AppletMsgBox. 13

Метод actionPerformed. 14

Классы Java для работы в сети.. 14

Класс InetAddress. 14

Класс URL. 15

Конструкторы класса. 15

Методы класса URL. 16

Класс URLConnection. 16

Взаимодействие приложений Java и расширений сервера Web.. 17

Аплет для передачи номера кредитной карточки. 17

Инициализация аплета. 18

Отправка данных расширению сервера Web. 19

Размещение аплета в документе HTML. 20

Исходный текст расширения ISAPI 20

Передача параметров странице ASP.. 21

Работа с архивами формата ZIP. 22

Немного теории. 22

Класс ZipFile. 22

Класс ZipEntry. 23

setCrc. 23

getCrc. 23

setMethod. 23

getMethod. 23

setExtra. 23

getExtra. 23

setComment 23

getComment 23

getCompressedSize. 24

isDirectory. 24

getName. 24

setTime. 24

getTime. 24

setSize. 24

getSize. 24

Определение размера несжатого файла. 24

Описание примера приложения ZipFileView.. 24

Класс ZipFileDialog. 26

Метод updateList класса ZipFileDialog. 26

Метод itemStateChanged класса ZipFileDialog. 27

Метод getZipFileEntryInfo класса ZipFileDialog. 27

Метод actionPerformed класса ZipFileDialog. 28

Метод saveZipFile класса ZipFileDialog. 28

Создание архива формата ZIP. 29

Немного теории. 29

Описание примера приложения DemoZip. 30

Метод main. 31

Метод createZipOutputStream.. 32

Метод addFileToZip. 32

Распаковка архива формата ZIP. 33

Немного теории. 33

Описание примера приложения DemoUnzip. 34

Метод main. 34

Метод extractFromZip. 35

Метод slash2sep. 37

Вычисление контрольной суммы файла.. 37

Немного теории. 37

Интерфейс Checksum.. 37

Классы CRC32 и Adler32. 38

Описание примера приложения GetChecksum.. 38

Полупрозрачные изображения.. 39

Немного теории. 39

Описание примера приложения AlphaDemo. 41

Главный класс аплета AlphaDemo. 42

Метод init 42

Метод paint 42

Метод imgToTransparent 43

Класс TransparentImageFilter 43

 

Последняя глава нашей книги посвящена использованию аплетов Java в приложениях Интернета. Как Вы знаете, программы, написанные на языке программирования Java, способны работать практически на всех распространенных компьютерных платформах. В результате одна из разновидностей таких программ, а именно аплеты Java, получили широкое распространение на страницах серверов Web. Именно так реализуется главное преимущество аплетов Java — совместимость с различными платформами, так как посетители этих страниц могут запускать свои браузеры под управлением различных операционных систем.

Заметим, однако, что в большинстве случаев аплеты Java используются только для достижения всевозможных визуальных эффектов, создания «интеллектуальных» графических ссылок, меню и т. д.

Между тем, они способны решать и более сложные задачи, такие, как непосредственное взаимодействие с расширениями сервера Web в виде приложений CGI и ISAPI и передача параметров страницам ASP. Мы рассмотрим именно эти вопросы, не нашедшие, на наш взгляд, достойного отражения в многочисленных книгах, посвященных приложениям Java.

Тем из Вас, кто еще никогда не создавал программ на языке Java, мы предлагаем наше руководство, размещенное на сервере создателя этого языка Sun Microsystems по адресу http://www.sun.ru/java/books/online/index.html.

Система Layout Manager

В наших примерах мы покажем, как создавать аплеты с формами, позволяющими вводить информацию о кредитных карточках. Эти формы содержат такие элементы управления, как текстовые поля и кнопки.

Начинающих программистов, особенно тех, кто создавал приложения для Windows или OS/2, может шокировать способ, которым в приложениях Java выполняется размещение компонентов и контейнеров внутри окна. Самая большая и неприятная на первый взгляд особенность заключается в невозможности размещения компонентов с указанием точных координат (хотя с применением специальной техники это все же достижимо).

Другая особенность — программы Java не имеют ресурсов, подобных ресурсам исполнимых файлов Windows и описывающих диалоговые панели или элементы управления. Внешний вид пользовательского интерфейса определяется динамически во время выполнения программы.

Поясним, в чем тут дело и для чего нужно преодолевать такие трудности.

Создавая приложения Java, никогда не следует забывать о том, что они предназначены для работы на различных компьютерных платформах. При этом Вы не можете полагаться на то, что Вам будет доступен какой-либо конкретный шрифт, кнопки или другие компоненты будут иметь определенный размер или форму, а видеоадаптер будет работать в режиме с каким-либо заданным или заранее известным разрешением.

Для того чтобы обеспечить работу приложений Windows в режимах с различным разрешением видеоадаптера, размеры элементов управления «привязываются» к размерам системного шрифта. Однако указанный способ недостаточно универсален для применения на различных платформах, так как в разных операционных системах, вероятно, эта «привязка» будет выполняться по-разному. Кроме того, теоретически системный шрифт в какой-нибудь операционной системе может отсутствовать как таковой.

С другой стороны, динамическое формирование внешнего вида пользовательского интерфейса во время работы программы позволит адаптировать его «на ходу» к особенностям конкретной операционной системы. Для этого приложения Java используют достаточно гибкую и мощную систему управления размещением компонентов и контейнеров с названием Layout Manager.

Система Layout Manager способна работать в нескольких основных режимах, отличающихся различными стратегиями размещения компонентов, определения их размеров и выравнивания.

Режим FlowLayout

В самом простом режиме FlowLayout компоненты добавляются в окно контейнера с применением следующего алгоритма. Каждый новый добавленный компонент располагается вслед за предыдущим в направлении слева направо и сверху вниз, при этом выполняется центровка компонентов по горизонтали. Одной из особенностей данного режима является возможное изменение взаимного расположения добавленных компонентов при изменении размеров контейнера.

Установка режима FlowLayout выполняется при помощи метода setLayout, как это показано ниже:

setLayout(new FlowLayout());

Далее компоненты добавляются в окно контейнера методом add, например:

TextField tf;
Button btnGetName;
  . . .
add(tf);
add(btnGetName);

Режим GridLayout

Когда установлен режим размещения GridLayout, все компоненты располагаются в ячейках таблицы, имеющей заданное количество строк и столбцов. Размеры компонентов изменяются таким образом, чтобы они полностью занимали свои ячейки.

Установка режима GridLayout выполняется при помощи метода setLayout, как это показано ниже:

setLayout(new GridLayout());

Далее компоненты добавляются в окно контейнера методом add, например:

TextField tf;
Button btnGetName;
  . . .
add(tf);
add(btnGetName);

Режим BorderLayout

Режим BorderLayout предполагает разделение окна контейнера на рамку и центральную часть. Методу add при этом указывается направление от центра окна, в котором следует размещать компоненты.

Направление указывается следующим образом:

add("Center", btn1); // центр
add("East",   btn2); // восток
add("West",   btn3); // запад
add("North", btn4); // север
add("South", btn5); // юг

Здесь мы добавили в окно контейнера компоненты btn1, …, btn5. При этом компонент btn1 располагается в центре окна контейнера, а остальные компоненты — по бокам. Размеры компонентов изменяются таким образом, чтобы они полностью заполняли контейнер.

Заметим, что Вы не обязаны каждый раз добавлять в контейнер именно пять компонентов и задействовать при этом все возможные направления.

Режим CardLayout

Режим размещения CardLayout предназначен для поочередного размещения нескольких компонентов в одном контейнере (например, класса Panel).

При добавлении компонента в контейнер необходимо передать его имя методу add через первый параметр, например:

picFrame pf;
pf = new picFrame();
add("pic0", pf);

Остальные компоненты добавляются аналогичным образом.

В классе CardLayout предусмотрено несколько методов, предназначенных для выбора отображаемого компонента. Эти методы перечислены в таблице 13-1.

Таблица 13-1. Методы для выбора компонента

Метод

Компонент для отображения

First

Первый

Last

Последний

Next

Следующий

previous

Предыдущий

Show

Произвольный, заданный своим именем

Всем указанным методам, кроме метода show, передается через единственный параметр ссылка на родительский контейнер, в котором выполняется размещение. Методу show через второй параметр дополнительно передается имя компонента (как строка класса String).

Режим GridBagLayout

Последний режим размещения системы LayoutManager — это режим GridBagLayout. Он считается наиболее трудным, однако по сравнению с другими режимами он очень гибкий. В ряде случаев Вам просто не обойтись без него.

Так же как и рассмотренный нами ранее режим GridLayout, режим GridBagLayout предполагает размещение компонентов в ячейках некоторой таблицы заданной размерности. Вот наиболее важные отличия между этими режимами:

·         в режиме GridLayout размещаемые компоненты изменяют свои размеры таким образом, чтобы заполнить ячейки таблицы, в которых они располагаются. Режим GridBagLayout позволяет контролировать этот процесс, причем при необходимости Вы можете задать стратегию такого изменения или отказаться от него вовсе;

·         в режиме GridLayout каждый компонент занимает только одну ячейку. Что же касается режима GridBagLayout, то здесь компоненты могут занимать несколько смежных ячеек в строках или столбцах;

·         при изменении размеров контейнера во время работы приложения при использовании режима GridLayout все компоненты неизбежно изменяют свои размеры. Это далеко не всегда удобно. В режиме GridBagLayout Вы можете управлять стратегией изменения размеров компонентов или отказаться от такого изменения.

Режим размещения компонентов GridBagLayout удобен для создания диалоговых панелей, содержащих такие компоненты, как текстовые поля редактирования, переключатели, кнопки и т. д.

Выбирая соответствующим образом параметры размещения отдельных компонентов путем заполнения соответствующих полей класса GridBagConstraints, можно создавать панели, напоминающие по своему внешнему виду и поведению стандартные диалоговые панели Windows или других операционных систем с графическим интерфейсом. При этом можно добиться, чтобы размеры компонентов и их взаимное расположение не изменялись при корректировке размеров окна контейнера. Это невозможно при работе в других режимах размещения, таких, как FlowLayout или GridLayout.

Как пользоваться режимом размещения GridBagLayout?

Схема достаточно проста.

Прежде всего Вы должны создать объект класса GridBagLayout при помощи конструктора и выбрать его, как это показано ниже:

GridBagLayout gbl = new GridBagLayout();
setLayout(gbl);

Далее Вам нужно создать объект класса GridBagConstraints, поля которого будут определять параметры размещения отдельных компонентов:

GridBagConstraints cnew GridBagConstraints();

Далее Вам нужно задать значения полей объекта класса GridBagConstraints, например, так (позже мы расскажем о назначении отдельных полей):

c.anchor = GridBagConstraints.NORTH;
c.fill = GridBagConstraints.NONE;
c.gridheight = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
c.gridx = GridBagConstraints.RELATIVE;
c.gridy = GridBagConstraints.RELATIVE;
c.insets = new Insets(40, 0, 0, 0);
c.ipadx = 0;
c.ipady = 0;
c.weightx = 0.0;
c.weighty = 0.0;

Подготовив объект класса GridBagConstraints, Вам нужно установить его в системе Layout Manager методом setConstraints и добавить очередной компонент в окно контейнера методом add:

tf = new TextField(30);
gbl.setConstraints(tf, c);
add(tf);

Далее описанная процедура выполняется над всеми остальными добавляемыми компонентами, причем объект класса GridBagConstraints можно не создавать каждый раз заново, а использовать повторно.

Но если все так просто, то в чем же тогда сложность работы с режимом размещения GridBagLayout?

Очевидно, дело в выборе значений параметров объекта класса GridBagConstraints.

Перечислим эти поля и дадим их краткую характеристику. Полную информацию Вы найдете в документации JDK.

Поля gridx и gridy

Поля gridx и gridy задают соответственно номер столбца и номер строки для ячейки, в которую будет помещен компонент. Левой верхней ячейке соответствуют нулевые значения.

В качестве значений для этих полей можно также указывать константу GridBagConstraints.RELATIVE. Если эта константа указана в поле gridx, номер столбца размещаемого компонента будет на единицу больше номера столбца для компонента, размещенного ранее. Аналогично и для поля gridy.

Вы можете использовать значение GridBagConstraints.RELATIVE в данных полях при последовательном размещении компонентов в ячейках таблицы в направлении слева направо и сверху вниз.

Поля gridwidth и gridheight

Поля gridwidth и gridheight определяют количество ячеек, занимаемых добавляемым компонентом.

Если компонент полностью помещается в одну ячейку, Вы вправе задать в этих полях значение единицы. Если же компонент должен занимать, например, две смежные ячейки в одной строке, то для gridwidth нужно задать значение, равное двум, а для gridheight — значение, равное единице.

Специальное значение GridBagConstraints.REMAINDER указывает, что компонент должен занять все оставшееся место в текущей строке (для поля gridwidth) или в текущем столбце (для поля gridheight).

В поля gridwidth и gridheight можно также записать значение GridBagConstraints.RELATIVE. В этом случае будет задано такое расположение компонента, при котором он займет все оставшееся место в строке (для поля gridwidth) или столбце (для поля gridheight), оставив при этом одну свободную ячейку в последнем столбце или строке.

Поле fill

Поле fill определяет стратегию распределения свободного пространства ячейки (или ячеек) таблицы для компонента, если его размеры меньше размеров выделенного для него места.

Возможные значения приведены в таблице 13-2.

Таблица 13-2. Значения поля fill

Значение

Стратегия выделения места

GridBagConstraints.NONE

Компонент не изменяет своих размеров

GridBagConstraints.BOTH

Изменяется высота и ширина, причем таким образом, чтобы компонент занимал все отведенное для него пространство

GridBagConstraints.HORIZONTAL

Компонент растягивается по горизонтали

GridBagConstraints.VERTICAL

Компонент растягивается по вертикали

Поле anchor

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

Для поля anchor Вы можете указать значения, приведенные в таблице 13-3.

Таблица 13-3. Значения поля anchor

Значение

Направление выравнивания

GridBagConstraints.CENTER

По центру

GridBagConstraints.NORTH

Вверх

GridBagConstraints.SOUTH

Вниз

GridBagConstraints.EAST

Вправо

GridBagConstraints.WEST

Влево

GridBagConstraints.NORHEAST

Вверх вправо

GridBagConstraints.NORTHWEST

Вверх влево

GridBagConstraints.SOUTHEAST

Вниз вправо

GridBagConstraints.SOUTHWEST

Вниз влево

Поля weightx и weighty

Эти поля определяют стратегию изменения размеров компонента, отвечая за выделение пространства для столбцов (weightx) и строк (weighty).

Если записать в них нулевые значения, все добавленные компоненты займут место в центре контейнера и будут выровнены по центру (как по вертикали, так и по горизонтали).

Чтобы размеры компонента изменялись по горизонтали или вертикали, в поля weightx и weightx нужно записать значения от 0,0 до 1,0.

Если в столбце несколько компонентов, то его ширина определяется компонентом с максимальным значением weightx. Аналогичное утверждение верно и для строк.

Заметим, что дополнительное пространство добавляется к строкам и столбцам снизу и справа соответственно.

Поля ipadx и ipady

В полях ipadx и ipady Вы можете указать, что размеры компонента необходимо увеличить на заданное количество пикселов по горизонтали и вертикали соответственно.

Поле insets

Поле insets позволяет задать для компонента отступы от краев выделенной ему области. По умолчанию такие отступы отсутствуют.

В поле insets необходимо записать ссылку на объект класса Insets, созданную соответствующим конструктором. Этот конструктор имеет следующий прототип:

public Insets(
  int top,      // отступ сверху
  int left,   // -"- слева
  int bottom, // -"- снизу
  int right); // -"- справа

Аплет с формой регистрации посетителей

В качестве примера демонстрации режима размещения GridBagLayout приведем исходные тексты аплета GridBag .с формой

В окне нашего аплета находится форма для регистрации посетителей сервера, в которой нужно заполнить несколько стандартных полей (рис. 13-1).

Рис. 13-1. Форма для регистрации посетителей

Если Вы заполните форму и щелкнете кнопку OK, на экране появится диалоговая панель, отображающая введенные значения в окне многострочного редактора (рис. 13-2). Кнопка Cancel удаляет введенную информацию.

Рис. 13-2. Отображение введенной информации

Как в окне основного аплета, так и в окне диалоговой панели мы установили режим размещения компонент GridBagLayout.

Перейдем к рассмотрению исходного текста аплета. Полностью он приведен в листинге 13-1.

Листинг 13-1 Вы найдете в файле chap13/Gridbag/GridBag.java на прилагаемом к книге компакт-диске.

Главный класс аплета GridBag

Главный класс аплета GridBag создан на базе класса Applet и реализует интерфейс ActionListener:

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

public class GridBag2 extends Applet
  implements ActionListener
{
  . . .
}

Данный интерфейс необходим для обработки событий, вызываемых нажатием кнопок.

В главном классе мы определили несколько полей, предназначенных для хранения ссылок на компоненты — текстовые поля, метки класса Label и кнопки:

TextField tfFirstName;
Label lbFirstName;
 
TextField tfMiddleName;
Label lbMiddleName;
 
TextField tfLastName;
Label lbLastName;
 
TextField tfZip;
Label lbZip;
 
TextField tfCountry;
Label lbCountry;
 
Button btnOK;
Button btnCancel;

Метод init

Этот метод получает управление при инициализации аплета. Он создает и размещает все компоненты в окне аплета, а затем регистрирует обработчики событий от кнопок.

Компоненты создаются обычным образом при помощи соответствующих конструкторов:

tfFirstName = new TextField(20);
lbFirstName = new Label("First name");
    
tfMiddleName = new TextField(20);
lbMiddleName = new Label("Middle name");
    
tfLastName = new TextField(20);
lbLastName = new Label("Last name");
    
tfZip = new TextField(10);
lbZip = new Label("ZIP code");
    
tfCountry = new TextField(8);
lbCountry = new Label("Country");
    
btnOK = new Button("OK");
btnCancel = new Button("Cancel");

Далее мы устанавливаем режим размещения GridBagLayout и создаем объект класса GridBagConstraints, необходимый для задания параметров размещения отдельных компонент:

GridBagLayout gbl = new GridBagLayout();
GridBagConstraints c =
  new GridBagConstraints();
setLayout(gbl);

Ниже мы расскажем о выборе этих параметров.

Поле First name

Заполнение параметров и размещение этого поля выполняется следующим образом:

c.anchor = GridBagConstraints.NORTHWEST;
c.fill = GridBagConstraints.NONE;
c.gridheight = 1;
c.gridwidth = 1;
c.gridx = GridBagConstraints.RELATIVE;
c.gridy = GridBagConstraints.RELATIVE;
c.insets = new Insets(10, 10, 0, 0);
    
gbl.setConstraints(tfFirstName, c);
add(tfFirstName);

В параметре anchor мы указываем, что выравнивание поля следует выполнять в направлении вверх влево, поэтому поле будет прижато к верхнему левому углу контейнера.

Параметр fill имеет значение GridBagConstraints.NONE, а значит, при корректировке размеров контейнера размеры поля изменяться не будут.

Так как значение полей gridheight и gridwidth равно единице, поле занимает одну ячейку таблицы.

Поля gridx и gridy содержат значение GridBagConstraints.RELATIVE, поэтому добавление поля выполняется в направлении слева направо и сверху вниз.

И наконец, поле insets задает отступы сверху и слева, равные 10 пикселам.

Метка для поля First name

Для этой метки мы используем те же параметры, что и для самого поля:

gbl.setConstraints(lbFirstName, c);
add(lbFirstName);

В результате метка займет положение справа от поля First name.

Кнопка OK

Вот как заполняются параметры размещения для кнопки OK:

c.gridwidth = GridBagConstraints.REMAINDER;
c.ipadx = 32;
gbl.setConstraints(btnOK, c);
add(btnOK);

Как видите, параметр gridwidth имеет значение, равное GridBagConstraints.REMAINDER. В результате кнопка будет последним компонентом в первой строке. Ее размеры останутся неизменными при корректировке размеров контейнера, так как поле fill имеет значение GridBagConstraints.NONE.

Чтобы несколько увеличить размеры кнопки OK по горизонтали, мы задали в поле ipadx значение, равное 32 пикселам.

Поле Middle name и метка этого поля

Перед добавлением поля и его метки мы восстанавливаем параметры ipadx и gridwidth, измененные на предыдущем этапе:

c.ipadx = 0;
c.gridwidth = 1;
gbl.setConstraints(tfMiddleName, c);
add(tfMiddleName);
gbl.setConstraints(lbMiddleName, c);
add(lbMiddleName);

В результате поле Middle name будет размещено в первой ячейки второй строки.

Кнопка Cancel

Эта кнопка размещается так:

c.gridwidth = GridBagConstraints.REMAINDER;
c.ipadx = 10;
c.weightx = 1.0;
gbl.setConstraints(btnCancel, c);
add(btnCancel);

Здесь в поле gridwidth мы указали значение GridBagConstraints.REMAINDER, поэтому кнопка Cancel будет последней во второй строке.

Обратите внимание на поле weightx. Его значение не равно нулю, значит, последний столбец нашей таблицы займет все оставшееся место в направлении вправо. Если бы значение этого поля равнялось нулю, все компоненты оказались бы выровненными по центру в горизонтальном направлении. Вы можете попробовать это сами.

Поле Last name

Это поле добавляется в начало третьей строки:

c.ipadx = 0;
c.gridwidth = 1;
c.weightx = 0.0;
gbl.setConstraints(tfLastName, c);
add(tfLastName);

Здесь мы просто восстанавливаем параметры, аналогичные параметрам поля Middle name, расположенного в начале второй строки.

Метка поля Last name

Эта метка занимает всю оставшуюся часть третьей строки, так как в поле gridwidth мы задали значение GridBagConstraints.REMAINDER:

c.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(lbLastName, c);
add(lbLastName);

Поле ZIP

При размещении этого поля мы восстанавливаем значение параметра gridwidth, измененное на предыдущем этапе:

c.gridwidth = 1;
gbl.setConstraints(tfZip, c);
add(tfZip);

Метка поля ZIP

Для этого компонента мы выделяем всю оставшуюся часть строки, задавая в поле gridwidth значение GridBagConstraints.REMAINDER:

c.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(lbZip, c);
add(lbZip);

Поле Country

Это поле занимает одну ячейку последней строки, поэтому в поле gridwidth мы записали значение 1:

c.gridwidth = 1;
gbl.setConstraints(tfCountry, c);
add(tfCountry);

Метка поля Country

Для этой метки мы установили следующие параметры:

c.weighty = 1.0;
c.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(lbCountry, c);
add(lbCountry);

Так как в поле weighty указано значение 1, для последней строки таблицы отводится все оставшееся снизу пространство контейнера. Если же записать сюда нулевое значение, все компоненты будут центрированы в окне контейнера по вертикали.

Перед завершением работы метод init регистрирует обработчики событий от кнопок:

btnOK.addActionListener(this);
btnCancel.addActionListener(this);

Метод actionPerformed

В задачу этого метода входит обработка событий, происходящих в результате щелчка кнопки, и отображение диалоговой панели.

При щелчке кнопки OK метод actionPerformed получает строки из полей нашей формы и записывает их в текстовую переменную с именем s:

String s = "<Personal information>";
s = "First name: "
  + tfFirstName.getText() +
  "\nMiddle name: "
+ tfMiddleName.getText() +
  "\nLast name: "
   + tfLastName.getText() +
  "\nZIP code: "
    + tfZip.getText() +
  "\nCountry: "
       + tfCountry.getText();

Далее метод создает диалоговую панель класса AppletMsgBox (определенный в нашем приложении), передавая строку s соответствующему конструктору:

AppletMsgBox amsgbox;
amsgbox = new AppletMsgBox(s, "Information");
amsgbox.show();

Панель затем отображается методом show.

В том случае если Вы щелкнете кнопку Cancel, поля формы очистятся:

tfFirstName.setText("");
tfMiddleName.setText("");
tfLastName.setText("");
tfZip.setText("");
tfCountry.setText("");

Класс AppletMsgBox

Этот класс мы создали для отображения диалоговой панели с сообщением и кнопкой OK. Он образован на базе класса Frame и реализует интерфейс ActionListener:

class AppletMsgBox extends Frame
  implements ActionListener
{
  . . .
}

В классе AppletMsgBox определены два поля:

Button btnOK;
TextArea ta;

Первое из них хранит ссылку на кнопку, а второе — ссылку на многострочный редактор текста, в окне которого мы отображаем сообщение.

Конструктор класса AppletMsgBox

Конструктору класса AppletMsgBox передаются два параметра — строка сообщения и строка заголовка:

public AppletMsgBox(String msg, String title)
{
  . . .
}

Первым делом конструктор класса AppletMsgBox вызывает конструктор базового класса Frame, передавая ему строку заголовка title, и устанавливает размеры окна панели:

super(title);
setSize(400, 200);

Кнопка и редактор текста создаются обычным образом:

btnOK = new Button("OK");
ta = new TextArea(msg, 5, 40);
ta.setEditable(false);

Так как поле ta будет использоваться только для отображения сообщений, мы отменяем функцию редактирования, вызывая метод setEditable.

Далее мы устанавливаем режим размещения компонента GridBagLayout:

GridBagLayout gbl = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
setLayout(gbl);

Параметры размещения текстового поля выбираются следующим образом:

c.anchor = GridBagConstraints.CENTER;
c.fill = GridBagConstraints.BOTH;
c.gridheight = 1;
c.gridwidth = GridBagConstraints.REMAINDER;
c.gridx = GridBagConstraints.RELATIVE;
c.gridy = GridBagConstraints.RELATIVE;
c.insets = new Insets(10, 0, 0, 0);
gbl.setConstraints(ta, c);
add(ta);

Задавая в поле anchor значение GridBagConstraints.CENTER, мы добиваемся центрирования редактора внутри выделенного ему пространства.

Так как поле fill имеет значение GridBagConstraints.BOTH, размеры окна редактора изменяются таким образом, чтобы он занимал всю поверхность выделенной ему ячейки таблицы.

Мы расположили окно редактора в одной ячейке (значение поля gridheight равно единице), причем так, чтобы оно заняло всю первую строку (в поле gridwidth мы установили значение GridBagConstraints.REMAINDER).

Что же касается кнопки, то ее размеры останутся постоянными при корректировке размеров контейнера:

c.fill = GridBagConstraints.NONE;
c.ipadx = 35;
gbl.setConstraints(btnOK, c);
add(btnOK);

Заметим, что мы не ввели значения в полях weightx и weighty. В результате при изменении размеров окна диалоговой панели и редактор, и кнопка остаются в его центре.

Перед завершением своей работы конструктор регистрирует обработчик событий для кнопки btnOK:

btnOK.addActionListener(this);

Метод actionPerformed

Этот метод скрывает окно диалоговой панели, когда пользователь щелкает кнопку OK:

public void actionPerformed(ActionEvent e)
{
  if(e.getSource().equals(btnOK))
  {
     setVisible(false);
  }
}

Классы Java для работы в сети

Язык программирования Java отличается богатой и продуманной библиотекой классов, предназначенной для решения самых разных задач — от создания архивов ZIP и работы с растровыми графическими изображениями до задач организации взаимодействия приложений Java через сеть.

В этом разделе кратко описаны основные классы сетевой библиотеки Java, которые потребуются нам для связи аплетов с расширениями сервера Web.

Класс InetAddress

Для работы с адресами IP в библиотеке классов Java предназначен класс InetAddress. С его помощью приложение определяет адрес IP локального узла, а также адреса удаленного узла, заданного своим доменным именем.

Вот прототипы наиболее интересных методов этого класса:

public static InetAddress getLocalHost();
public static InetAddress
getByName(String host);
public static InetAddress[]
getAllByName(String host);
public byte[] getAddress();
public String toString();
public String getHostName();
public boolean equals(Object obj);

Заметим, что создание объекта класса InetAddress выполняется не с помощью оператора new, а с применением статических методов getLocalHost, getByName и getAllByName.

Метод getLocalHost создает объект класса InetAddress для локального узла, то есть для той рабочей станции, на которой выполняется приложение Java:

InetAddress iaLocal;
iaLocal = InetAddress.getLocalHost();

В том случае, если Вас интересует удаленный узел сети, Вы можете создать для него объект класса InetAddress, используя методы getByName или getAllByName. Первый возвращает адрес узла, а второй — массив всех адресов IP, связанных с данным узлом.

Если узел с указанным именем не существует, при выполнении методов getByName и getAllByName возникает исключение UnknownHostException.

Методам getByName и getAllByName допустимо передавать не только имя узла, например www.sun.com, но и строку адреса IP в виде четырех десятичных чисел, разделенных точками.

Кратко рассмотрим другие методы класса InetAddress.

Метод getAddress возвращает массив из четырех байт IP-адреса объекта. Байт с нулевым индексом этого массива содержит старший байт адреса IP.

Метод toString возвращает текстовую строку, которая содержит имя узла, разделитель «/» и адрес IP в виде четырех десятичных чисел, разделенных точками.

Средствами метода getHostName Вы можете определить имя узла, для которого был создан объект класса InetAddress.

И наконец, метод equals предназначен для сравнения адресов IP как объектов класса InetAddress.

Класс URL

Для работы с ресурсами, заданными адресами URL, в библиотеке классов Java имеется очень удобный и мощный класс с названием URL.

С помощью определенных в нем конструкторов и методов нетрудно извлечь и проверить отдельные компоненты адреса: протокол, адрес узла, номер порта, имя файла. Вы также можете открыть поток, связанный с ресурсом и прочитать его для отображения, обработки или для копирования в другой поток.

Расскажем кратко о классе URL.

Конструкторы класса

В этом классе предусмотрено четыре конструктора.

Первый из них создает объект URL для сетевого ресурса, адрес URL которого передается в виде текстовой строки через единственный параметр spec:

public URL(String spec);

В процессе создания объекта проверяется заданный адрес URL. Если адрес указан неверно, возникает исключение MalformedURLException. Это же происходит при попытке использовать протокол, с которым данная система не может работать.

Второй вариант конструктора класса URL допускает раздельное указание протокола, адреса узла, номера порта, а также имя файла:

public URL(String protocol, String host, int port, String file);

Третий вариант предполагает использование номера порта, принятого по умолчанию:

public URL(String protocol, String host, String file);

Для протокола HTTP, например, это порт с номером 80.

И наконец, четвертый вариант конструктора допускает указание контекста адреса URL и строки адреса URL:

public URL(URL context, String spec);

Строка контекста позволяет указывать компоненты адреса URL, отсутствующие в строке spec, такие, как протокол, имя узла, файла или номер порта.

Методы класса URL

Кратко рассмотрим самые интересные методы, определенные в классе URL.

Метод openStream позволяет создать входной поток для чтения файла ресурса, связанного с созданным объектом класса URL:

public final InputStream openStream();

Для выполнения операции чтения из созданного таким образом потока Вы можете использовать метод read, определенный в классе InputStream.

Метод getHost позволит Вам определить имя узла, соответствующего данному объекту URL:

public String getHost();

Метод getFile позволяет получить имя файла, связанного с данным объектом URL:

public String getFile();

Метод getPort предназначен для определения номера порта, на котором выполняется связь для объекта URL:

public int getPort();

Методом getProtocol Вы можете определить протокол, использование которого приводит к установлению соединения с ресурсом, заданным объектом URL:

public String getProtocol();

Метод getRef возвращает текстовую строку ссылки на ресурс, соответствующий данному объекту URL:

public String getRef();

Метод hashCode возвращает хэш-код объекта URL:

public int hashCode();

Вы можете использовать метод equals для определения идентичности адресов URL, заданных двумя объектами класса URL:

public boolean equals(Object obj);

Если адреса URL идентичны, метод equals возвращает значение true, если нет — значение false.

Метод toExternalForm возвращает текстовую строку внешнего представления адреса URL, определенного данным объектом класса URL:

public String toExternalForm();

Метод toString возвращает текстовую строку, представляющую данный объект класса URL:

public String toString();

Класс URLConnection

В этом классе нам интересен метод openConnection.

Он предназначен для создания канала между приложением и сетевым ресурсом, представленным объектом класса URL:

public URLConnection openConnection();

В этом классе также определены методы getOutputStream и getInputStream, средствами которых Вы сможете создать соответственно потоки вывода и ввода , привязанные к каналу.

Взаимодействие приложений Java и расширений сервера Web

Библиотеки классов Java позволяют организовать взаимодействие между приложением Java и такими расширениями сервера Web, как CGI или ISAPI. В этом случае приложения или аплеты Java смогут посылать произвольные данные расширению сервера Web для обработки, а затем получать результат этой обработки.

Методика организации взаимодействия приложений Java и расширений сервера Web основана на применении классов URL и URLConnection.

Приложение Java, желающее работать с расширением сервера Web, создает объект класса URL для программы расширения (то есть для исполняемого модуля расширения CGI или библиотеки динамической компоновки DLL расширения ISAPI).

Далее приложение получает ссылку на канал передачи данных с этим расширением, представленную в качестве объекта класса URLConnection. Затем, пользуясь методами getOutputStream и getInputStream из класса URLConnection, приложение создает с расширением сервера Web выходной и входной канал передачи данных.

Когда приложение отправляет данные в выходной канал, созданный подобным образом, они попадают в стандартный поток ввода приложения CGI или ISAPI. Все выглядит так, как будто бы данные отправлены методом POST из формы, определенной в документе HTML.

Обработав полученные данные, расширение сервера Web записывает их в свой стандартный выходной поток. После этого они становятся доступны приложению Java через входной поток, открытый методом getInputStream класса URLConnection.

Аплет для передачи номера кредитной карточки

Некоторые процессинговые компании предлагают своим клиентам систему безопасного ввода информации о кредитных карточках, реализованную на базе аплетов Java. Их работа основана на шифровании на стороне клиента сведений о карточке, введенных покупателем. При этом аплет шифрует информацию перед передачей ее через сеть, а специальное приложение, работающее на сервере процессинговой компании, получает ее и расшифровывает.

Заметим, что подобная задача также решается при помощи клиентского элемента управления ActiveX, работающего на компьютере покупателя, или при помощи специальной программы. Однако первый способ, основанный на применении ActiveX, не пользуется особой популярностью из-за потенциальной опасности клиентских элементов управления ActiveX. Второй, предполагающий загрузку специальной программы, неудобен для покупателя.

Еще один способ безопасной передачи номера кредитной карточки связан с использованием протокола Secure HTTP. При этом информация шифруется средствами браузера, а расшифровывается на стороне сервера Web. Но и этот способ не без недостатков. Во-первых, длина ключа, равная для интернациональных версий браузера Microsoft Internet Explorer, составляет 40 бит, чего не всегда достаточно. Во-вторых, прежде чем применять указанный протокол, необходимо приобрести специальный сертификат, за использование которого в дальнейшем придется вносить ежемесячную или ежегодную плату.

Аплеты Java полностью обезопасят Вашу информацию. Их можно применять для защищенной передачи данных независимо от наличия соответствующих браузера, кроме того, Вам не придется приобретать какие-либо сертификаты.

Однако, если Вы решили создать собственное средство защищенной передачи критичной информации через Интернет, то мы настоятельно рекомендуем выполнять шифрование с использованием каких-либо известных и хорошо проверенных методов. В Интернете немало доступных исходных текстов криптографических программ, созданных на основе серьезных математических исследований. Что же касается самодельных криптографических систем, то, скорее всего, они не смогут противостоять серьезным попыткам дешифрования.

Мы не будем описывать криптографические алгоритмы и программы, так как это выходит за рамки рассматриваемого в книге материала. Мы приведем исходные тексты аплета CreditCard, передающего информацию о кредитной карточке расширению сервера Web в открытом виде. При необходимости Вы сами добавите к аплету криптографические модули.

Внешний вид аплета CreditCard показан на рис. 13-3.

Рис. 13-3. Ввод номера кредитной карточки в окне аплета

После ввода информации о кредитной карточке посетитель должен щелкнуть кнопку OK. При этом данные из полей формы ввода будут переданы расширению сервера Web, созданному нами в виде приложения ISAPI. Это приложение извлечет данные формы и отправит их обратно аплету. Аплет покажет строку, принятую расширением ISAPI, в диалоговой панели, показанной на рис. 13-4.

Рис. 13-4. Информация о кредитной карточке, полученная от расширения ISAPI

Реальная система передачи номера кредитной карточки обязательно шифрует информацию перед передачей ее на сервер. При этом аплет, выполняющий шифрование, не должен содержать в себе никаких ключей. Дело в том, что злоумышленники могут вскрыть байт-код аплета с целью извлечения секретного ключа. Поэтому лучше, если для получения ключа аплет, например, установит прямую связь с сервером процессинговой компании с применением сокетов и получит ключ через этот канал.

Расширение сервера Web в реальной системе должно расшифровать полученную информацию и выполнить платежную операцию через сервер банка.

Для разработки программных модулей, выполняющих все описанные выше операции, необходимо привлекать специалистов процессинговой компании или банка (если Вы создаете сервер процессинговой компании). Поэтому мы не станем описывать этот процесс более подробно, а займемся исходными текстами аплета, выполняющего обмен данными с расширением сервера Web. Полный исходные текст аплета Вы найдете в листинге 13-2.

Листинг 13-2 хранится в файле chap13/CreditCard/CreditCard.java на прилагаемом к книге компакт-диске.

Инициализация аплета

При инициализации аплета метод init вначале создает все необходимые компоненты и устанавливает режим размещения GridBagLayout:

tfName = new TextField(20);
lbName = new Label("Your name");
    
tfNumber = new TextField(20);
lbNumber = new Label("Credit card number");
    
tfExpiration = new TextField(20);
lbExpiration = new Label("Expiration date");
    
btnOK = new Button("OK");
btnCancel = new Button("Cancel");
    
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
    
setLayout(gbl);

Далее метод init устанавливает параметры размещения отдельных компонентов и добавляет их в окно аплета. При этом используется техника, примененная нами ранее в этой главе при создании аплета GridBag.

Отправка данных расширению сервера Web

Когда пользователь щелкает кнопку OK, управление передается методу actionPerformed. Рассмотрим выполняемые им действия.

Прежде всего, этот метод извлекает данные из компонентов, размещенных в окне аплета. Данные оформляются в виде текстовой строки параметров запуска расширения ISAPI с именем iscard.dll, как это показано ниже:

URL u;
try
{
  u = new URL("http://saturn/cgi-bin/iscard.dll?" +
     "Name=" + tfName.getText() +
     "&cc=" + tfNumber.getText() +
     "&expir=" + tfExpiration.getText() + "&");
  . . .
}
catch(Exception ioe)
{
  String serr=ioe.toString();
  amsgbox = new AppletMsgBox(serr, "Information");
  amsgbox.show();
}

При этом создается новый объект класса URL, представляющий собой адрес URL запускаемого расширения ISAPI. Все операции выполняются в блоке try-catch, так как при работе приложения могут возникать ошибки, связанные, например, с разрывом связи между браузером покупателя и сервером.

Далее мы создаем канал связи с расширением ISAPI как объект класса URLConnection:

URLConnection c;
c = u.openConnection();
c.setDoOutput(true);
c.setDoInput(true);

С помощью методов setDoOutput и setDoInput для канала разрешается выполнения операций вывода и ввода соответственно.

Чтобы принять данные от расширения ISAPI, мы создаем входной поток данных класса DataInputStream, основанный на потоке InputStream. Этот поток, в свою очередь, получен при помощи метода getInputStream:

DataInputStream is;
is = new DataInputStream(c.getInputStream());

Чтение из потока выполняется в цикле:

String str="";
String str1="";
while(true)
{
  str1 = is.readLine();
  if(str1 == null)
     break;
  str += str1;
}

Здесь просто накапливаются полученные данные в строке str.

По достижении конца потока мы его закрываем:

is.close();

После получения всех строк от расширения сервера Web мы отображаем содержимое строки str в диалоговой панели:

amsgbox = new AppletMsgBox(str, "Information");
amsgbox.show();

Размещение аплета в документе HTML

Аплет, выполняющий обмен данными с сервера Web, надо загружать именно с этого сервера, а не с какого-либо другого. Поэтому в документе HTML с аплетом правильно указывайте параметры тега <APPLET>:

<applet name="CreditCard" code="CreditCard"
codebase="http://www.YourServer.ru/AppletDir"
width="400" height="150" align="Top">
</applet>

Параметр CODE должен задавать имя аплета, а параметр CODEBASE — адрес URL каталога, в котором будет расположен аплет.

Полный исходный текст документа HTML, подготовленный нами для аплета CreditCard, Вы найдете в листинге 13-3.

Листинг 13-3 хранится в файле chap13/CreditCard/CreditCard.html на прилагаемом к книге компакт-диске.

Исходный текст расширения ISAPI

В листинге 13-4 находится полный исходный текст расширения ISAPI, подготовленного нами для совместной работы с аплетом CreditCard.

Листинг 13-4 Вы найдете в файле chap13/IsCard/IsCard.c на прилагаемом к книге компакт-диске.

Все основные действия выполняются расширением в функции HttpExtensionProc.

Получив управление, эта функция записывает в буфер szBuff заголовок формируемого документа:

wsprintf(szBuff, "Content-Type: text/plain\r\n\r\n");

Обратите внимание: в заголовке мы указали, что документ представляет собой текст без форматирования, а не страницы HTML. Передаваемый текст из буфера будет прочитан аплетом CreditCard из входного потока.

Далее функция HttpExtensionProc получает строку параметров, переданную аплетом при загрузке расширения, и добавляет ее в буфер:

wsprintf(szTempBuf, "%s", lpECB->lpszQueryString);
strcat(szBuff, szTempBuf);

Таким образом, наше расширение ISAPI отправляет обратно аплету полученные от него параметры.

Содержимое буфера посылается аплету следующим образом:

if(!lpECB->ServerSupportFunction(lpECB->ConnID,
  HSE_REQ_SEND_RESPONSE_HEADER, NULL, NULL, (LPDWORD)szBuff))
{
  return HSE_STATUS_ERROR;
}

lpECB->dwHttpStatusCode = 200;
return HSE_STATUS_SUCCESS;

Создавая реальный проект, не забудьте выполнить сканирование строки lpECB->lpszQueryString с целью извлечения из него параметров и при необходимости — расшифровку. Далее Вы сможете использовать параметры в соответствии с логикой обработки номеров кредитных карточек, предусмотренной банком или процессинговой компанией.

Передача параметров странице ASP

Если по каким-либо причинам Вы не сумеете создать на сервере расширение ISAPI, описанный способ передачи информации о кредитных карточках можно реализовать с помощью серверных сценариев, расположенных в документах ASP. При этом аплет Java получит номер кредитной карточки посетителя и зашифрует его, а затем передаст странице ASP с использованием механизма загрузки нового документа в окно браузера. В этом разделе мы приведем пример аплета CreditCard2, выполняющего указанные действия.

Полный исходный текст аплета приведен в листинге 13-5.

Листинг 13-5 Вы найдете в файле chap13/CreditCard2/CreditCard2.java на прилагаемом к книге компакт-диске.

Так же как и только что описанный аплет CreditCard, аплет CreditCard2 получает от посетителя информацию о кредитной карточке при помощи компонентов, добавленных в режиме размещения GridBagLayout.

Когда посетитель щелкает в окне аплета кнопку OK, метод actionPerformed загружает страницу ASP с именем ccard.asp, указывая ее полный адрес URL в конструкторе класса URL:

u = new URL("http://saturn/ccard.asp?" +
  "Name=" + tfName.getText() +
  "&cc=" + tfNumber.getText() +
  "&expir=" + tfExpiration.getText() + "&");  

Для того чтобы серверный сценарий, расположенный на странице ccard.asp, получил информацию о кредитной карточке, при создании объекта класса URL мы добавляем к адресу страницы параметры, отделив их символом «?». При этом параметры отделяются друг от друга символом «&».

Чтобы загрузить страницу ccard.asp в окно браузера, мы создаем объект класса AppletContext, вызывая для этого метод getAppletContext:

AppletContext appletContext;
appletContext = getAppletContext();

Далее, пользуясь полученным контекстом, аплет загружает в окно браузера документ с адресом URL, подготовленным в переменной u:

if (u != null)
{
  appletContext.showDocument(u, "_self");
}

Для загрузки мы вызываем метод showDocument, передавая ему в качестве второго параметра имя окна для загрузки документа. Пользуясь этим параметром, аплет может загрузить документ в любой фрейм, в существующее или вновь созданное окно браузера. Параметр «_self» означает, что документ будет загружен в то же самое окно, где находится аплет.

Исходный текст страницы ccard.asp приведен в листинге 13-6.

Листинг 13-6 Вы найдете в файле chap13/CreditCard2/ccard.asp на прилагаемом к книге компакт-диске.

В нем мы получаем параметры, переданные аплетом, при помощи объекта Request, а затем отображаем их значения в динамически создаваемом документе HTML:

<p>Name = <%=Request("Name")(1)%>
<br>Credit card number = <%=Request("cc")(1)%>
<br>Expiration date = <%=Request("expir")(1)%>

В реальном проекте параметры можно, например, передать серверному элементу управления ActiveX для расшифровки и дальнейшей обработки. Таким образом, страница ASP станет удобным средством извлечения параметров из данных, отправленных посетителем Вашего сервера при помощи аплета Java.

Работа с архивами формата ZIP

В этом разделе показана методика просмотра содержимого архивных файлов формата ZIP, а также способ извлечения из такого архива отдельных файлов. Приведен пример приложения, демонстрирующего использование классов ZipFile и ZipEntry.

Немного теории

Среди различных библиотек классов Java есть одна очень интересная — с названием java.util.zip. Она позволяет работать с библиотеками файлов широко распространенных форматов ZIP и GZIP.

Применяя соответствующие классы, Вы сможете разрабатывать приложения, способные создавать библиотеки архивных файлов, просматривать их содержимое, извлекать из библиотек отдельные или все файлы.

В этом разделе мы рассмотрим способы извлечения отдельных файлов из библиотеки формата ZIP и получения подробной информации о хранящихся там файлах. Чтобы получить доступ к таким файлам, Вы должны, прежде всего, создать объект класса ZipFile, пользуясь одним из конструкторов этого класса. Первый конструктор позволяет открыть ZIP-файл через объект класса File, а второй — через полный путь к имени файла:

try

  zf = new ZipFile(szZipFilePath);
  . . .
}
catch(Exception ex)
{
  System.out.println(ex.toString());
}

В процессе создания объекта класса ZipFile могут возникать как обычные для файлов исключения IOException, так и исключения класса ZipException, специфические для класса ZipFile.

Класс ZipFile

В классе ZipFile определены методы, перечисленные в табл. 13-4.

Таблица 13-4. Методы класса ZipFile

Метод

Описание

entries

Возвращает перечисление объектов, хранящихся в архивной библиотеке формата ZIP

getName

Возвращает имя ZIP-файла

getInputStream

Возвращает ссылку на входной поток для чтения объектов их архива

getEntry

Возвращает объект, хранящийся в библиотеке, по его имени

Close

Закрывает ZIP-файл

Методы entries и getEntry позволяют получить элементы, хранящиеся в архиве, как объекты класса ZipEntry. Ниже мы получаем перечисление таких объектов:

Enumeration en = zf.entries();

Чтобы извлечь элементы перечисления и записать их в массив zipEntries класса Vector, мы можем создать цикл:

while(en.hasMoreElements())
{
  zipEntries.addElement(
   (ZipEntry)en.nextElement());
}

Класс ZipEntry

В классе ZipEntry определены две константы, конструктор и несколько методов.

Константы обозначают использованный метод компрессии:

public static final int STORED;
public static final int DEFLATED;

Если файл сохранен в архиве без компрессии, для него используется метод ZipEntry.STORED, а если с компрессией — метод ZipEntry.DEFLATED.

Конструктор класса ZipEntry позволяет создать новый элемент оглавления архива с заданным именем:

public ZipEntry(String name);

Что же касается методов класса ZipEntry, то их можно разделить на две группы, одна из которых предназначена для установки атрибутов элемента оглавления архива ZIP, а другая — для извлечения.

Рассмотрим эти методы.

setCrc

public void setCrc(long crc);

Этот метод позволяет установить значение контрольной суммы для неупакованного файла, описываемого данным элементом оглавления архива. Для вычисления контрольной суммы применяется алгоритм CRC-32.

getCrc

public long getCrc();

Метод предназначен для извлечения контрольной суммы, записанной в элемент оглавления.

setMethod

public void setMethod(int method);

С помощью этого метода Вы можете установить режим добавления файла в архив — с упаковкой (компрессией) или без упаковки. В первом случае методу необходимо передать значение ZipEntry.DEFLATED, а во втором — значение ZipEntry.STORED.

getMethod

public int getMethod();

Метод getMethod позволяет знать способ упаковки, который был использован для файла, соответствующего данному элементу архива. Он может возвращать значения ZipEntry.DEFLATED или ZipEntry.STORED.

setExtra

public void setExtra(byte extra[]);

С помощью этого метода можно записать в элемент оглавления архива произвольную дополнительную информацию.

getExtra

public byte[] getExtra();

Метод getExtra возвращает дополнительную информацию, записанную в элементе оглавления архива.

setComment

public void setComment(String comment);

Запись в элемент оглавления архива дополнительной текстовой строки комментария.

getComment

public String getComment();

Чтение строки комментария.

getCompressedSize

public long getCompressedSize();

Метод getCompressedSize возвращает размер упакованного файла или значение -1, если этот размер неизвестен.

isDirectory

public boolean isDirectory();

Помимо файлов, в архиве могут храниться каталоги. В этом случае имя соответствующего элемента должно оканчиваться символом «/». Метод isDirectory позволяет узнать, является ли данные элемент описателем каталога или файла. В первом случае он возвращает значение true, а во втором — false.

getName

public String getName();

Этот метод позволяет узнать имя, соответствующее данному элементу оглавления архива. Изменить имя нельзя — оно передается конструктору класса ZipEntry при создании нового элемента оглавления.

setTime

public void setTime(long time);

Установка времени модификации файла (в количестве миллисекунд, прошедших с начала эпохи).

getTime

public long getTime();

Определение времени модификации файла.

setSize

public void setSize(long size);

Установка размера несжатого файла.

getSize

public long getSize();

Определение размера несжатого файла.

Описание примера приложения ZipFileView

Наш пример автономного приложения ZipFileView создает одно главное окно класса Frame, в котором предусмотрено меню (рис. 13-5).

Рис. 13-5. Главное окно приложения

Строка Open меню File позволяет выбрать (с помощью стандартного диалогового окна класса FileDialog) файл формата ZIP. Сразу после выбора такого файла на экране появляется диалоговое окно со списком имен элементов оглавления выбранного архива ZIP (рис. 13-5).

Рис. 13-5. Список элементов оглавления выбранного архива

Если выделить в этом списке любую строку, в главном окне приложения появится описание содержимого соответствующего элемента оглавления (рис. 13-5).

С помощью кнопки Extract Вы сможете извлечь выделенный файл из архива и сохранить его на диске. Для выбора пути и имени сохраняемого файла наше приложение отобразит на экране стандартное диалоговое окно класса FileDialog.

Рассмотрим наиболее интересные фрагменты исходного текста приложения (листинг 13-7).

Листинг 13-7 Вы найдете в файле chap13/ZipFileView/ZipFileView.java на прилагаемом к книге компакт-диске.

Прежде всего, обратите внимание на классы, подключаемые оператором import:

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.text.*;

Классы java.util.zip.* нужны для работы с файлами ZIP, а класс java.text.* — для форматирования строки даты изменения файлов, записанных в архиве.

Когда пользователь выбирает строку Open из меню File, метод actionPerformed, определенный в классе FrameWindow, отображает на экране стандартное диалоговое окно для выбора ZIP-файла:

public void actionPerformed(ActionEvent e)
{
  String szSelected = null;
   
  if(e.getSource().equals(miOpen))
  {
    fdlg = new FileDialog(this, "Open ZIP-file",
      FileDialog.LOAD);
    fdlg.setFile("*.zip");
    fdlg.show();
     
    if(fdlg.getDirectory() == null ||
      fdlg.getFile() == null)
       return;
     
    String szPath = fdlg.getDirectory() +
      fdlg.getFile();
    . . .
}

Путь к выбранному файлу записывается в переменную szPath. Он затем передается конструктору класса ZipFileDialog, определенному в нашем приложении для отображения списка элементов оглавления ZIP-файла:

ZipFileDialog d = new ZipFileDialog(
  "ZIP File: ", szPath, this);
d.show();

Класс ZipFileDialog

Класс ZipFileDialog создан на базе класса Dialog и реализует ряд интерфейсов:

class ZipFileDialog extends Dialog
  implements ActionListener, WindowListener,
    ItemListener
{
  . . .
}

В этом классе определено несколько полей.

В поле zf класса ZipFile хранится ссылка на объект, созданный для выбранного пользователем ZIP-файла:

ZipFile zf;

Массив zipEntries класса Vector предназначен для хранения извлеченных элементов оглавления ZIP-файла:

Vector zipEntries = new Vector();

Конструктор класса ZipFileDialog создает все необходимые компоненты, и добавляют их в окно панели с применением режима размещения компонентов GridBagLayout. Перед завершением своей работы он вызывает метод updateList:

updateList();

Этот метод заполняет список, расположенный в окне, именами элементов оглавления архива.

Метод updateList класса ZipFileDialog

Рассмотрим исходный текст метода updateList, заполняющий список именами элементов оглавления архива.

Первым делом этот метод очищает список, удаляя из него все элементы:

l.removeAll();

Далее метод updateList выполняет все операции в блоке try-catch, что необходимо для обработки исключений.

Первым делом мы создаем объект класса ZipFile и с помощью метода entries получаем перечисление элементов оглавления архива:

try

  zf = new ZipFile(szZipFilePath);   
  Enumeration en = zf.entries();
  . . .
}

Далее метод изменяет заголовок диалогового окна, добавляя туда путь к выбранному архиву:

setTitle("ZIP - " + szZipFilePath);

Пользуясь полученным перечислением, мы заполняем массив zipEntries, записывая в него объекты класса ZipEntry:

while(en.hasMoreElements())
{
  zipEntries.addElement(
   (ZipEntry)en.nextElement());
}

Далее, перебирая элементы массива zipEntries в цикле, мы извлекаем имена элементов и добавляем их в список, расположенный в окне диалогового окна:

int i = 0;
for (i = 0; i < zipEntries.size(); i++)
{
  ZipEntry ze =
   (ZipEntry)zipEntries.elementAt(i);
  l.add(ze.getName());
}

Метод itemStateChanged класса ZipFileDialog

Когда пользователь выделяет в списке строку имени элемента оглавления массива, управление передается методу itemStateChanged, реализованному как часть интерфейса ItemListener:

public void itemStateChanged(ItemEvent e)
{
  getZipFileEntryInfo();
}

Наша реализация этого метода вызывает метод getZipFileEntryInfo, извлекающий информацию из элемента оглавления архива и отображающий ее в главном окне приложения.

Метод getZipFileEntryInfo класса ZipFileDialog

Получив управление, метод getZipFileEntryInfo определяет номер элемента, выделенного пользователем в списке:

int nSelected = l.getSelectedIndex();
if(nSelected < 0)
  return;

Далее из массива zipEntries извлекается соответствующий элемент класса ZipEntry:

ZipEntry ze = (ZipEntry)zipEntries.elementAt(nSelected);

Перед тем как отобразить атрибуты данного элемента в главном окне приложения, мы очищаем расположенное там многострочное окно редактирования, выделяя в нем весь текст и заменяя пустой строкой:

((FrameWindow)par).ta.selectAll();
 ((FrameWindow)par).ta.replaceRange("", 0,  ((FrameWindow)par).ta.getSelectionEnd());

Затем мы формируем новый текст в переменной szItemInfo, записывая туда значения атрибутов, извлеченные из элемента оглавления соответствующими методами класса ZipEntry:

szItemInfo = "Entry name: " + ze.getName();
szItemInfo += "\nComment: " + ze.getComment();
szItemInfo += "\nCompressed size: " +
  ze.getCompressedSize();
szItemInfo += "\nSize: " + ze.getSize();
szItemInfo += "\nCRC-32: " + ze.getCrc();
szItemInfo += "\nExtra data: " + ze.getExtra();

szItemInfo += "\nCRC-32: " + ze.getCrc();
szItemInfo += "\nDate: " +
  DateFormat.getDateTimeInstance(
    DateFormat.FULL, DateFormat.FULL).format(
    new Date(ze.getTime()));
   
if(ze.getMethod() ==  ZipEntry.DEFLATED)
  szItemInfo += "\nMethod: DEFLATED";
     
else if(ze.getMethod() ==  ZipEntry.STORED)
  szItemInfo += "\nMethod: STORED";
   
if(ze.isDirectory())
  szItemInfo += "\n- DIRECTORY -";
else 
  szItemInfo += "\n- FILE -";

Затем полученная строка добавляется в редактор главного окна приложения:

((FrameWindow)par).ta.append(szItemInfo);

Метод actionPerformed класса ZipFileDialog

Когда пользователь нажимает кнопку Extract, расположенную в нижней части диалоговой панели со списком, метод actionPerformed извлекает из списка имя выделенного элемента архива и передает его методу saveZipFile:

public void actionPerformed(ActionEvent e)
{
  if(e.getSource().equals(bExtract))
  {
    String s = l.getSelectedItem();
    if(s != null)
      saveZipFile(s);
  }
  . . . 
}

Метод saveZipFile класса ZipFileDialog

В задачу метода saveZipFile входит извлечение из архива указанного файла и сохранение его на диске.

Прежде всего? мы выделяем из полного пути к файлу имя:

s = s.substring(s.lastIndexOf("/") + 1);

Это имя затем используется при инициализации панели выбора выходного класса:

FileDialog fdlg;
fdlg = new FileDialog(par, "Save file as...",
  FileDialog.SAVE);
fdlg.setFile(s);
fdlg.show();

if(fdlg.getDirectory() == null ||
  fdlg.getFile() == null)
  return;
     
String szPath = fdlg.getDirectory() +
  fdlg.getFile();

Таким образом, пользователю предлагается сохранить файл под тем именем, под которым он хранился в архиве. Далее метод saveZipFile записывает полный путь к сохраняемому файлу в переменной szPath.

На следующем этапе метод создает выходной поток, связанный с файлом:

int nSelected;
try
{
  FileOutputStream fos =
    new FileOutputStream(szPath);
  . . .
}
catch(Exception ex)
{
  System.out.println(ex.toString());
}

Узнав номер строки, выделенной пользователем в списке имен файлов, мы извлекаем из массива zipEntries соответствующий элемент оглавления класса ZipEntry:

nSelected = l.getSelectedIndex();
if(nSelected < 0)
  return;
      
ZipEntry ze = (ZipEntry)zipEntries.elementAt(nSelected);

Далее нам необходимо скопировать данные из архива в выходной файл fos. Чтобы это сделать, мы получаем ссылку на входной поток, связанный с объектом ZipFile:

InputStream is = zf.getInputStream(ze);

Копирование выполняется в цикле с применением методов read и write:

int nLength;
byte[] buf = new byte[8000];
while(true)
{
  nLength = is.read(buf);
  if(nLength < 0)
    break;
  fos.write(buf, 0, nLength);
}
is.close();
fos.close();

Создание архива формата ZIP

В этом разделе мы показали способ создания архива ZIP из файлов, расположенном в заданном каталоге, с применением классов ZipFile и ZipEntry.

Немного теории

В предыдущем разделе мы кратко рассказали о классах ZipFile и ZipEntry, предназначенных для работы с архивами формата ZIP и рассмотрели методику извлечения отдельных файлов из такого архива. Здесь мы продемонстрируем способ создания архивов ZIP.

Для того чтобы создать архив ZIP, Вам нужно вначале создать объект класса ZipOutputStream:

zos = new ZipOutputStream(new FileOutputStream(tempfile));

Этот объект является выходным потоком и может быть создан на базе потока FileOutputStream.

Далее при помощи метода setLevel класса ZipOutputStream нужно установить уровень упаковки:

zos.setLevel(Deflater.DEFAULT_COMPRESSION);

В качестве параметра методу setLevel передается целое число в диапазоне от 0 до 9 или константа Deflater.DEFAULT_COMPRESSION. В последнем случае используется уровень упаковки, принятый по умолчанию.

В классе Deflater определены и другие константы, определяющие степень упаковки:

Таблица 13-5. Константы определения степени упаковки

Константа

Описание

NO_COMPRESSION

Упаковка не выполняется

BEST_SPEED

Уровень упаковки, обеспечивающий максимальную скорость работы

BEST_COMPRESSION

Уровень, обеспечивающий максимальную степень упаковки

Следующий этап создания архива ZIP заключается в формировании объектов класса ZipEntry, соответствующих элементам оглавления архива. Каждый такой элемент необходимо записать в архив методом putNextEntry:

ZipEntry ze;
ze = new ZipEntry(szName);
zos.putNextEntry(ze);

Когда элемент оглавления для добавляемого файла записан в архив, следует выполнить копирование потока, связанного с добавляемым файлом в поток класса ZipOutputStream.

Указанная операция выполняется в цикле для всех добавляемых файлов.

Описание примера приложения DemoZip

Сразу после запуска наше приложение запрашивает с консоли путь к каталогу, расположенному на локальном диске компьютера. Когда пользователь введет этот путь, программа создает в рабочем каталоге архивный файл формата ZIP с именем !temp.zip и добавляет в него все файлы, расположенные в рабочем каталоге.

При этом для каждого файла выводится полный путь, исходный размер файла в байтах, размер файла после сжатия и процентное отношение последних двух величин:

Enter full path:
c:\temp
c:\temp\ZipFileDialog.class 7722 (3875) 50%
c:\temp\FrameWindow.class 3421 (1607) 54%
c:\temp\test.zip 654 (249) 62%
c:\temp\descr.htm 22124 (5986) 73%
c:\temp\ZipFileView.class 585 (382) 35%
c:\temp\ZipFileView.java 9053 (2204) 76%
c:\temp\ZipFileView.java.bak 167 (109) 35%
c:\temp\ZipFileView.map 124 (76) 39%
c:\temp\ZipFileView.prj 169 (111) 35%
c:\temp\ZipFileView.zip 8947 (8623) 4%

После обработки всех файлов, расположенных в указанном каталоге, программа завершает свою работу.

Рассмотрим исходный текст нашей программы (листинг 13-8).

Листинг 13-8 Вы найдете в файле chap13/DemoZip/DemoZip.java на прилагаемом к книге компакт-диске.

Метод main

Как мы уже говорили, путь к исходному каталогу наша программа запрашивает с консоли. Для этого мы используем метод getKbdString, определенный в нашем приложении:

String s;
System.out.println("Enter full path: ");
s = new String(getKbdString());

Строка полного пути к каталогу, возвращенная этим методом, сохраняется в переменной с именем s.

Прежде чем продолжить свою работу, метод main проверяет, что указанный путь относится к существующему каталогу:

File f = new File(s);
if(!f.exists())
{
  System.out.println("\nNot found: " + s);
  System.exit(0);
}
     
if(!f.isDirectory())
{
  System.out.println("\nNot directory: " + s);
  System.exit(0);
}

В противном случае работа программы завершается с сообщением об ошибке.

Если путь к каталогу указан правильно, метод main создает выходной поток для нового архивного файла с именем !temp.zip:

ZipOutputStream zos = createZipOutputStream("!temp.zip");

Для этого вызывается метод createZipOutputStream, определенный в нашем приложении. Данный метод возвращает ссылку на поток класса ZipOutputStream. Кроме того, метод main создает массив строк с именами файлов и каталогов, расположенных в указанном пользователем каталоге:

String[] sDirList = f.list();

Добавление файлов к архиву ZIP выполняется в цикле методом addFileToZip, определенным в нашем приложении:

int i;
try
{   
  for(i = 0; i < sDirList.length; i++)
  {
    File f1 = new File(s + File.separator + sDirList[i]);
       
    if(f1.isFile())
    {
      addFileToZip(zos, s + File.separator,  sDirList[i]);
    }
  } 
  zos.close();
}    
catch(Exception ex)
{
  System.out.println(ex.toString());
}

Этот метод вызывается только в том случае, если очередной элемент массива содержит имя файла, а не каталога. Через первый параметр методу addFileToZip передается ссылка на выходной поток класса ZipOutputStream, через второй — путь к каталогу с файлами, а через третий — имя добавляемого файла.

После завершения цикла добавления файлов метод main закрывает выходной поток класса ZipOutputStream. На этом работа программы завершается.

Метод createZipOutputStream

Метод createZipOutputStream предназначен для создания выходного потока класса ZipOutputStream, привязанного к файлу. В качестве единственного параметра этому методу передается путь к создаваемому файлу архива ZIP:

static ZipOutputStream
    createZipOutputStream(String szPath)
{
  . . .
}

Все операции выполняются методом внутри блока try-catch, так как они могут вызвать появление исключений.

Вначале метод createZipOutputStream создает для файла, путь к которому был передан через параметр, объект класса File:

File tempfile; 
ZipOutputStream zos = null;
   
try
{   
  tempfile = new File(szPath);
  . . .
}  
catch(Exception ex)
{
  System.out.println(ex.toString());
}

Далее с помощью соответствующего конструктора создается поток класса ZipOutputStream:

zos = new ZipOutputStream(new FileOutputStream(tempfile));

Для этого потока мы устанавливаем уровень упаковки, принятый по умолчанию:

zos.setLevel(Deflater.DEFAULT_COMPRESSION);

Затем метод createZipOutputStream возвращает ссылку на созданный выходной поток:

return zos;

Метод addFileToZip

Прототип метода addFileToZip представлен ниже:

static void addFileToZip(ZipOutputStream zos,
  String szPath, String szName)
  throws Exception
{
  . . .
}

Как мы уже говорили, через первый параметр методу addFileToZip передается ссылка на выходной поток класса ZipOutputStream, через второй — путь к каталогу с файлами, а через третий — имя добавляемого файла. С помощью ключевого слова throws мы указали, что в процессе работы этого метода могут возникать исключения, которые необходимо обработать в вызывающем методе.

Свою работу метод addFileToZip начинает с того, что выводит на консоль путь к файлу, который добавляется в архив:

System.out.print(szPath + szName);

Далее для добавляемого файла мы создаем новый элемент оглавления (как объект класса ZipEntry) и записываем его в выходной поток zos класса ZipOutputStream методом putNextEntry:

ZipEntry ze;
ze = new ZipEntry(szName);
zos.putNextEntry(ze);

Теперь нужно скопировать в выходной поток zos сам добавляемый файл. Эта операция выполняется в цикле:

FileInputStream fis = new FileInputStream(szPath + szName);
 
byte[] buf = new byte[8000];
int nLength;
while(true)
{
  nLength = fis.read(buf);
  if(nLength < 0)
    break;
  zos.write(buf, 0, nLength);
}

После завершения цикла необходимо закрыть поток, связанный с добавляемым файлом и новый элемент оглавления:

fis.close();
zos.closeEntry();

Теперь элемент оглавления полностью сформирован и записан в выходной поток zos, связанный с архивом. Мы извлекаем из этого элемента сведения об исходном размере файла и размере файла после сжатия, а затем отображаем эту информацию на консоли:

long nSize = ze.getSize();
long nCompressedSize = ze.getCompressedSize();
   
long nPercent = 100 - ((nCompressedSize * 100) / nSize);
   
System.out.println(" " + nSize + " (" +
  nCompressedSize + ") " + nPercent + "%");

Далее метод addFileToZip возвращает управление вызвавшему его методу.

Распаковка архива формата ZIP

В этом разделе мы показали способ распаковки архива ZIP, путь к которому вводится через консоль. Распакованное дерево каталогов будет расположено в заданном каталоге. При этом используются классы ZipFile и ZipEntry.

Немного теории

Классы ZipFile и ZipEntry позволяют организовать распаковку существующих архивов формата ZIP.

Как она выполняется?

Прежде всего, Вы должны создать объект класса ZipFile, передав соответствующему конструктору путь к файлу, например:

ZipFile zf;
zf = new ZipFile(szZipFilePath);

Далее Вы получаете перечисление элементов оглавления архива методом entries:

Enumeration en = zf.entries();

Пользуясь этим перечислением, нетрудно организовать извлечение всех элементов оглавления для последующей записи в массив:

while(en.hasMoreElements())
{
  zipEntries.addElement((ZipEntry)en.nextElement());
}

Перебирая в цикле элементы оглавления, Вы должны открыть для каждого такого элемента, не являющегося каталогом, входной поток класса InputStream. Такая операция может быть выполнена методом getInputStream, определенным в классе ZipFIle:

ZipEntry ze;
InputStream is = zf.getInputStream(ze);

Далее Вы открываете для очередного извлекаемого файла выходной поток класса FileOutputStream и выполняете копирование из потока is в этот выходной поток.

Обрабатывая элементы оглавления архива, Вы должны учитывать следующие моменты:

·         во-первых, имя элемента оглавления может содержать путь к файлу. В качестве разделителя каталогов в этом пути применяется символ «/», который перед созданием выходного каталога следует заменить символом File.separatorChar. Это нужно сделать, так как на разных платформах применяются разные символы-разделители;

·         во-вторых, наряду с элементами, описывающими файлы, в оглавлении могут встречаться элементы с описанием каталогов. Имя таких элементов оканчивается символом «\». Вы можете их или игнорировать, или использовать при создании дерева каталогов

Описание примера приложения DemoUnzip

После запуска наше приложение запрашивает с консоли путь к исходному файлу архива ZIP и путь к каталогу, куда нужно записать извлеченные из архива файлы. Затем программа извлекает файлы и записывает их в указанный каталог, создавая при необходимости дерево вложенных каталогов.

В ходе этого процесса на консоль выводятся пути к создаваемым файлам, их полная длина и длина после сжатия:

Enter full path to Zip-file:
c:\temp\AppletFrame.zip
Enter path to extract files:
c:\temp
TEMP\AppletFrame\AppletFrame.tmp.html 307 (178)
TEMP\AppletFrame\AppletFrame.java.bak 904 (339)
TEMP\AppletFrame\AppletFrame.java 1186 (477)
TEMP\AppletFrame\AppletFrame.zip 2913 (2913)
TEMP\AppletFrame\AppletFrame.htm 3683 (1573)
TEMP\AppletFrame\AppletFrame.map 83 (56)
TEMP\AppletFrame\AppletFrame.prj 455 (235)
TEMP\AppletFrame\FrameWindow.class 1219 (727)
TEMP\AppletFrame\AppletFrame.class 998 (562)
Done!

Рассмотрим исходный текст приложения (листинг 13-9).

Листинг 13-9 Вы найдете в файле chap13/DemoUnzip/DemoUnzip.java на прилагаемом к книге компакт-диске.

Метод main

Получив управление, метод main запрашивает у пользователя два пути — к исходному файлу ZIP и к выходному каталогу. После проверки эти пути сохраняются, соответственно, в переменных szZipFilePath и ExtractPath.

Далее метод main создает объект класса ZipFile, получает перечисление элементов оглавления и сохраняет их в массиве zipEntries:

ZipFile zf;
Vector zipEntries = new Vector();
      
try

  zf = new ZipFile(szZipFilePath);   
  Enumeration en = zf.entries();
     
  while(en.hasMoreElements())
  {
    zipEntries.addElement((ZipEntry)en.nextElement());
  }
  . . .
}
catch(Exception ex)
{
  System.out.println(ex.toString());
}

Затем элементы массива zipEntries обрабатываются в цикле:

int i;
for (i = 0; i < zipEntries.size(); i++)
{
  ZipEntry ze = (ZipEntry)zipEntries.elementAt(i);
         
  extractFromZip(szZipFilePath, szExtractPath,
    ze.getName(), zf, ze);
}

Мы передаем каждый такой элемент как объект класса ZipEntry методу extractFromZip, определенному в нашем приложении (через последний параметр). Через первый параметр этот метод получает путь к исходному файлу архива ZIP, через второй — путь, куда нужно записать извлеченный файл, через третий — имя файла, а через четвертый — ссылку на объект класса ZipFile.

После завершения цикла метод main закрывает объект zf класса ZipFile и выводит на консоль сообщение о завершении работы:

zf.close();
System.out.println("Done!");

Метод extractFromZip

Прежде всего, метод extractFromZip проверяет, не является ли переданный ему элемент описателем каталога:

if(ze.isDirectory())
  return;

Если является, то метод просто возвращает управление для перехода к следующему элементу. В том случае, когда элемент описывает файл, метод extractFromZip выполняет замену разделителей «\» на File.separatorChar, вызывая для этого метод slash2sep, определенный в нашем приложении:

String szDstName = slash2sep(szName);

Замена выполняется в имени файла, извлеченного из элемента оглавления архива. Напомним, что это имя может содержать путь в виде списка каталогов с разделителями.

На следующем этапе метод extractFromZip отрезает от этого пути имя файла:

String szEntryDir;
   
if(szDstName.lastIndexOf(File.separator) != -1)
{
  szEntryDir = szDstName.substring(0,
      szDstName.lastIndexOf(File.separator));
}
else    
  szEntryDir = "";

В итоге в переменной szEntryDir будет записан путь к каталогу, куда нужно скопировать извлеченный файл. Если в имени извлекаемого файла нет каталогов и разделителей, файл будет записан в каталог, указанный пользователем с консоли.

Имя файла, извлеченное из элемента оглавления и исправленное (с замененными символами-разделителями), выводится на консоль:

System.out.print(szDstName);

Туда же мы выводим размер исходного и сжатого файла:

long nSize = ze.getSize();
long nCompressedSize = ze.getCompressedSize();
   
System.out.println(" " + nSize + " (" +
  nCompressedSize + ")");

Далее мы создаем дерево каталогов для записи извлекаемого файла:

try
{
  File newDir = new File(szExtractPath +
    File.separator + szEntryDir);
        
  newDir.mkdirs();
  . . .
}  
catch(Exception ex)
{
  System.out.println(ex.toString());
  System.exit(0);
}

Путь к каталогу образуется путем слияния пути для распаковки, заданного с консоли, и пути, извлеченного из имени файла по элементу оглавления.

Чтобы извлечь файл, мы создаем два потока:

FileOutputStream fos =
  new FileOutputStream(szExtractPath +
  File.separator + szDstName);
InputStream is = zf.getInputStream(ze);

Поток fos связан с извлекаемым файлом, а поток is — с элементом оглавления архива.

Копирование данных выполняется в цикле:

byte[] buf = new byte[1024];
int nLength;
      
while(true)
{
  try
  {
    nLength = is.read(buf);
  }     
  catch (EOFException ex)
  {
    break;
  } 
        
  if(nLength < 0)
    break;

  fos.write(buf, 0, nLength);
}

Если в процессе извлечения файла из архива происходит исключение EOFException или достигается конец потока, цикл копирования завершает свою работу. Далее мы закрываем потоки:

is.close();
fos.close();

Метод slash2sep

Этот метод предназначен для замены символа-разделителя «/», применяющегося в архивах формата ZIP, на символ File.separatorChar. Такая замена необходима для обеспечения нормальной работы нашего приложения на различных компьютерных платформах.

Замена выполняется простым циклическим просмотром исходной строки, преданной методу в качестве параметра:

static String slash2sep(String src)
{
  int i;
  char[] chDst = new char[src.length()];
  String dst;
   
  for(i = 0; i < src.length(); i++)
  {
    if(src.charAt(i) == '/')
      chDst[i] = File.separatorChar;
    else
      chDst[i] = src.charAt(i);
  }
  dst = new String(chDst);
  return dst;
}

Здесь в цикле на базе исходной строки формируется массив символов chDst, который затем преобразуется в строку класса String и возвращается вызывающему методу.

Вычисление контрольной суммы файла

В примере демонстрируется использование библиотеки классов java.util.zip для вычисления контрольной суммы файла по методам CRC-32 и Adler-32.

Немного теории

Иногда перед программистом встает задача вычисления контрольной суммы файла. Такая сумма может потребоваться, например, чтобы убедиться в сохранности файла при его передаче по сети или при его хранении в небезопасных условиях.

Разумеется, Вы можете сами организовать подсчет контрольной суммы, например, просто складывая последовательности четырехбайтовых слов по модулю два или с применением какого-либо иного алгоритма. В библиотеке классов java.util.zip есть классы и интерфейсы, намного упрощающие данную задачу. Это классы CRC32, Adler32 и интерфейс Checksum.

Интерфейс Checksum

Интерфейс Checksum определяет методы, применяющиеся при подсчете контрольной суммы. Это методы update, getValue и reset.

Метод update существует в виде двух реализаций:

public abstract void update(int b);
public abstract void update(byte b[],int off, int len);

Первая из них обновляет контрольную сумму значением одного байта, а вторая — значениями массива байт.

Метод getValue возвращает текущее значение контрольной суммы:

public abstract long getValue();

Метод reset сбрасывает значение контрольной суммы в исходное состояние:

public abstract void reset();

Классы CRC32 и Adler32

Классы CRC32 и Adler32 предоставляют в ваше распоряжение две различные готовые реализации интерфейса Checksum, использующие 32-разрядное суммирование. Первый из них реализует алгоритм CRC-32, а второй Adler-32 (более быстродействующий).

Для того чтобы организовать в своем приложении вычисление контрольной суммы файла с применением классов CRC32 или Adler32, Вам вначале нужно создать объект одного из этих классов, воспользовавшись для этого соответствующим конструктором. Заметим, что конструкторы этих классов не имеют параметров.

Далее Вам нужно создать входной поток для исследуемого файла и организовать его чтение блоками в цикле. На каждой итерации цикла необходимо вызывать метод update класса CRC32 или Adler32.

После завершения цикла искомая контрольная сумма может быть получена методом getValue, определенным в этих классах.

Описание примера приложения GetChecksum

Наше приложение позволяет выбирать файлы с помощью стандартного диалогового окна класса FileDialog, определяя для них контрольные суммы с применением алгоритмов CRC-32 и Adler-32.

Путь к файлу и значения контрольных сумм отображаются в окне многострочного редактора текста, расположенного в главном окне приложения (рис. 13-6).

Рис. 13-6. Просмотр контрольных сумм файлов

Рассмотрим наиболее важные фрагменты исходного текста приложения (листинг 13-10).

Листинг 13-10 Вы найдете в файле chap13/GetChecksum/GetChecksum.java на прилагаемом к книге компакт-диске.

Когда пользователь выбирает из меню File строку Open, управление передается методу actionPerformed. Данный метод создает и отображает на экране диалоговое окно выбора входного файла, получает путь к выбранному пользователем файлу, сохраняет его в переменной szPath и показывает в окне приложения:

if(e.getSource().equals(miOpen))
{
  fdlg = new FileDialog(this, "Open file", FileDialog.LOAD);
  fdlg.show();
     
  String szPath = fdlg.getDirectory() + fdlg.getFile();
     
  ta.append("Open: " + szPath  + "\n");
  . . .
}

Далее мы два раза вызываем метод calculate, определенный в нашем приложении, который возвращает текстовое представление значения контрольной суммы:

ta.append("CRC-32: " + calculate(szPath, new CRC32()) + "\n");
ta.append("Adler-32: " + calculate(szPath, new Adler32()) + "\n");

Через первый параметр этому методу передается путь к файлу, а через второй — ссылка на класс, определяющий метод вычисления контрольной суммы.

Как устроен метод calculate?

Прежде всего, этот метод открывает входной поток для файла, контрольную сумму которого необходимо вычислить:

String s = "";
byte[] buf = new byte[8000];
int nLength = 0;
   
try
{
  FileInputStream fis = new FileInputStream(szPath);
  . . .
}

Далее мы организуем чтение из потока в цикле блоками по 8000 байт (значение, выбранное нами произвольно), с периодическим обновлением контрольной суммы методом update:

while(true)
{
  nLength = fis.read(buf);
  if(nLength < 0)
    break;
         
  cs.update(buf, 0, nLength);
}

После достижения конца файла связанный с ним поток закрывается, а значение контрольной суммы извлекается методом getValue:

fis.close();
. . .
s = new Long(cs.getValue()).toString();
return s;

Мы преобразуем его в текстовую строку класса String и возвращаем вызвавшему методу.

Полупрозрачные изображения

При работе с изображениями приложения Java могут задавать для пикселов такую характеристику, как прозрачность. Наложением полупрозрачных изображений можно получит оригинальные визуальные эффекты. В примере аплета AlphaDemo, описанного в этом разделе, показывается техника наложения полупрозрачного изображения на непрозрачное изображение.

Немного теории

Те из Вас, кто занимался подготовкой графических иллюстраций для размещения на сервере Web, знает о так называемых прозрачных изображениях GIF. С помощью специального редактора Вы можете указать, что один из цветов в изображении должен быть прозрачным. Прозрачное изображение обычно размещают над другим изображением или фоном для достижения эффекта наложения.

В процессе создания изображения задействованы два объекта: ImageProducer и ImageConsumer. Первый из них является интерфейсом для тех объектов, которые поставляют данные для изображений, а второй — интерфейсом для получения и преобразования данных.

Библиотека классов AWT позволяет встроить фильтр в поток данных, передаваемых из ImageProducer в ImageConsumer. В результате Вы сможете изменять изображения «на лету», получая интересные эффекты.

Фильтр может выполнить обработку всех или отдельных пикселов изображения, изменив их цвет или прозрачность. И, что немаловажно, создание и подключение фильтра не является чрезвычайно сложной задачей.

Вот как выглядит такой фильтр:

class SomeMyOwnImageFilter
  extends RGBImageFilter
{
  public int filterRGB(int x, int y, int nRGB)
  {
    int nA = (nRGB >> 255) & 0xff;
    int nR = (nRGB >> 16)  & 0xff;
    int nG = (nRGB >> 8)   & 0xff;
    int nB = nRGB & 0xff;
   
    . . .
   
    return((nA >> 255) | (nR << 16) |
      (nG << 8) | nB);
  }
}

В процессе преобразования изображения для каждого пиксела вызывается метод filterRGB. Через параметры ему передаются координаты (параметры x и y), значения цвета и прозрачности (параметр nRGB). Метод filterRGB должен вернуть новое значение прозрачности и цвета данного пиксела.

В процессе преобразования Вы можете проверять координаты пикселов или игнорировать их, а также выполнять произвольные преобразования значений цветовых компонент и прозрачности, лежащих в диапазоне от 0 до 255.

Теперь о том, как подключить фильтр.

Рассмотрим следующую ситуацию. Пусть, например, мы создаем фильтр для преобразования некоего изображения imgSrc в изображение imgResult.

Вначале необходимо создать новый фильтр как объект:

SomeMyOwnImageFilter someFilter =
  new SomeMyOwnImageFilter();

Далее нужно создать объект класса ImageProducer:

ImageProducer ip = new FilteredImageSource(imgSrc.getSource(), someFilter);

Этот объект создается при помощи конструктора класса FilteredImageSource. В качестве первого параметра мы передаем конструктору ссылку на источник данных исходного изображения, полученный методом getSource, а через второй — ссылку на свой фильтр.

Затем мы можем создать новое изображение методом createImage, как это показано ниже:

Image imgResult = createImage(ip);

Чтобы дождаться процесса завершения формирования изображения imgResult, используйте класс MediaTracker:

MediaTracker mt = new MediaTracker(this);
mt.addImage(imgResult, 0); 
   
try
{
  mt.waitForAll();
}
catch(InterruptedException ie)
{
}

Теперь изображение imgResult готово и доступно для рисования методом drawImage.

Фильтры, созданные на базе интерфейса RGBImageFilter, могут управлять прозрачностью более тонко. За прозрачность пиксела отвечает поле nA. Оно может принимать значения от 0 (полная прозрачность) до 255 (полная непрозрачность).

С помощью фильтра Вы можете сделать прозрачным несколько цветов в изображении, а также создать полупрозрачные изображения. Все это значительно увеличивает возможности дизайнеров, занимающихся оформлением страниц серверов Web.

Чтобы сделать все пикселы полупрозрачными, достаточно в теле метода filterRGB установить значение переменной nA, равное 128. Указывая другие значения, Вы можете устанавливать различную прозрачность пикселов.

Описание примера приложения AlphaDemo

В окне нашего аплета одно изображение (рис. 13-9) накладывается на другое (рис. 13-8). Результат такого наложения показан на рис. 13-7.

Рис. 13-7. Два наложенных изображений в окне аплета

Рис. 13-8. Изображение, которое рисуется в окне непрозрачным

Рис. 13-9. Это изображение преобразуется в полупрозрачное

Рассмотрим исходный текст аплета (листинг 13-11).

Листинг 13-11 Вы найдете в файле chap13/AlphaDemo/AlphaDemo.java на прилагаемом к книге компакт-диске.

Главный класс аплета AlphaDemo

В главном классе нашего аплета мы определили два поля с именами img1 и img2:

import java.awt.*;
import java.applet.*;
import java.awt.image.*;

public class AlphaDemo extends Applet
{
  Image img1;
  Image img2;
  . . .
}

Первое из них предназначено для хранения ссылки на изображение, показанное на рис. 13-8, а второе — для хранения ссылки на изображение, показанное на рис. 13-9.

Метод init

При инициализации аплета метод init загружает оба изображения:

img1 = getImage(getCodeBase(), "img1.jpg");
img2 = getImage(getCodeBase(), "img2.jpg");

Чтобы дождаться завершения процесса загрузки, мы использовали класс MediaTracker:

MediaTracker mt = new MediaTracker(this);
mt.addImage(img1, 0); 
mt.addImage(img2, 0); 
try
{
  mt.waitForAll();
}
catch(InterruptedException ie)
{
  return;
}

Метод paint

Исходный текст этого метода предельно прост:

public void paint(Graphics g)
{
  g.drawImage(img1, 0, 0, this);
  g.drawImage(imgToTransparent(img2), 0, 0, this);
}

Здесь мы вначале рисуем первое изображение, а затем второе. Второе изображение перед рисованием преобразуется в полупрозрачное изображение методом imgToTransparent, определенным в нашем приложении.

Метод imgToTransparent

Метод imgToTransparent получает через свой единственный параметр исходное изображение, делает его полупрозрачным и возвращает результат как объект класса Image:

Image imgToTransparent(Image imgSrc)
{
  . . .
}

Прежде всего, метод imgToTransparent создает фильтр класса TransparentImageFilter и объект класса ImageProducer:

TransparentImageFilter trFilter =
  new TransparentImageFilter();
     
ImageProducer ip = new FilteredImageSource(
  imgSrc.getSource(), trFilter);

Новое полупрозрачное изображение создается методом createImage:

Image imTransparent = createImage(ip);

Чтобы дождаться завершения формирования полупрозрачного изображения мы создаем объект класса MediaTracker:

MediaTracker mt = new MediaTracker(this);
mt.addImage(imTransparent, 0); 
   
try
{
  mt.waitForAll();
}
catch(InterruptedException ie)
{
  return null;
}

Если преобразование завершилось без ошибок, метод imgToTransparent возвращает ссылку на созданное изображение:

return imTransparent;

Класс TransparentImageFilter

В классе TransparentImageFilter мы определили метод filterRGB:

class TransparentImageFilter
  extends RGBImageFilter
{
  public int filterRGB(int x, int y, int nRGB)
  {
    int nAlpha = 0x80;
    return((nAlpha << 24) | (nRGB & 0xffffff));
 
}
}

Единственное, что делает наш фильтр, это устанавливает для параметра nAlpha значение 0x80. Цветовые компоненты пикселов не изменяются.

[Назад] [Содержание] [Дальше]