Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
181
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

hr.scheduleChange(st2,ss.start);

}

}

addACK();

}

Метод addMeeting можно разбить на три части: 1) разбор и верификация, 2) реальное добавление к базе данных и 3) оповещение. При каждом возникновении ошибки посылается пакет addNAK. Клиент должен указать пользователю, в чем ошибка. Затем они могут подогнать входные данные и снова послать пакет.

На этапе разбор и верификации нужно убедиться в правильности дат и времени для встреч. Для разбора применяется конструктор Dates. Конструктор вызывает Date.parse, который придает смысл этой строке. Dates поддерживает хост с форматами дат, но при этом обладает необычным свойством: нумерация месяцев начинается с нуля, а не с единицы. Так что если дата записана как 07/27/1992, это означает 27 августа 1992 года, а не 27 июля. На стороне клиента мы изменили дату так, чтобы посланная дата была правильной, - то есть уменьшили номер месяца на единицу. Если вам интересно, как работает класс Date, посмотрите документацию или почитайте исходный текст программы для класса Java.util.Date.

С помощью объекта schedule легко добавлять данные в базу данных. Если в определенное время пользователь уже занят, мы получим исключение duplicateException. К примеру, если назначается встреча четырех человек, могут возникнуть неприятности. Обратной транзакции здесь не существует, поэтому если мы планируем встречу четырех человек и один из них в это время уже занят, то в расписание этого человека встреча не записывается. Мораль проста:

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

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

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

Модуль, специфический для данного проекта

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

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

Клиент Schedule

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

public class clientSchedule implements schedule { Hashtable memoryStore = new Hashtable();

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

Класс clientSchedule реализует интерфейс schedule. Этот класс даст нам программу для описанного предварительно интерфейса. Удобство использования интерфейсов состоит в том, что если вам не нравится данная реализация, вы можете написать свою собственную. Вы даже можете взять для использования некоторые фрагменты программы и заменить те ее части, которые вам не нравятся:

public void add(scheduleStruct newSchedule) throws DuplicateException, IOException {

if (memoryStore.put(newSchedule.start, newSchedule) != null) {

throw new DuplicateException();

}

}

Метод add используется для добавления новых встреч. Относительно этой программы у нас есть два важных замечания. Одно касается того, каким образом помещать новые пункты в хештаблицу. Мы должны иметь данные и ключ (в данном случае это дата начала встречи), являющиеся объектами. Хеш-таблицы только хранят объект по значению ключа. Таким образом, если мы попытаемся записать две встречи на одно и то же время, мы получим ошибку. Такая система работает, поскольку большинство людей не посещают две встречи одновременно.

Второе важное замечание касается метода выдачи исключений. Как вы помните, мы должны описать все исключения, которые могут возникнуть в результате работы метода. Метод add, повидимому, может вызвать только одно исключение - DuplicateException. IOException генерируется методом put хеш-таблицы. Поскольку мы здесь не занимаемся ошибками, мы передаем их вызывающему методу. Маловероятно, что мы получим IOException при работе со структурой данных, находящейся в памяти, но тем не менее это возможно. Если такое случится, предоставим программисту решать эту задачу.

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

public void del(scheduleStruct del) throws NotFoundException {

if (memoryStore.remove(del.start) == null) throw new NotFoundException();

}

public scheduleStruct find(Date date) throws NotFoundException { scheduleStruct ss;

ss = (scheduleStruct) memoryStore.get(date); if (ss == null) {

throw new NotFoundException();

}

Методы delete и find просты и практически не нуждаются в пояснениях. Для возвращения информации об ошибках снова используются исключения. Оба метода хеш-таблицы (get и remove) возвращают по запрошенной информации ссылочное значение. Если значение ключа не найдено, возвращается ноль. В этом случае будет сгенерировано NotFoundException.

Последний метод - findRange. Тут мы воспользуемся нумерацией, которая поможет нам вернуть значения данных. Метод findRange получает временной промежуток и возвращает все встречи за указанный период. Поскольку хеш-таблица не содержит данные по временным промежуткам, нам придется производить поиск последовательно. Зато хеш-таблица реализует интерфейс нумерации. Это позволяет последовательно просматривать хеш-таблицу.

Мы будем просматривать таблицу дважды. При первом просмотре мы определим число встреч, которые будут возвращены, а при втором просмотре заполним результирующий массив. Мы вынуждены так делать потому, что размер, по крайней мере, одного измерения массива должен быть определен во время его создания. Заметьте, что мы вызываем элементы метода хештаблицы дважды. Каждый раз после просмотра нумерации она уничтожается. При каждом новом обращении нужно заново создавать нумерацию. Ниже приводится программа для метода findRange:

www.books-shop.com

public scheduleStruct[] findRange(Date start, Date end) throws IOException {

Enumeration list = memoryStore.elements(); scheduleStruct ss;

int cnt=0;

// считаем число элементов while (list.hasMoreElements()) {

ss = (scheduleStruct) list.nextElement(); if (start.getTime() <= ss.start.getTime() &&

end.getTime() >= ss.end.getTime()) { cnt++;

}

}

if (cnt < 1) return null;

scheduleStruct result[] = new scheduleStruct[cnt];

//нумерация уничтожена, нумеруем заново list = memoryStore.elements();

//создаем результирующий массив

cnt=0;

while (list.hasMoreElements()) {

ss = (scheduleStruct) list.nextElement(); if (start.getTime() <= ss.start.getTime() &&

end.getTime() >= ss.end.getTime()) { result[cnt++] = ss;

}

}

return result;

}

}

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

Сервер Schedule

Сервер schedule несколько сложнее, потому что мы будем иметь дело со структурой данных, хранящейся на диске. Для того чтобы работать с базой данных внушительного размера, реализация сервера должна быть максимально эффективной. Мы будем хранить данные в линейном файле, но для выполнения более быстрого поиска использовать индексный файл. Если вам интересно, как работает индексный файл, посмотрите schedule/IndexFile.java. Программа для него достаточно проста, но слишком длинна для того, чтобы приводить ее здесь. Вот программа для serverSchedule:

public class serverSchedule implements schedule { IndexFile index;

RandomAccessFile dataFile;

Мы снова реализуем интерфейс schedule. На этот раз мы используем для сохранения данных два файла. Первый - индексный файл, который индексируется по начальной дате и хранит местоположение данных в файле данных. Файл данных хранит записи в формате переменной длины. Оба файла связаны с именем пользователя, причем индексный файл имеет расширение

.idx, а файл данных - .dat.

public serverSchedule(String user) throws IOException { this.user = user;

index = new IndexFile(user + ".idx","rw",Size,Size); dataFile = new RandomAccessFile(user + ".dat","rw");

}

www.books-shop.com

Конструктор для этого класса содержит строку, идентифицирующую пользователя. Эта строка используется для того, чтобы создать имена индексного файла и файла данных. Заметим, что эта подпрограмма возвращает IOException. Это может случиться, если сервер не имел разрешения записывать в файлы - вы должны найти эту ошибку в вашей программе. Для хранения данных мы используем RandomAccessFile. Файл открывается на чтение и запись (с кодом доступа "rw") под именем, составленным из идентификатора пользователя и расширения .dat. Так, для пользователя с именем maggie файл данных будет называться maggie.dat. Следующий раздел программы добавляет встречу в расписание:

public void add(scheduleStruct newSchedule) throws DuplicateException, IOException { String key;

String data; Long conv; long pos;

dataFile.seek(dataFile.length()); pos = dataFile.getFilePointer();

dataFile.writeLong(newSchedule.start.getTime());

dataFile.writeLong(newSchedule.end.getTime()); dataFile.writeBytes(newSchedule.desc + "\n"); dataFile.writeBytes(newSchedule.descURL + "\n"); dataFile.writeBytes(newSchedule.minutesURL + "\n"); dataFile.writeBytes(newSchedule.attending + "\n"); conv = new Long(newSchedule.start.getTime());

key = conv.toString();

key = StringUtil.padStringLeft(key,' ',Size); conv = new Long(pos);

data = conv.toString();

data = StringUtil.padStringLeft(data,' ',Size); index.addRecord(key,data);

}

Метод add должен добавлять элементы к файлу данных и к индексному файлу. RandomAccessFile находится над фильтром DataOutput и DataInput, так что мы можем читать и писать типы Java непосредственно. Было бы хорошо реально написать объект, но, увы, эта возможность не предусмотрена. Еще одно важное замечание: мы пользовались методом writeBytes. Он записывает данные как 8-битовые байты. Если бы мы пользовались методом writeChars, мы имели бы 16 битов на символ. Метод writeUTF запишет 16-битовые символы Unicode. Если вы собираетесь использовать метод readLine, вам нужно пользоваться методом writeBytes. Соответствующего метода чтения, который бы читал выходные данные writeChars, не существует.

Заметим также, что после каждой строки стоит "\n". В дальнейшем мы будем пользоваться методом readLine - он воспринимает возврат каретки как символ конца строки. Это позволяет задавать строки переменной длины, что экономит немного места и дает возможность не проводить разбор строк.

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

public void del(scheduleStruct del)

throws NotFoundException, IOException { Long conv;

String key; String data; long pos;

conv = new Long(del.start.getTime()); key = conv.toString();

key = StringUtil.padStringLeft(key,' ',Size); data = index.findRecord(key);

conv = new Long(data.trim()); pos = conv.longValue();

www.books-shop.com

//теперь можно удалить данные на pos,

//но мы не будем этого делать index.delRecord(key);

}

Метод delete уничтожает только элемент индексного файла. Если бы мы уничтожили элемент файла данных, у нас образовалась бы дыра в файле. Чтобы ее устранить, нам пришлось бы переместить все данные в файле к началу и заменить все соответствующие элементы индексного файла. Операция такого типа является обычной для программ сжатия и, как правило, выполняется редко. Дисковое пространство достаточно дешево, так что мы можем позволить нашей программе выполняться несколько быстрее и только периодически выполнять операции с базой данных. Приведенный ниже фрагмент кода показывает, как был выполнен метод find для нашего расписания, хранящегося на диске:

public scheduleStruct find(Date date)

throws NotFoundException, IOException { scheduleStruct result;

long pos; Long conv; String key; String data;

conv = new Long(date.getTime()); key = conv.toString();

key = StringUtil.padStringLeft(key,' ',Size); data = index.findRecord(key);

conv = new Long(data.trim()); pos = conv.longValue(); return findPos(pos);

}

public scheduleStruct findPos(long pos) throws IOException {

scheduleStruct result = new scheduleStruct(); dataFile.seek(pos);

result.start = new Date(dataFile.readLong()); result.end = new Date(dataFile.readLong()); result.desc = new String(dataFile.readLine()); result.descURL = new String(dataFile.readLine()); result.minutesURL = new String(dataFile.readLine()); result.attending = new String(dataFile.readLine()); return result;

}

Этот метод состоит из двух подпрограмм. Первая, определенная интерфейсом schedule, просматривает индексный файл и находит местоположение необходимого элемента из файла данных. Вторая подпрограмма - findPos - используется для восстановления данных из файла данных. Здесь применяется метод readLine. Мы ищем соответствующую позицию и читаем каждое поле из файла данных. Затем эти данные возвращаются вызывающему методу.

В результате мы подходим к последней, самой трудной подпрограмме - serversSchedule. Чтобы она быстрее работала, подпрограмма findRange использует отсортированный индексный файл. Мы производим два двоичных поиска в индексном файле, чтобы найти начальный и конечный элементы. Затем мы просматриваем каждый элемент и находим его данные в файле данных. В случае большой базы данных и разумного диапазона данных такая процедура выполняется гораздо быстрее, чем если просматривать всю базу данных. Теперь, когда у нас есть подпрограмма для нахождения определенного расписания, нам нужна подпрограмма, возвращающая диапазон расписаний:

public scheduleStruct[] findRange(Date start, Date end) throws IOException {

searchStruct resultStart, resultEnd; Long conv;

String key,data; int num;

conv = new Long(start.getTime()); key = conv.toString();

www.books-shop.com

key = StringUtil.padStringLeft(key,' ',Size); resultStart = index.binarySearch(key);

conv = new Long(end.getTime()); key = conv.toString();

key = StringUtil.padStringLeft(key,' ',Size); resultEnd = index.binarySearch(key);

num = (int) (resultEnd.pos - resultStart.pos) / index.getRecordSize();

if (num < 1) return null;

scheduleStruct result[] = new scheduleStruct[num]; int cnt=0;

for(long i=resultStart.pos; i < resultEnd.pos;) { data = index.findPos(i);

// Data - это индекс в файле .dat conv = new Long(data.trim());

result[cnt++] = findPos(conv.longValue()); i += index.getRecordSize();

}

return result;

}

}

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

Модуль пользовательского интерфейса

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

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

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

class MenuFrame extends Frame { MenuBar mb = new MenuBar();

public MenuFrame(String s, ui applet) { super(s);

owner = applet;

}

public void createMainMenu() { setMenuBar(mb);

}

public void init() { createMainMenu();

}

Класс MenuFrame содержит дополнительные функции по сравнению с классом Frame. Мы хотели бы рассмотреть фреймы и апплеты под общим углом зрения. Это означает, что мы можем взять несколько конвенций из апплетов и применить их к фрейму.Чтобы это сделать, мы сделали конвенцию, в которой создан метод init. Его функции аналогичны функциям апплета: задать переменную и пространство окна. Наш метод init вызывает createMainMenu, который задает систему меню и выглядит следующим образом:

www.books-shop.com

public void createMainMenu() { setMenuBar(mb);

Menu m = new Menu("File"); m.add(new MenuItem("Login"));

m.add(new MenuItem("Open Schedule")); m.add(new MenuItem("-"));

m.add(new MenuItem("Quit")); mb.add(m);

m = new Menu("Setup");

m.add(new MenuItem("User Name")); m.add(new MenuItem("Server Name")); mb.add(m);

}

После появления меню мы должны знать, когда пользователь выберет пункт меню. Это можно сделать, переопределив метод handleEvent. Каждый раз, когда в данном фрейме возникает событие, вызывается метод handleEvent. Нас касаются только два события.

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

Другое важное событие - ACTION_EVENT, ассоциирующееся с меню. Для выяснения того, что на MenuItem случилось это событие, воспользуемся оператором instanceof. Поскольку у нас только одно меню, понятно, что событие произошло именно там. Чтобы выяснить название кнопки меню, мы можем посмотреть evt.arg. Потом мы вызовем подпрограмму для работы с этой кнопкой.

При обработке события во фреймах важно помнить, что они не порождают цепочку событий. Каждый фрейм является независимым объектом и не имеет реального родителя. Если вы думаете, что событие Unhandled будет передано новому окну (например, апплету), то вы ошибаетесь. Вам придется обрабатывать события на месте или в явном виде выдавать событие апплету:

public boolean handleEvent(Event evt) {

if (evt.id == Event.WINDOW_MOVED) {

}

else if (evt.id == Event.WINDOW_DESTROY) { dispose();

}

else if (evt.id == Event.ACTION_EVENT) {

if (evt.target instanceof MenuItem) { String st = (String) evt.arg; if (st.equals("Quit")) {

quit(); return true;

}

else if (st.equals("Login")) { login();

return true;

}

else if (st.equals("Open Schedule")) { open();

return true;

}

else if (st.equals("Server Name")) { snFrame sname = new snFrame("Change

Server",owner);

sname.init();

sname.show(); return true;

}

else if (st.equals("User Name")) {

unFrame uname = new unFrame("User Name",owner);

www.books-shop.com

uname.init();

uname.show(); return true;

}

else System.out.println("Menu: " + (String) evt.arg); return true;

}

}

return false;

}

}

Класс acFrame используется для того, чтобы добавить новые встречи в систему. Приведенная программа демонстрирует использование нестандартного менеджера размещения. Мы пользуемся пакетом менеджера TK. Если вам не нравится, как работает менеджер размещения в Java, вы можете написать свой собственный. Рассмотрим для примера ventana/awt/PackerLayout.java:

class acFrame extends Frame { ui owner;

Panel datePanel = new Panel(); Panel timePanel = new Panel();

Label mdLabel = new Label("Meeting Date"); TextField mdField = new TextField(7); Label tsLabel = new Label("Time Start"); TextField tsField = new TextField(4); Label endLabel = new Label("End"); TextField endField = new TextField(4); Panel dataPanel = new Panel();

Panel labPanel = new Panel(); Panel fieldPanel = new Panel();

Label descLabel = new Label("Description"); TextField descField = new TextField(40); Label attendLabel = new Label("Attending"); TextField attendField = new TextField(40); Label urlLabel = new Label("URL"); TextField urlField = new TextField(40); Panel buttonPanel = new Panel();

Button schedButton = new Button("Schedule"); Button cancelButton = new Button("Cancel"); public acFrame(String s, String user, ui owner) {

super(s); this.owner = owner;

}

public void init() {

setLayout(new PackerLayout()); datePanel.setLayout(new PackerLayout()); timePanel.setLayout(new PackerLayout()); datePanel.add("mdLabel;side=left",mdLabel); datePanel.add("mdField;side=left",mdField); timePanel.add("tsLabel;side=left",tsLabel); timePanel.add("tsField;side=left",tsField); timePanel.add("endLabel;side=left",endLabel); timePanel.add("endField;side=left",endField); dataPanel.setLayout(new PackerLayout()); labPanel.setLayout(new PackerLayout()); fieldPanel.setLayout(new PackerLayout()); dataPanel.add("labPanel;side=left",labPanel); dataPanel.add("fieldPanel;side=left",fieldPanel); labPanel.add("descLabel;anchor=w;ipady=3",descLabel);

labPanel.add("attendLabel;anchor=w;ipady=3",attendLabel);

labPanel.add("urlLabel;anchor=w;ipady=3",urlLabel);

fieldPanel.add("descField",descField);

fieldPanel.add("attendField",attendField);

www.books-shop.com

fieldPanel.add("urlField",urlField); buttonPanel.setLayout(new PackerLayout()); buttonPanel.add("schedButton;side=left;padx=5",schedButton); buttonPanel.add("cancelButton;side=left",cancelButton); add("datePanel;anchor=w;pady=5",datePanel); add("timePanel;anchor=w;pady=5",timePanel); add("dataPanel;anchor=w;pady=5",dataPanel); add("buttonPanel",buttonPanel);

}

}

Создание даже такой простой страницы - занятие достаточно нудное. С использованием PackerLayout это сделать проще, хотя программирование расположения элементов экрана не может быть увлекательным занятием. Новая интегрированная среда разработки (New Integrated Development System, IDEs) вскоре позволит перемещать компоненты в пределах экрана. Тогда будет создана программа AWT. Вам нужно будет только обрабатывать события и склеивать вместе программы.

Возможные улучшения

Мы создали только основу апплета планировщика встреч. Чтобы добавить в него все свойства, которые мы захотим, может потребоваться несколько недель. Окончательный планировщик может много чего уметь, и программа его будет очень длинной. Если вы хотели бы расширить возможности программы, вы можете обдумать следующие возможности:

Добавление общего вида еженедельника/ежемесячника.

Разрешение пользователю или компании задавать дни, когда пользователь занят, или выходные.

Указание группы людей, для которой компьютер будет подбирать подходящий временной интервал определенной длины.

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

Посылка некоторых сообщений по электронной почте:

Извещение пользователя, когда его расписание изменилось.

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

Разрешение планирования использования ресурсов - таких, как залы заседаний и аудио-, видеоаппаратура.

www.books-shop.com

Приложение А

О странице Online Companion

Знание - сила! Web-страница Online Companion соединит вас с лучшими информационными источниками в Интернет, посвященными программированию на Java.

Online Companion доступна по адресу http://www.vmedia.com/java.html. Некоторые из расположенных там ссылок указывают на другие полезные Web-страницы, архивы постоянно обновляющегося программного обеспечения и информацию о публикуемых издательством

Ventana книгах.

Online Companion соединит вас с книжным магазином, где вы найдете массу интересных предложений и новой продукции. Библиотека Ventana's Online Library позволит заказать интересующие вас книги прямо по сети.

Страница Online Companion постоянно развивается, на ней появляется все больше информации, указателей, справочников и разделов книг, опубликованных издательством.

Приложение В

Диск CD-ROM

В состав CD-ROM диска, прилагаемого к книге, входят коллекция апплетов вместе с примерами исходных текстов, рассматриваемых в книге, и несколько полезных программ-утилит.

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

Для Windows 95/NT. Дважды щелкните по пиктограмме CD-ROM и затем дважды щелкните по появившейся пиктограмме viewer.exe из окна Проводника или File Manager.

Для Macintosh. Дважды щелкните по пиктограмме CD-ROM и затем по пиктограмме Viewer.

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

Чтобы просмотреть примеры апплетов, необходимо указать на броузер, способный выполнять апплеты. Затем нужно нажать кнопку Launch.

СОВЕТ Для того чтобы вернуться в программу просмотра после того, как был вызван броузер, необходимо еще раз дважды щелкнуть мышью по viewer.exe. Если вы просматриваете текст апплета в текстовом редакторе, для возврата в главное меню CD-ROM необходимо нажать кнопку Main после закрытия текстового редактора.

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

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

www.books-shop.com