четверг, 9 декабря 2010 г.

Работа с RMI

Как клиент, так и сервер должны иметь доступ к интерфейсу, реализуемому классом удаленного объекта.
Сервер должен привязать объект, к которому будут производиться обращения, к RMI регистру (по сути, работает также, как urls.py в Django - создается словарь (хэш, мап, нужное подчеркнуть, недостающее вписать), содержащий пары адрес - название). При желании можно сделать привязку к уже существующему регистру.
Клиент должен выполнить следующие шаги:
  • получить адрес следующего типа:

"rmi://<адрес хоста>:<номер порта>/<имя объекта>";
  • получить ссылку на объект с помощью метода Naming.lookup(<адрес из предыдущего пункта>);
  • если имеющийся объект является экземпляром интерфейса, реализуемого серверным классом, перейти к следующему шагу, иначе возбудить исключение;
  • вызывать нужные нам методы так же, как если бы объект был локальным, при этом нужно учитывать, что все методы удаленного класса (за исключением main) обязаны возбуждать исключение RemoteException, соответственно, его нужно обрабатывать.

воскресенье, 5 декабря 2010 г.

Создание проекта Java средствами shell

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

#!/bin/sh
mkdir $1 && \
cd $1 && \
touch ${1}.java && \
touch myscript && \
echo '#!/bin/sh\njavac '${1}.java' && java '${1} > myscript
chmod +x myscript

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

Работа с базами данных в Java

Используемые сокращения:
  • БД - база данных;
  • СУБД - система управления базами данных;
  • API - Application Programming Interface, интерфейс программирования приложений;
  • ODBC -
  • SQL - Structured Query Language, структурированный язык запросов
Клиентское приложение может общаться с БД через т.н. драйвер БД. Здесь написать о разновидностях драйверов. Приложения Java используют для работы интерфейс JDBC (Java Database Connectivity), предоставляющий API для работы с различными СУБД. В библиотеках J2SE поставляется мост JDBC-ODBC, но предпочтительным является использование JDBC драйвера, поставляемого для конкретной СУБД. Дело в том, что ODBC был разработан с условиями максимальной переносимости между различными СУБД, что пагубно сказалось на производительности. Драйвера, написанные для конкретной СУБД, как правило, избавлены от этого недостатка. Рассмотрим общую последовательность работы Java-приложения с БД. Во-первых, все приложения, использующие SQL, должны использовать классы из пакета java.sql, поэтому их нужно импортировать: import java.sql.*; Затем, процедура, работающая с SQL, как правило, должна быть объявлена как возбуждающая исключение SQLException, примерно следующим образом: public static void main(String[] args) throws SQLException Теперь можно открывать соединение. Это делается следующим образом: Connection con = DriverManager.getConnection("jdbc:sybase:Tds:<хост>:<порт>/<база>","имя","пароль"); Следующим шагом является создание объекта Statement либо PreparedStatement. PreparedStatement отличается от Statement тем, что предоставляет возможность параметризованных запросов. Эти объекты предназначены для выполнения статических операторов SQL и возврата генерируемых результатов. //Вариант 1 Statement statement = con.createStatement(); //Вариант 2 PreparedStatement preparedStatement = con.prepareStatement(sql); Затем выполняется запрос, результаты которого при желании могут возвращаться в экземпляр класса ResultSet. //Вариант 1.1 statement.execute(sql); ResultSet resultSet = statement.getResultSet(); //Вариант 1.2 ResultSet resultSet = statement.executeQuery(sql); //Вариант 2 ResultSet resultSet = preparedStatement.getResultSet(); Для полученного объекта ResultSet доступны следующие основные операции: 1. Получение количества столбцов: int columnCount = metaData.getColumnCount(); 2. Получение метаданных о столбце (для всех процедур параметр i представляет собой номер столбца, допустимый диапазон его значений - от 1 до значения, полученного при вызове metaData.getColumnCount() 2.1 Получение заголовка столбца: metaData.getColumnLabel(i); 2.2 Получение максимальной ширины отображения столбца: metaData.getColumnDisplaySize(i); 3. Получение кортежа данных (необходимо перед выполнением операций с данными, например, получения значения конкретного столбца): resultSet.next(); 4. Получение значений столбцов: resultSet.getObject(i); (i - аналогично операции 2) Метод getResultSet может возвращать, кроме ResultSet'а, значение null или количество операций обновления, которое может быть получено методом getUpdateCount. В случае, если getResultSet вернул null, можно проверить наличие дополнительных результатов запроса вызовом метода getMoreResults, возвращающего булево значение (true или false). После использования созданные объекты - Connection, Statement, ResultSet - должны быть освобождены вызовом метода close(), причем в порядке, обратном порядку создания: resultSet.close(); statement.close(); con.close();

четверг, 18 ноября 2010 г.

Конвертация tiff в pdf

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

Здесь нам приходит на помощь утилита tiffset, находящаяся в пакете libtiff-tools. Устанавливается этот пакет обычным способом:

# aptitude install libtiff-tools

С помощью утилит tiffdump и tiffinfo мы можем посмотреть информацию о файле, причем tiffdump выдает подробную информацию, содержащую:
- имя поля;
- числовой идентификатор поля;
- тип данных поля;
- count (если кто-нибудь знает, что это такое, напишите, буду очень благодарен);
- значение.

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

262     0x0106     PhotometricInterpretation     Используемая цветовая модель.
282     0x011A     XResolution     Количество пикселей в ResolutionUnit строки.
283     0x011B     YResolution     Количество пикселей в ResolutionUnit столбца.

Первое поле сканирующие программы зачастую просто пропускают. Программы просмотра изображений используют по умолчанию значение 1, но программы преобразования ругаются на его отсутствие.
XResolution и YResolution - поля, определяющие количество точек на ResolutionUnit, как правило - на дюйм. Часто встречаются многостраничные tiff-файлы, в которых первая страница имеет разрешение 300x300, а все остальные - 96x96. Почему так получается - я не знаю.

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

$tiffset -s <идентификатор тэга> <присваимое значение> <имя файла>

Например, чтобы изменить цветовую модель, нужно выполнить следующую команду:

$tiffset -s 262 1 image.tif

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

$tiffsplit image.tif

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

$for file in `ls xaa*.tif`; do tiffset -s 262 1 $file; done

Объединить несколько файлов в один можно с помощью команды tiffcp:

$tiffcp *.tif newfile.tif

Теперь полученный файл можно преобразовать, например, в pdf:

$tiff2pdf newfile.tif > newfile.pdf

В данной команде используется оператор перенаправления вывода ">", так как программа tiff2pdf выводит полученные двоичные данные на стандартный вывод

среда, 17 ноября 2010 г.

Задачная задача

Над этой задачей я бился 3 дня :-[
Есть таблица, содержащая дерево меню:

   create table menu( id     int primary key,  -- ID пункта меню
                      parent int null,         -- ID "родителя" (null для пунктов верхнего уровня)
                      name   char(30) )        -- Наименование пункта меню

Разработать сохраненную процедуру, позволяющую получить информацию о данном меню в следующем виде:

   Level    ID     Name
   ------   ----   ------------------------------------
   0        1      Меню 1
   1        5      Меню 1.1
   1        6      Меню 1.2
   2        10     Меню 1.2.1
   0        2      Меню 2
   0        3      Меню 3
   1        8      Меню 3.1
   2        12     Меню 3.1.1
   2        13     Меню 3.1.2
   1        9      Меню 3.2
   ...

   Т.е., пункты меню должны быть отсортированы так, чтобы при их обработке клиентское приложение могло построить меню за "один проход". Каждая следующая запись должна быть либо подпунктом предыдущей (если ее уровень вложенности больше, чем у предыдущей), либо иметь общего родителя с предыдущей записью того же уровня вложенности.

Решение основано на создании суррогатного ключа, представляющего собой конкатенацию (склейку) строковых представлений ключей записей, являющихся "предками" данной записи
Например, для приведенной выше выборки данных ключи будут следующими:

synt        id           name                           
----------------------------------
1             1            Меню 1                         
15           5            Меню 1.1                       
16           6            Меню 1.2                       
1610       10          Меню 1.2.1                     
161011   11          Меню 1.2.1.1                   
2             2            Меню 2                         
3             3            Меню 3                         
38           8            Меню 3.1                       
3812       12          Меню 3.1.1                     
3813       13          Меню 3.1.2                     
39           9            Меню 3.2 

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

Итак, поехали:

CREATE PROCEDURE MyProc
AS
/* Первым делом создаем временную таблицу, включающую в себя поля
исходной таблицы плюс синтетический ключ и число уровней вложенности*/
CREATE TABLE #temp
(id int NULL,
parent int NULL,
synt varchar(255),
name char(30),
lev int
)

/*
Инициализируем таблицу значениями из имеющейся таблицы menu.
Синтетический ключ изначально представляет собой строковое представление первичного ключа.
*/
INSERT INTO #temp
SELECT id, parent, convert(varchar, id) AS synt, name, 0
FROM menu

WHILE ((SELECT COUNT(parent) FROM #temp) > 0)
BEGIN
/* "Переходим" на родительскую запись */
    UPDATE #temp
        SET id = parent
/* Изменяем указатель родительской записи */
    UPDATE #temp
        SET #temp.parent = (SELECT parent FROM menu WHERE menu.id = #temp.parent)
/* Добавляем идентификатор текущей записи к синтетическому ключу */
    UPDATE #temp
        SET synt = convert(varchar,id) + synt
/* Если мы еще не добрались до конца, увеличиваем уровень вложенности */
    UPDATE #temp
        SET lev = lev + 1
    WHERE (id IS NOT NULL)
END
/* Собственно, выборка данных */
SELECT #temp.lev,menu.id,menu.name
FROM menu, #temp
WHERE menu.name = #temp.name
ORDER BY synt
/* Не забываем убирать за собой! */
DROP TABLE #temp