Аплет с формой регистрации посетителей Поле Middle name и метка этого поля Конструктор класса AppletMsgBox Взаимодействие приложений Java и расширений сервера Web Аплет для передачи номера кредитной карточки Отправка данных расширению сервера Web Размещение аплета в документе HTML Исходный текст расширения ISAPI Передача параметров странице ASP Определение размера несжатого файла. Описание примера приложения ZipFileView Метод updateList класса ZipFileDialog Метод itemStateChanged класса ZipFileDialog Метод getZipFileEntryInfo класса ZipFileDialog Метод actionPerformed класса ZipFileDialog Метод saveZipFile класса ZipFileDialog Описание примера приложения DemoZip Описание примера приложения DemoUnzip Вычисление контрольной суммы файла Описание примера приложения GetChecksum Описание примера приложения AlphaDemo Главный класс аплета AlphaDemo
Последняя глава нашей книги посвящена использованию аплетов Java в приложениях Интернета. Как Вы знаете, программы, написанные на языке программирования Java, способны работать практически на всех распространенных компьютерных платформах. В результате одна из разновидностей таких программ, а именно аплеты Java, получили широкое распространение на страницах серверов Web. Именно так реализуется главное преимущество аплетов Java — совместимость с различными платформами, так как посетители этих страниц могут запускать свои браузеры под управлением различных операционных систем. Заметим, однако, что в большинстве случаев аплеты Java используются только для достижения всевозможных визуальных эффектов, создания «интеллектуальных» графических ссылок, меню и т. д. Между тем, они способны решать и более сложные задачи, такие, как непосредственное взаимодействие с расширениями сервера Web в виде приложений CGI и ISAPI и передача параметров страницам ASP. Мы рассмотрим именно эти вопросы, не нашедшие, на наш взгляд, достойного отражения в многочисленных книгах, посвященных приложениям Java. Тем из Вас, кто еще никогда не создавал программ на языке Java, мы предлагаем наше руководство, размещенное на сервере создателя этого языка Sun Microsystems по адресу http://www.sun.ru/java/books/online/index.html. В наших примерах мы покажем, как создавать аплеты с формами, позволяющими вводить информацию о кредитных карточках. Эти формы содержат такие элементы управления, как текстовые поля и кнопки. Начинающих программистов, особенно тех, кто создавал приложения для Windows или OS/2, может шокировать способ, которым в приложениях Java выполняется размещение компонентов и контейнеров внутри окна. Самая большая и неприятная на первый взгляд особенность заключается в невозможности размещения компонентов с указанием точных координат (хотя с применением специальной техники это все же достижимо). Другая особенность — программы Java не имеют ресурсов, подобных ресурсам исполнимых файлов Windows и описывающих диалоговые панели или элементы управления. Внешний вид пользовательского интерфейса определяется динамически во время выполнения программы. Поясним, в чем тут дело и для чего нужно преодолевать такие трудности. Создавая приложения Java, никогда не следует забывать о том, что они предназначены для работы на различных компьютерных платформах. При этом Вы не можете полагаться на то, что Вам будет доступен какой-либо конкретный шрифт, кнопки или другие компоненты будут иметь определенный размер или форму, а видеоадаптер будет работать в режиме с каким-либо заданным или заранее известным разрешением. Для того чтобы обеспечить работу приложений Windows в режимах с различным разрешением видеоадаптера, размеры элементов управления «привязываются» к размерам системного шрифта. Однако указанный способ недостаточно универсален для применения на различных платформах, так как в разных операционных системах, вероятно, эта «привязка» будет выполняться по-разному. Кроме того, теоретически системный шрифт в какой-нибудь операционной системе может отсутствовать как таковой. С другой стороны, динамическое формирование внешнего вида пользовательского интерфейса во время работы программы позволит адаптировать его «на ходу» к особенностям конкретной операционной системы. Для этого приложения Java используют достаточно гибкую и мощную систему управления размещением компонентов и контейнеров с названием Layout Manager. Система Layout Manager способна работать в нескольких основных режимах, отличающихся различными стратегиями размещения компонентов, определения их размеров и выравнивания. В самом простом режиме FlowLayout компоненты добавляются в окно контейнера с применением следующего алгоритма. Каждый новый добавленный компонент располагается вслед за предыдущим в направлении слева направо и сверху вниз, при этом выполняется центровка компонентов по горизонтали. Одной из особенностей данного режима является возможное изменение взаимного расположения добавленных компонентов при изменении размеров контейнера. Установка режима FlowLayout выполняется при помощи метода setLayout, как это показано ниже: setLayout(new FlowLayout()); Далее компоненты добавляются в окно контейнера методом add, например: TextField tf; Когда установлен режим размещения GridLayout, все компоненты располагаются в ячейках таблицы, имеющей заданное количество строк и столбцов. Размеры компонентов изменяются таким образом, чтобы они полностью занимали свои ячейки. Установка режима GridLayout выполняется при помощи метода setLayout, как это показано ниже: setLayout(new GridLayout()); Далее компоненты добавляются в окно контейнера методом add, например: TextField tf; Режим BorderLayout предполагает разделение окна контейнера на рамку и центральную часть. Методу add при этом указывается направление от центра окна, в котором следует размещать компоненты. Направление указывается следующим образом: add("Center", btn1); // центр Здесь мы добавили в окно контейнера компоненты btn1, …, btn5. При этом компонент btn1 располагается в центре окна контейнера, а остальные компоненты — по бокам. Размеры компонентов изменяются таким образом, чтобы они полностью заполняли контейнер. Заметим, что Вы не обязаны каждый раз добавлять в контейнер именно пять компонентов и задействовать при этом все возможные направления. Режим размещения CardLayout предназначен для поочередного размещения нескольких компонентов в одном контейнере (например, класса Panel). При добавлении компонента в контейнер необходимо передать его имя методу add через первый параметр, например: picFrame pf; Остальные компоненты добавляются аналогичным образом. В классе CardLayout предусмотрено несколько методов, предназначенных для выбора отображаемого компонента. Эти методы перечислены в таблице 13-1. Таблица 13-1. Методы для выбора компонента
Всем указанным методам, кроме метода show, передается через единственный параметр ссылка на родительский контейнер, в котором выполняется размещение. Методу show через второй параметр дополнительно передается имя компонента (как строка класса String). Последний режим размещения системы LayoutManager — это режим GridBagLayout. Он считается наиболее трудным, однако по сравнению с другими режимами он очень гибкий. В ряде случаев Вам просто не обойтись без него. Так же как и рассмотренный нами ранее режим GridLayout, режим GridBagLayout предполагает размещение компонентов в ячейках некоторой таблицы заданной размерности. Вот наиболее важные отличия между этими режимами: · в режиме GridLayout размещаемые компоненты изменяют свои размеры таким образом, чтобы заполнить ячейки таблицы, в которых они располагаются. Режим GridBagLayout позволяет контролировать этот процесс, причем при необходимости Вы можете задать стратегию такого изменения или отказаться от него вовсе; · в режиме GridLayout каждый компонент занимает только одну ячейку. Что же касается режима GridBagLayout, то здесь компоненты могут занимать несколько смежных ячеек в строках или столбцах; · при изменении размеров контейнера во время работы приложения при использовании режима GridLayout все компоненты неизбежно изменяют свои размеры. Это далеко не всегда удобно. В режиме GridBagLayout Вы можете управлять стратегией изменения размеров компонентов или отказаться от такого изменения. Режим размещения компонентов GridBagLayout удобен для создания диалоговых панелей, содержащих такие компоненты, как текстовые поля редактирования, переключатели, кнопки и т. д. Выбирая соответствующим образом параметры размещения отдельных компонентов путем заполнения соответствующих полей класса GridBagConstraints, можно создавать панели, напоминающие по своему внешнему виду и поведению стандартные диалоговые панели Windows или других операционных систем с графическим интерфейсом. При этом можно добиться, чтобы размеры компонентов и их взаимное расположение не изменялись при корректировке размеров окна контейнера. Это невозможно при работе в других режимах размещения, таких, как FlowLayout или GridLayout. Как пользоваться режимом размещения GridBagLayout? Схема достаточно проста. Прежде всего Вы должны создать объект класса GridBagLayout при помощи конструктора и выбрать его, как это показано ниже: GridBagLayout gbl = new
GridBagLayout(); Далее Вам нужно создать объект класса GridBagConstraints, поля которого будут определять параметры размещения отдельных компонентов: GridBagConstraints c = new GridBagConstraints(); Далее Вам нужно задать значения полей объекта класса GridBagConstraints, например, так (позже мы расскажем о назначении отдельных полей): c.anchor = GridBagConstraints.NORTH; Подготовив объект класса GridBagConstraints, Вам нужно установить его в системе Layout Manager методом setConstraints и добавить очередной компонент в окно контейнера методом add: tf = new TextField(30); Далее описанная процедура выполняется над всеми остальными добавляемыми компонентами, причем объект класса GridBagConstraints можно не создавать каждый раз заново, а использовать повторно. Но если все так просто, то в чем же тогда сложность работы с режимом размещения GridBagLayout? Очевидно, дело в выборе значений параметров объекта класса GridBagConstraints. Перечислим эти поля и дадим их краткую характеристику. Полную информацию Вы найдете в документации JDK. Поля 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 определяет стратегию распределения свободного пространства ячейки (или ячеек) таблицы для компонента, если его размеры меньше размеров выделенного для него места. Возможные значения приведены в таблице 13-2. Таблица 13-2. Значения поля fill
Поле anchor задает выравнивание компонента внутри отведенного для него пространства. Он включается в работу, когда размеры компонента меньше размеров выделенного для него места. Для поля anchor Вы можете указать значения, приведенные в таблице 13-3. Таблица 13-3. Значения поля anchor
Поля weightx и weighty Эти поля определяют стратегию изменения размеров компонента, отвечая за выделение пространства для столбцов (weightx) и строк (weighty). Если записать в них нулевые значения, все добавленные компоненты займут место в центре контейнера и будут выровнены по центру (как по вертикали, так и по горизонтали). Чтобы размеры компонента изменялись по горизонтали или вертикали, в поля weightx и weightx нужно записать значения от 0,0 до 1,0. Если в столбце несколько компонентов, то его ширина определяется компонентом с максимальным значением weightx. Аналогичное утверждение верно и для строк. Заметим, что дополнительное пространство добавляется к строкам и столбцам снизу и справа соответственно. В полях ipadx и ipady Вы можете указать, что размеры компонента необходимо увеличить на заданное количество пикселов по горизонтали и вертикали соответственно. Поле insets позволяет задать для компонента отступы от краев выделенной ему области. По умолчанию такие отступы отсутствуют. В поле insets необходимо записать ссылку на объект класса Insets, созданную соответствующим конструктором. Этот конструктор имеет следующий прототип: public Insets( Аплет с формой регистрации посетителей В качестве примера демонстрации режима размещения GridBagLayout приведем исходные тексты аплета GridBag .с формой В окне нашего аплета находится форма для регистрации посетителей сервера, в которой нужно заполнить несколько стандартных полей (рис. 13-1).
Рис. 13-1. Форма для регистрации посетителей Если Вы заполните форму и щелкнете кнопку OK, на экране появится диалоговая панель, отображающая введенные значения в окне многострочного редактора (рис. 13-2). Кнопка Cancel удаляет введенную информацию.
Рис. 13-2. Отображение введенной информации Как в окне основного аплета, так и в окне диалоговой панели мы установили режим размещения компонент GridBagLayout. Перейдем к рассмотрению исходного текста аплета. Полностью он приведен в листинге 13-1. Листинг 13-1 Вы найдете в файле chap13/Gridbag/GridBag.java на прилагаемом к книге компакт-диске. Главный класс аплета GridBag создан на базе класса Applet и реализует интерфейс ActionListener: import java.applet.Applet; Данный интерфейс необходим для обработки событий, вызываемых нажатием кнопок. В главном классе мы определили несколько полей, предназначенных для хранения ссылок на компоненты — текстовые поля, метки класса Label и кнопки: TextField tfFirstName; Этот метод получает управление при инициализации аплета. Он создает и размещает все компоненты в окне аплета, а затем регистрирует обработчики событий от кнопок. Компоненты создаются обычным образом при помощи соответствующих конструкторов: tfFirstName = new TextField(20); Далее мы устанавливаем режим размещения GridBagLayout и создаем объект класса GridBagConstraints, необходимый для задания параметров размещения отдельных компонент: GridBagLayout gbl = new
GridBagLayout(); Ниже мы расскажем о выборе этих параметров. Поле First name Заполнение параметров и размещение этого поля выполняется следующим образом: c.anchor
= GridBagConstraints.NORTHWEST;
В параметре anchor мы указываем, что выравнивание поля следует выполнять в направлении вверх влево, поэтому поле будет прижато к верхнему левому углу контейнера. Параметр fill имеет значение GridBagConstraints.NONE, а значит, при корректировке размеров контейнера размеры поля изменяться не будут. Так как значение полей gridheight и gridwidth равно единице, поле занимает одну ячейку таблицы. Поля gridx и gridy содержат значение GridBagConstraints.RELATIVE, поэтому добавление поля выполняется в направлении слева направо и сверху вниз. И наконец, поле insets задает отступы сверху и слева, равные 10 пикселам. Для этой метки мы используем те же параметры, что и для самого поля: gbl.setConstraints(lbFirstName, c); В результате метка займет положение справа от поля First name. Вот как заполняются параметры размещения для кнопки OK: c.gridwidth =
GridBagConstraints.REMAINDER; Как видите, параметр gridwidth имеет значение, равное GridBagConstraints.REMAINDER. В результате кнопка будет последним компонентом в первой строке. Ее размеры останутся неизменными при корректировке размеров контейнера, так как поле fill имеет значение GridBagConstraints.NONE. Чтобы несколько увеличить размеры кнопки OK по горизонтали, мы задали в поле ipadx значение, равное 32 пикселам. Поле Middle name и метка этого поля Перед добавлением поля и его метки мы восстанавливаем параметры ipadx и gridwidth, измененные на предыдущем этапе: c.ipadx = 0; В результате поле Middle name будет размещено в первой ячейки второй строки. Эта кнопка размещается так: c.gridwidth =
GridBagConstraints.REMAINDER; Здесь в поле gridwidth мы указали значение GridBagConstraints.REMAINDER, поэтому кнопка Cancel будет последней во второй строке. Обратите внимание на поле weightx. Его значение не равно нулю, значит, последний столбец нашей таблицы займет все оставшееся место в направлении вправо. Если бы значение этого поля равнялось нулю, все компоненты оказались бы выровненными по центру в горизонтальном направлении. Вы можете попробовать это сами. Это поле добавляется в начало третьей строки: c.ipadx = 0; Здесь мы просто восстанавливаем параметры, аналогичные параметрам поля Middle name, расположенного в начале второй строки. Метка поля Last name Эта метка занимает всю оставшуюся часть третьей строки, так как в поле gridwidth мы задали значение GridBagConstraints.REMAINDER: c.gridwidth =
GridBagConstraints.REMAINDER; При размещении этого поля мы восстанавливаем значение параметра gridwidth, измененное на предыдущем этапе: c.gridwidth = 1; Для этого компонента мы выделяем всю оставшуюся часть строки, задавая в поле gridwidth значение GridBagConstraints.REMAINDER: c.gridwidth =
GridBagConstraints.REMAINDER; Это поле занимает одну ячейку последней строки, поэтому в поле gridwidth мы записали значение 1: c.gridwidth = 1; Для этой метки мы установили следующие параметры: c.weighty = 1.0; Так как в поле weighty указано значение 1, для последней строки таблицы отводится все оставшееся снизу пространство контейнера. Если же записать сюда нулевое значение, все компоненты будут центрированы в окне контейнера по вертикали. Перед завершением работы метод init регистрирует обработчики событий от кнопок: btnOK.addActionListener(this); В задачу этого метода входит обработка событий, происходящих в результате щелчка кнопки, и отображение диалоговой панели. При щелчке кнопки OK метод actionPerformed получает строки из полей нашей формы и записывает их в текстовую переменную с именем s: String s = "<Personal
information>"; Далее метод создает диалоговую панель класса AppletMsgBox (определенный в нашем приложении), передавая строку s соответствующему конструктору: AppletMsgBox amsgbox; Панель затем отображается методом show. В том случае если Вы щелкнете кнопку Cancel, поля формы очистятся: tfFirstName.setText(""); Этот класс мы создали для отображения диалоговой панели с сообщением и кнопкой OK. Он образован на базе класса Frame и реализует интерфейс ActionListener: class AppletMsgBox extends Frame В классе AppletMsgBox определены два поля: Button btnOK; Первое из них хранит ссылку на кнопку, а второе — ссылку на многострочный редактор текста, в окне которого мы отображаем сообщение. Конструктор класса AppletMsgBox Конструктору класса AppletMsgBox передаются два параметра — строка сообщения и строка заголовка: public AppletMsgBox(String msg, String
title) Первым делом конструктор класса AppletMsgBox вызывает конструктор базового класса Frame, передавая ему строку заголовка title, и устанавливает размеры окна панели: super(title); Кнопка и редактор текста создаются обычным образом: btnOK = new Button("OK"); Так как поле ta будет использоваться только для отображения сообщений, мы отменяем функцию редактирования, вызывая метод setEditable. Далее мы устанавливаем режим размещения компонента GridBagLayout: GridBagLayout gbl = new
GridBagLayout(); Параметры размещения текстового поля выбираются следующим образом: c.anchor
= GridBagConstraints.CENTER; Задавая в поле anchor значение GridBagConstraints.CENTER, мы добиваемся центрирования редактора внутри выделенного ему пространства. Так как поле fill имеет значение GridBagConstraints.BOTH, размеры окна редактора изменяются таким образом, чтобы он занимал всю поверхность выделенной ему ячейки таблицы. Мы расположили окно редактора в одной ячейке (значение поля gridheight равно единице), причем так, чтобы оно заняло всю первую строку (в поле gridwidth мы установили значение GridBagConstraints.REMAINDER). Что же касается кнопки, то ее размеры останутся постоянными при корректировке размеров контейнера: c.fill = GridBagConstraints.NONE; Заметим, что мы не ввели значения в полях weightx и weighty. В результате при изменении размеров окна диалоговой панели и редактор, и кнопка остаются в его центре. Перед завершением своей работы конструктор регистрирует обработчик событий для кнопки btnOK: btnOK.addActionListener(this); Этот метод скрывает окно диалоговой панели, когда пользователь щелкает кнопку OK: public void actionPerformed(ActionEvent
e) Язык программирования Java отличается богатой и продуманной библиотекой классов, предназначенной для решения самых разных задач — от создания архивов ZIP и работы с растровыми графическими изображениями до задач организации взаимодействия приложений Java через сеть. В этом разделе кратко описаны основные классы сетевой библиотеки Java, которые потребуются нам для связи аплетов с расширениями сервера Web. Для работы с адресами IP в библиотеке классов Java предназначен класс InetAddress. С его помощью приложение определяет адрес IP локального узла, а также адреса удаленного узла, заданного своим доменным именем. Вот прототипы наиболее интересных методов этого класса: public static InetAddress
getLocalHost(); Заметим, что создание объекта класса InetAddress выполняется не с помощью оператора new, а с применением статических методов getLocalHost, getByName и getAllByName. Метод getLocalHost создает объект класса InetAddress для локального узла, то есть для той рабочей станции, на которой выполняется приложение Java: InetAddress iaLocal; В том случае, если Вас интересует удаленный узел сети, Вы можете создать для него объект класса 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, в библиотеке классов 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. Метод 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); Далее метод init устанавливает параметры размещения отдельных компонентов и добавляет их в окно аплета. При этом используется техника, примененная нами ранее в этой главе при создании аплета GridBag. Отправка данных расширению сервера Web Когда пользователь щелкает кнопку OK, управление передается методу actionPerformed. Рассмотрим выполняемые им действия. Прежде всего, этот метод извлекает данные из компонентов, размещенных в окне аплета. Данные оформляются в виде текстовой строки параметров запуска расширения ISAPI с именем iscard.dll, как это показано ниже: URL u; При этом создается новый объект класса URL, представляющий собой адрес URL запускаемого расширения ISAPI. Все операции выполняются в блоке try-catch, так как при работе приложения могут возникать ошибки, связанные, например, с разрывом связи между браузером покупателя и сервером. Далее мы создаем канал связи с расширением ISAPI как объект класса URLConnection: URLConnection c; С помощью методов setDoOutput и setDoInput для канала разрешается выполнения операций вывода и ввода соответственно. Чтобы принять данные от расширения ISAPI, мы создаем входной поток данных класса DataInputStream, основанный на потоке InputStream. Этот поток, в свою очередь, получен при помощи метода getInputStream: DataInputStream is; Чтение из потока выполняется в цикле: String str=""; Здесь просто накапливаются полученные данные в строке str. По достижении конца потока мы его закрываем: is.close(); После получения всех строк от расширения сервера Web мы отображаем содержимое строки str в диалоговой панели: amsgbox = new AppletMsgBox(str,
"Information"); Размещение аплета в документе HTML Аплет, выполняющий обмен данными с сервера Web, надо загружать именно с этого сервера, а не с какого-либо другого. Поэтому в документе HTML с аплетом правильно указывайте параметры тега <APPLET>: <applet name="CreditCard"
code="CreditCard" Параметр 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); Таким образом, наше расширение ISAPI отправляет обратно аплету полученные от него параметры. Содержимое буфера посылается аплету следующим образом: if(!lpECB->ServerSupportFunction(lpECB->ConnID, Создавая реальный проект, не забудьте выполнить сканирование строки 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?" + Для того чтобы серверный сценарий, расположенный на странице ccard.asp, получил информацию о кредитной карточке, при создании объекта класса URL мы добавляем к адресу страницы параметры, отделив их символом «?». При этом параметры отделяются друг от друга символом «&». Чтобы загрузить страницу ccard.asp в окно браузера, мы создаем объект класса AppletContext, вызывая для этого метод getAppletContext: AppletContext appletContext; Далее, пользуясь полученным контекстом, аплет загружает в окно браузера документ с адресом URL, подготовленным в переменной u: if (u != null) Для загрузки мы вызываем метод showDocument, передавая ему в качестве второго параметра имя окна для загрузки документа. Пользуясь этим параметром, аплет может загрузить документ в любой фрейм, в существующее или вновь созданное окно браузера. Параметр «_self» означает, что документ будет загружен в то же самое окно, где находится аплет. Исходный текст страницы ccard.asp приведен в листинге 13-6. Листинг 13-6 Вы найдете в файле chap13/CreditCard2/ccard.asp на прилагаемом к книге компакт-диске. В нем мы получаем параметры, переданные аплетом, при помощи объекта Request, а затем отображаем их значения в динамически создаваемом документе HTML: <p>Name = <%=Request("Name")(1)%> В реальном проекте параметры можно, например, передать серверному элементу управления ActiveX для расшифровки и дальнейшей обработки. Таким образом, страница ASP станет удобным средством извлечения параметров из данных, отправленных посетителем Вашего сервера при помощи аплета Java. В этом разделе показана методика просмотра содержимого архивных файлов формата ZIP, а также способ извлечения из такого архива отдельных файлов. Приведен пример приложения, демонстрирующего использование классов ZipFile и ZipEntry. Среди различных библиотек классов Java есть одна очень интересная — с названием java.util.zip. Она позволяет работать с библиотеками файлов широко распространенных форматов ZIP и GZIP. Применяя соответствующие классы, Вы сможете разрабатывать приложения, способные создавать библиотеки архивных файлов, просматривать их содержимое, извлекать из библиотек отдельные или все файлы. В этом разделе мы рассмотрим способы извлечения отдельных файлов из библиотеки формата ZIP и получения подробной информации о хранящихся там файлах. Чтобы получить доступ к таким файлам, Вы должны, прежде всего, создать объект класса ZipFile, пользуясь одним из конструкторов этого класса. Первый конструктор позволяет открыть ZIP-файл через объект класса File, а второй — через полный путь к имени файла: try В процессе создания объекта класса ZipFile могут возникать как обычные для файлов исключения IOException, так и исключения класса ZipException, специфические для класса ZipFile. В классе ZipFile определены методы, перечисленные в табл. 13-4. Таблица 13-4. Методы класса ZipFile
Методы entries и getEntry позволяют получить элементы, хранящиеся в архиве, как объекты класса ZipEntry. Ниже мы получаем перечисление таких объектов: Enumeration en = zf.entries(); Чтобы извлечь элементы перечисления и записать их в массив zipEntries класса Vector, мы можем создать цикл: while(en.hasMoreElements()) В классе ZipEntry определены две константы, конструктор и несколько методов. Константы обозначают использованный метод компрессии: public static final int STORED; Если файл сохранен в архиве без компрессии, для него используется метод ZipEntry.STORED, а если с компрессией — метод ZipEntry.DEFLATED. Конструктор класса ZipEntry позволяет создать новый элемент оглавления архива с заданным именем: public ZipEntry(String name); Что же касается методов класса ZipEntry, то их можно разделить на две группы, одна из которых предназначена для установки атрибутов элемента оглавления архива ZIP, а другая — для извлечения. Рассмотрим эти методы. public void setCrc(long crc); Этот метод позволяет установить значение контрольной суммы для неупакованного файла, описываемого данным элементом оглавления архива. Для вычисления контрольной суммы применяется алгоритм CRC-32. public long getCrc(); Метод предназначен для извлечения контрольной суммы, записанной в элемент оглавления. public void setMethod(int method); С помощью этого метода Вы можете установить режим добавления файла в архив — с упаковкой (компрессией) или без упаковки. В первом случае методу необходимо передать значение ZipEntry.DEFLATED, а во втором — значение ZipEntry.STORED. public int getMethod(); Метод getMethod позволяет знать способ упаковки, который был использован для файла, соответствующего данному элементу архива. Он может возвращать значения ZipEntry.DEFLATED или ZipEntry.STORED. public void setExtra(byte extra[]); С помощью этого метода можно записать в элемент оглавления архива произвольную дополнительную информацию. public byte[] getExtra(); Метод getExtra возвращает дополнительную информацию, записанную в элементе оглавления архива. public void setComment(String comment); Запись в элемент оглавления архива дополнительной текстовой строки комментария. public String getComment(); Чтение строки комментария. public long getCompressedSize(); Метод getCompressedSize возвращает размер упакованного файла или значение -1, если этот размер неизвестен. public boolean isDirectory(); Помимо файлов, в архиве могут храниться каталоги. В этом случае имя соответствующего элемента должно оканчиваться символом «/». Метод isDirectory позволяет узнать, является ли данные элемент описателем каталога или файла. В первом случае он возвращает значение true, а во втором — false. public String getName(); Этот метод позволяет узнать имя, соответствующее данному элементу оглавления архива. Изменить имя нельзя — оно передается конструктору класса ZipEntry при создании нового элемента оглавления. public void setTime(long time); Установка времени модификации файла (в количестве миллисекунд, прошедших с начала эпохи). public long getTime(); Определение времени модификации файла. public void setSize(long size); Установка размера несжатого файла. 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.*; Классы java.util.zip.* нужны для работы с файлами ZIP, а класс java.text.* — для форматирования строки даты изменения файлов, записанных в архиве. Когда пользователь выбирает строку Open из меню File, метод actionPerformed, определенный в классе FrameWindow, отображает на экране стандартное диалоговое окно для выбора ZIP-файла: public void actionPerformed(ActionEvent e) Путь к выбранному файлу записывается в переменную szPath. Он затем передается конструктору класса ZipFileDialog, определенному в нашем приложении для отображения списка элементов оглавления ZIP-файла: ZipFileDialog d = new ZipFileDialog( Класс ZipFileDialog Класс ZipFileDialog создан на базе класса Dialog и реализует ряд интерфейсов: class ZipFileDialog extends Dialog В этом классе определено несколько полей. В поле 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 Далее метод изменяет заголовок диалогового окна, добавляя туда путь к выбранному архиву: setTitle("ZIP - " + szZipFilePath); Пользуясь полученным перечислением, мы заполняем массив zipEntries, записывая в него объекты класса ZipEntry: while(en.hasMoreElements()) Далее, перебирая элементы массива zipEntries в цикле, мы извлекаем имена элементов и добавляем их в список, расположенный в окне диалогового окна: int i = 0; Метод itemStateChanged класса ZipFileDialog Когда пользователь выделяет в списке строку имени элемента оглавления массива, управление передается методу itemStateChanged, реализованному как часть интерфейса ItemListener: public void itemStateChanged(ItemEvent
e) Наша реализация этого метода вызывает метод getZipFileEntryInfo, извлекающий информацию из элемента оглавления архива и отображающий ее в главном окне приложения. Метод getZipFileEntryInfo класса ZipFileDialog Получив управление, метод getZipFileEntryInfo определяет номер элемента, выделенного пользователем в списке: int nSelected = l.getSelectedIndex(); Далее из массива zipEntries извлекается соответствующий элемент класса ZipEntry: ZipEntry ze = (ZipEntry)zipEntries.elementAt(nSelected); Перед тем как отобразить атрибуты данного элемента в главном окне приложения, мы очищаем расположенное там многострочное окно редактирования, выделяя в нем весь текст и заменяя пустой строкой: ((FrameWindow)par).ta.selectAll(); Затем мы формируем новый текст в переменной szItemInfo, записывая туда значения атрибутов, извлеченные из элемента оглавления соответствующими методами класса ZipEntry: szItemInfo = "Entry name:
" + ze.getName(); Затем полученная строка добавляется в редактор главного окна приложения: ((FrameWindow)par).ta.append(szItemInfo); Метод actionPerformed класса ZipFileDialog Когда пользователь нажимает кнопку Extract, расположенную в нижней части диалоговой панели со списком, метод actionPerformed извлекает из списка имя выделенного элемента архива и передает его методу saveZipFile: public void
actionPerformed(ActionEvent e) Метод saveZipFile класса ZipFileDialog В задачу метода saveZipFile входит извлечение из архива указанного файла и сохранение его на диске. Прежде всего? мы выделяем из полного пути к файлу имя: s = s.substring(s.lastIndexOf("/") + 1); Это имя затем используется при инициализации панели выбора выходного класса: FileDialog fdlg; Таким образом, пользователю предлагается сохранить файл под тем именем, под которым он хранился в архиве. Далее метод saveZipFile записывает полный путь к сохраняемому файлу в переменной szPath. На следующем этапе метод создает выходной поток, связанный с файлом: int nSelected; Узнав номер строки, выделенной пользователем в списке имен файлов, мы извлекаем из массива zipEntries соответствующий элемент оглавления класса ZipEntry: nSelected = l.getSelectedIndex(); Далее нам необходимо скопировать данные из архива в выходной файл fos. Чтобы это сделать, мы получаем ссылку на входной поток, связанный с объектом ZipFile: InputStream is = zf.getInputStream(ze); Копирование выполняется в цикле с применением методов read и write: int nLength; В этом разделе мы показали способ создания архива 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. Константы определения степени упаковки
Следующий этап создания архива ZIP заключается в формировании объектов класса ZipEntry, соответствующих элементам оглавления архива. Каждый такой элемент необходимо записать в архив методом putNextEntry: ZipEntry ze; Когда элемент оглавления для добавляемого файла записан в архив, следует выполнить копирование потока, связанного с добавляемым файлом в поток класса ZipOutputStream. Указанная операция выполняется в цикле для всех добавляемых файлов. Описание примера приложения DemoZip Сразу после запуска наше приложение запрашивает с консоли путь к каталогу, расположенному на локальном диске компьютера. Когда пользователь введет этот путь, программа создает в рабочем каталоге архивный файл формата ZIP с именем !temp.zip и добавляет в него все файлы, расположенные в рабочем каталоге. При этом для каждого файла выводится полный путь, исходный размер файла в байтах, размер файла после сжатия и процентное отношение последних двух величин: Enter full path: После обработки всех файлов, расположенных в указанном каталоге, программа завершает свою работу. Рассмотрим исходный текст нашей программы (листинг 13-8). Листинг 13-8 Вы найдете в файле chap13/DemoZip/DemoZip.java на прилагаемом к книге компакт-диске. Как мы уже говорили, путь к исходному каталогу наша программа запрашивает с консоли. Для этого мы используем метод getKbdString, определенный в нашем приложении: String s; Строка полного пути к каталогу, возвращенная этим методом, сохраняется в переменной с именем s. Прежде чем продолжить свою работу, метод main проверяет, что указанный путь относится к существующему каталогу: File f = new File(s); В противном случае работа программы завершается с сообщением об ошибке. Если путь к каталогу указан правильно, метод main создает выходной поток для нового архивного файла с именем !temp.zip: ZipOutputStream zos = createZipOutputStream("!temp.zip"); Для этого вызывается метод createZipOutputStream, определенный в нашем приложении. Данный метод возвращает ссылку на поток класса ZipOutputStream. Кроме того, метод main создает массив строк с именами файлов и каталогов, расположенных в указанном пользователем каталоге: String[] sDirList = f.list(); Добавление файлов к архиву ZIP выполняется в цикле методом addFileToZip, определенным в нашем приложении: int i; Этот метод вызывается только в том случае, если очередной элемент массива содержит имя файла, а не каталога. Через первый параметр методу addFileToZip передается ссылка на выходной поток класса ZipOutputStream, через второй — путь к каталогу с файлами, а через третий — имя добавляемого файла. После завершения цикла добавления файлов метод main закрывает выходной поток класса ZipOutputStream. На этом работа программы завершается. Метод createZipOutputStream предназначен для создания выходного потока класса ZipOutputStream, привязанного к файлу. В качестве единственного параметра этому методу передается путь к создаваемому файлу архива ZIP: static ZipOutputStream Все операции выполняются методом внутри блока try-catch, так как они могут вызвать появление исключений. Вначале метод createZipOutputStream создает для файла, путь к которому был передан через параметр, объект класса File: File tempfile; Далее с помощью соответствующего конструктора создается поток класса ZipOutputStream: zos = new ZipOutputStream(new FileOutputStream(tempfile)); Для этого потока мы устанавливаем уровень упаковки, принятый по умолчанию: zos.setLevel(Deflater.DEFAULT_COMPRESSION); Затем метод createZipOutputStream возвращает ссылку на созданный выходной поток: return zos; Метод addFileToZip Прототип метода addFileToZip представлен ниже: static void
addFileToZip(ZipOutputStream zos, Как мы уже говорили, через первый параметр методу addFileToZip передается ссылка на выходной поток класса ZipOutputStream, через второй — путь к каталогу с файлами, а через третий — имя добавляемого файла. С помощью ключевого слова throws мы указали, что в процессе работы этого метода могут возникать исключения, которые необходимо обработать в вызывающем методе. Свою работу метод addFileToZip начинает с того, что выводит на консоль путь к файлу, который добавляется в архив: System.out.print(szPath + szName); Далее для добавляемого файла мы создаем новый элемент оглавления (как объект класса ZipEntry) и записываем его в выходной поток zos класса ZipOutputStream методом putNextEntry: ZipEntry ze; Теперь нужно скопировать в выходной поток zos сам добавляемый файл. Эта операция выполняется в цикле: FileInputStream fis = new FileInputStream(szPath
+ szName); После завершения цикла необходимо закрыть поток, связанный с добавляемым файлом и новый элемент оглавления: fis.close(); Теперь элемент оглавления полностью сформирован и записан в выходной поток zos, связанный с архивом. Мы извлекаем из этого элемента сведения об исходном размере файла и размере файла после сжатия, а затем отображаем эту информацию на консоли: long
nSize = ze.getSize(); Далее метод addFileToZip возвращает управление вызвавшему его методу. В этом разделе мы показали способ распаковки архива ZIP, путь к которому вводится через консоль. Распакованное дерево каталогов будет расположено в заданном каталоге. При этом используются классы ZipFile и ZipEntry. Классы ZipFile и ZipEntry позволяют организовать распаковку существующих архивов формата ZIP. Как она выполняется? Прежде всего, Вы должны создать объект класса ZipFile, передав соответствующему конструктору путь к файлу, например: ZipFile zf; Далее Вы получаете перечисление элементов оглавления архива методом entries: Enumeration en = zf.entries(); Пользуясь этим перечислением, нетрудно организовать извлечение всех элементов оглавления для последующей записи в массив: while(en.hasMoreElements()) Перебирая в цикле элементы оглавления, Вы должны открыть для каждого такого элемента, не являющегося каталогом, входной поток класса InputStream. Такая операция может быть выполнена методом getInputStream, определенным в классе ZipFIle: ZipEntry ze; Далее Вы открываете для очередного извлекаемого файла выходной поток класса FileOutputStream и выполняете копирование из потока is в этот выходной поток. Обрабатывая элементы оглавления архива, Вы должны учитывать следующие моменты: · во-первых, имя элемента оглавления может содержать путь к файлу. В качестве разделителя каталогов в этом пути применяется символ «/», который перед созданием выходного каталога следует заменить символом File.separatorChar. Это нужно сделать, так как на разных платформах применяются разные символы-разделители; · во-вторых, наряду с элементами, описывающими файлы, в оглавлении могут встречаться элементы с описанием каталогов. Имя таких элементов оканчивается символом «\». Вы можете их или игнорировать, или использовать при создании дерева каталогов Описание примера приложения DemoUnzip После запуска наше приложение запрашивает с консоли путь к исходному файлу архива ZIP и путь к каталогу, куда нужно записать извлеченные из архива файлы. Затем программа извлекает файлы и записывает их в указанный каталог, создавая при необходимости дерево вложенных каталогов. В ходе этого процесса на консоль выводятся пути к создаваемым файлам, их полная длина и длина после сжатия: Enter full path to Zip-file: Рассмотрим исходный текст приложения (листинг 13-9). Листинг 13-9 Вы найдете в файле chap13/DemoUnzip/DemoUnzip.java на прилагаемом к книге компакт-диске. Получив управление, метод main запрашивает у пользователя два пути — к исходному файлу ZIP и к выходному каталогу. После проверки эти пути сохраняются, соответственно, в переменных szZipFilePath и ExtractPath. Далее метод main создает объект класса ZipFile, получает перечисление элементов оглавления и сохраняет их в массиве zipEntries: ZipFile zf; Затем элементы массива zipEntries обрабатываются в цикле: int i; Мы передаем каждый такой элемент как объект класса ZipEntry методу extractFromZip, определенному в нашем приложении (через последний параметр). Через первый параметр этот метод получает путь к исходному файлу архива ZIP, через второй — путь, куда нужно записать извлеченный файл, через третий — имя файла, а через четвертый — ссылку на объект класса ZipFile. После завершения цикла метод main закрывает объект zf класса ZipFile и выводит на консоль сообщение о завершении работы: zf.close(); Прежде всего, метод extractFromZip проверяет, не является ли переданный ему элемент описателем каталога: if(ze.isDirectory()) Если является, то метод просто возвращает управление для перехода к следующему элементу. В том случае, когда элемент описывает файл, метод extractFromZip выполняет замену разделителей «\» на File.separatorChar, вызывая для этого метод slash2sep, определенный в нашем приложении: String szDstName = slash2sep(szName); Замена выполняется в имени файла, извлеченного из элемента оглавления архива. Напомним, что это имя может содержать путь в виде списка каталогов с разделителями. На следующем этапе метод extractFromZip отрезает от этого пути имя файла: String szEntryDir; В итоге в переменной szEntryDir будет записан путь к каталогу, куда нужно скопировать извлеченный файл. Если в имени извлекаемого файла нет каталогов и разделителей, файл будет записан в каталог, указанный пользователем с консоли. Имя файла, извлеченное из элемента оглавления и исправленное (с замененными символами-разделителями), выводится на консоль: System.out.print(szDstName); Туда же мы выводим размер исходного и сжатого файла: long nSize = ze.getSize(); Далее мы создаем дерево каталогов для записи извлекаемого файла: try Путь к каталогу образуется путем слияния пути для распаковки, заданного с консоли, и пути, извлеченного из имени файла по элементу оглавления. Чтобы извлечь файл, мы создаем два потока: FileOutputStream fos = Поток fos связан с извлекаемым файлом, а поток is — с элементом оглавления архива. Копирование данных выполняется в цикле: byte[] buf = new byte[1024]; Если в процессе извлечения файла из архива происходит исключение EOFException или достигается конец потока, цикл копирования завершает свою работу. Далее мы закрываем потоки: is.close(); Этот метод предназначен для замены символа-разделителя «/», применяющегося в архивах формата ZIP, на символ File.separatorChar. Такая замена необходима для обеспечения нормальной работы нашего приложения на различных компьютерных платформах. Замена выполняется простым циклическим просмотром исходной строки, преданной методу в качестве параметра: static String slash2sep(String src) Здесь в цикле на базе исходной строки формируется массив символов chDst, который затем преобразуется в строку класса String и возвращается вызывающему методу. Вычисление контрольной суммы файла В примере демонстрируется использование библиотеки классов java.util.zip для вычисления контрольной суммы файла по методам CRC-32 и Adler-32. Иногда перед программистом встает задача вычисления контрольной суммы файла. Такая сумма может потребоваться, например, чтобы убедиться в сохранности файла при его передаче по сети или при его хранении в небезопасных условиях. Разумеется, Вы можете сами организовать подсчет контрольной суммы, например, просто складывая последовательности четырехбайтовых слов по модулю два или с применением какого-либо иного алгоритма. В библиотеке классов java.util.zip есть классы и интерфейсы, намного упрощающие данную задачу. Это классы CRC32, Adler32 и интерфейс Checksum. Интерфейс Checksum определяет методы, применяющиеся при подсчете контрольной суммы. Это методы update, getValue и reset. Метод update существует в виде двух реализаций: public abstract void update(int b); Первая из них обновляет контрольную сумму значением одного байта, а вторая — значениями массива байт. Метод 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)) Далее мы два раза вызываем метод calculate, определенный в нашем приложении, который возвращает текстовое представление значения контрольной суммы: ta.append("CRC-32: " +
calculate(szPath, new CRC32()) + "\n"); Через первый параметр этому методу передается путь к файлу, а через второй — ссылка на класс, определяющий метод вычисления контрольной суммы. Как устроен метод calculate? Прежде всего, этот метод открывает входной поток для файла, контрольную сумму которого необходимо вычислить: String s = ""; Далее мы организуем чтение из потока в цикле блоками по 8000 байт (значение, выбранное нами произвольно), с периодическим обновлением контрольной суммы методом update: while(true) После достижения конца файла связанный с ним поток закрывается, а значение контрольной суммы извлекается методом getValue: fis.close(); Мы преобразуем его в текстовую строку класса String и возвращаем вызвавшему методу. При работе с изображениями приложения Java могут задавать для пикселов такую характеристику, как прозрачность. Наложением полупрозрачных изображений можно получит оригинальные визуальные эффекты. В примере аплета AlphaDemo, описанного в этом разделе, показывается техника наложения полупрозрачного изображения на непрозрачное изображение. Те из Вас, кто занимался подготовкой графических иллюстраций для размещения на сервере Web, знает о так называемых прозрачных изображениях GIF. С помощью специального редактора Вы можете указать, что один из цветов в изображении должен быть прозрачным. Прозрачное изображение обычно размещают над другим изображением или фоном для достижения эффекта наложения. В процессе создания изображения задействованы два объекта: ImageProducer и ImageConsumer. Первый из них является интерфейсом для тех объектов, которые поставляют данные для изображений, а второй — интерфейсом для получения и преобразования данных. Библиотека классов AWT позволяет встроить фильтр в поток данных, передаваемых из ImageProducer в ImageConsumer. В результате Вы сможете изменять изображения «на лету», получая интересные эффекты. Фильтр может выполнить обработку всех или отдельных пикселов изображения, изменив их цвет или прозрачность. И, что немаловажно, создание и подключение фильтра не является чрезвычайно сложной задачей. Вот как выглядит такой фильтр: class SomeMyOwnImageFilter В процессе преобразования изображения для каждого пиксела вызывается метод filterRGB. Через параметры ему передаются координаты (параметры x и y), значения цвета и прозрачности (параметр nRGB). Метод filterRGB должен вернуть новое значение прозрачности и цвета данного пиксела. В процессе преобразования Вы можете проверять координаты пикселов или игнорировать их, а также выполнять произвольные преобразования значений цветовых компонент и прозрачности, лежащих в диапазоне от 0 до 255. Теперь о том, как подключить фильтр. Рассмотрим следующую ситуацию. Пусть, например, мы создаем фильтр для преобразования некоего изображения imgSrc в изображение imgResult. Вначале необходимо создать новый фильтр как объект: SomeMyOwnImageFilter someFilter = Далее нужно создать объект класса ImageProducer: ImageProducer ip = new FilteredImageSource(imgSrc.getSource(), someFilter); Этот объект создается при помощи конструктора класса FilteredImageSource. В качестве первого параметра мы передаем конструктору ссылку на источник данных исходного изображения, полученный методом getSource, а через второй — ссылку на свой фильтр. Затем мы можем создать новое изображение методом createImage, как это показано ниже: Image imgResult = createImage(ip); Чтобы дождаться процесса завершения формирования изображения imgResult, используйте класс MediaTracker: MediaTracker mt = new
MediaTracker(this); Теперь изображение 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.*; Первое из них предназначено для хранения ссылки на изображение, показанное на рис. 13-8, а второе — для хранения ссылки на изображение, показанное на рис. 13-9. При инициализации аплета метод init загружает оба изображения: img1 = getImage(getCodeBase(),
"img1.jpg"); Чтобы дождаться завершения процесса загрузки, мы использовали класс MediaTracker: MediaTracker mt = new
MediaTracker(this); Исходный текст этого метода предельно прост: public void paint(Graphics g) Здесь мы вначале рисуем первое изображение, а затем второе. Второе изображение перед рисованием преобразуется в полупрозрачное изображение методом imgToTransparent, определенным в нашем приложении. Метод imgToTransparent получает через свой единственный параметр исходное изображение, делает его полупрозрачным и возвращает результат как объект класса Image: Image imgToTransparent(Image imgSrc) Прежде всего, метод imgToTransparent создает фильтр класса TransparentImageFilter и объект класса ImageProducer: TransparentImageFilter trFilter = Новое полупрозрачное изображение создается методом createImage: Image imTransparent = createImage(ip); Чтобы дождаться завершения формирования полупрозрачного изображения мы создаем объект класса MediaTracker: MediaTracker mt = new
MediaTracker(this); Если преобразование завершилось без ошибок, метод imgToTransparent возвращает ссылку на созданное изображение: return imTransparent; Класс TransparentImageFilter В классе TransparentImageFilter мы определили метод filterRGB: class TransparentImageFilter Единственное, что делает наш фильтр, это устанавливает для параметра nAlpha значение 0x80. Цветовые компоненты пикселов не изменяются. |