среда, 22 июня 2011 г.

Java i18n - никакого мошенничества, только ловкость рук

В очередной раз старушка джава подкидывает прикол. На этот раз история у нас с интернационализацией, или просто i18n.

Как мы все знаем, при использовании стандартного подхода, тексты сообщений для различных языков размещаются в .properties файлах. Для каждого языка создается файл с соответствующим суффиксом. Кроме того, заботливые разработчики джавы настоятельно рекомендуют в доках:
You should always create a default properties file.
И даже пример приводят, где английский вариант помещен в качестве дефолтного и, соответственно, варианта файла с суффиксом для английского языка нету. Вот, взято оттуда же:

The PropertiesDemo sample program ships with three properties files:

LabelsBundle.properties
LabelsBundle_de.properties
LabelsBundle_fr.properties

Отлично! - подумал я. - Всё понятно, предоставишь язык de - выберется LabelsBundle_de.properties, предоставишь язык en, LabelsBundle_en.properties найден не будет и выберется LabelsBundle.properties!

Переполненный оптимизмом и радостью я сделал свой вариант
Messages.properties
Messages_ru.properties
где в дефолтном файле поместил английские тексты.
Протестировал локально, все замечательно работает. Загрузил на сервак.
Каков же был размер кирпича, когда я увидел, что несмотря на самые отчаяннейшие попытки кликать по ссылке "en" я получал один и тот же - русский вариант перевода.

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

Conceptually, getBundle uses the following strategy for locating and instantiating resource bundles:

getBundle uses the base name, the specified locale, and the default locale (obtained from Locale.getDefault) to generate a sequence of candidate bundle names. If the specified locale's language, country, and variant are all empty strings, then the base name is the only candidate bundle name. Otherwise, the following sequence is generated from the attribute values of the specified locale (language1, country1, and variant1) and of the default locale (language2, country2, and variant2):

* baseName + "_" + language1 + "_" + country1 + "_" + variant1
* baseName + "_" + language1 + "_" + country1
* baseName + "_" + language1
* baseName + "_" + language2 + "_" + country2 + "_" + variant2
* baseName + "_" + language2 + "_" + country2
* baseName + "_" + language2
* baseName

Вот она роковая часть. Вызывая ResourceBundle.getBundle("Messages", new Locale("en")) и ожидая что подхватится безсуффиксный файл заместо отсутствующего английского, я не знал, что разработчики джавы слишком умные и прежде чем юзать безсуффиксный файл ищут вот это:

...the default locale (language2, country2, and variant2)

* baseName + "_" + language2 + "_" + country2 + "_" + variant2
* baseName + "_" + language2 + "_" + country2
* baseName + "_" + language2

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

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

вторник, 21 июня 2011 г.

matplotlib - чертим кодом

В гостях у нас новая модная фишка - библиотека питона для отображения 2D графиков и геометрических фигур. Я использовал ее для создания графиков в системе документирования проектов. Достаточно удобно, за исключением того факта, что отсутствуют какие-либо плагины для трака. Эх, как же было бы прекрасно писать в самой вики трака код, нажимать кнопку и получать готовую картинку. Как собственно я делал для графвиза. Но увы, на данный момент приходится сперва с помощью скрипта создавать изображение в каком-нибудь формате PNG, а уж затем впихивать эту картинку в трак. Может взять это за повод разработать плагин для трака?
Кстати раз уж упомянул, в чем же отличие Graphviz от matplotlib? Графвиз, как свидетельствует корень слова, специализируется на графиках. Это высокоуровневый движок предназначенный для отрисовки рафиков, описание которых не указывает форму отрисовки, лишь отношение между графами. Матплотлиб это более низкоуровневый движок, где отрисовка детально описывается кодом, и нарисовать можно все что угодно.

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

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

2. мучаем оси координат
По-умолчанию заботливый матплотлиб дарит нам отметки осей координат на всех четырех сторонах изображения. Что же можно с этим сделать...
a) убираем отметки (так называемые ticks) с верхней оси:

ax.xaxis.set_ticks_position('bottom')

где ax - объект типа matplotlib.axes.Axes , соответствующий метод для yaxis здесь.

б) убираем ось полностью

ax.yaxis.set_visible(False)

метод наследуется классом оси от класса matplotlib.artist.Artist

в) устанавливаем границы изображения по осям

plt.axis([0, 7, 0.5, 2.5])


г) указываем границы отметок-ticks и как их подписать

import pylab
...
pylab.xticks( pylab.arange(8), ('', 't1', '', 't2', '', 't1+d1','t2+d2') )

а что же это за модуль такой pylab? Это всего лишь процедурный интерфейс к другим различным модулям, в том числе к matplotlib и numpy. Достаточно посмотреть исходный код pylab чтобы увидеть все бесстыдство сплошных импортов и ни единого кусочка собственного кода. Однако это должно быть удобным. Так или иначе на моей убунточке матплотлиб установка включает в себя pylab, выбора тут нету.

д) указываем, где нужно разместить отметки-ticks (если дефолтные предположения матплотлиб рисуют ненужные тики или не рисуют нужные)

ax.xaxis.set_major_locator(matplotlib.ticker.IndexLocator(1,0))


3. меняем размеры изображения
Размеры картинки при работе с матплотлиб могут пониматься в двух смыслах. В векторном и в растровом. Изначально мы задаем размеры в векторном смысле. Что определяют эти размеры? Они определяют в основном относительное расположение элементом рисунка на координатной оси (при этом некоторые детали сохраняют свои размеры, например текст). Можно сказать что на этом этапе мы просто сжимаем/разжимаем координаты используя категории дюймов, сантиметров и тп. Второй и заключительный этап - преобразование вектора в растр, здесь мы просто указываем DPI (хотя наверное это правильнее было бы назвать pixels per inch) и все что мы наваяли в векторе преобразуется в растр. Вот кусок кода:

defaultSize = fig.get_size_inches() #получаем размер нашего изображения
pylab.savefig("plot01.png", bbox_inches="tight",dpi=75) #сохраняем как png с dpi 75

#уменьшаем изображение по вертикали, при этом текст например сохраняет свои размеры
fig.set_size_inches(defaultSize[0], defaultSize[1] * 0.4)
pylab.savefig("plot02.png", bbox_inches="tight",dpi=75) #вновь сохраняем с теми же dpi


Вот пока и всё. Читаем доки с сайта, смотрим питоновские help(...) и чертим с удовольствием!

пятница, 10 июня 2011 г.

Maven и забота о детях

У меня мультимодульный проект в мавене 2, в котором имеется главный родительский модуль типа pom. Для удобства управления версиями, само указание версии для дочерних модулей было опущено, что, как мы все знаем, приводит к наследования версии родительского модуля.
Однако хардкод версии все же прокрадывается в дочерние модули через секцию <parent>:
<parent>
    <artifactId>parent</artifactId>
    <groupId>com.project</groupId>
    <version>0.1-SNAPSHOT</version>
</parent>
Да-да, именно так. Мы вынуждены указывать версию родителя, которую используем. Возможно кому-то и посчастливиться воспользоваться этой фичей возможности использования разных версий родителя, но это явно не для моего быдло проектика. Я всегда хочу использовать последнюю версию родителя.
И вот мы наконец подошли к проблеме. При обновлении версии в родительском поме, как обновить ссылку на версию родителя во всех дочерних помах?

Добрый товарищ dwh указал на maven-release-plugin и его возможность release:update-versions. Справедливости ради стоит отметить что такое же решение предлагают и на форумах. Но сразу же появились проблемы. Сначала оказалось, что в моем супер поме указана староватая бета-версия релиз плагина, не имеющая указанной возможности. После попытки обновится на более новую релиз плагин начал капризно требовать scm настроек, которые мне совершенно не сдались. В итоге пришлось отказаться от идеи релиз плагина.

Намного лучшим решением оказался плагин versions-maven-plugin из проекта Mojo. Этот плагин и специализируется на работе с версиями (в отличие от релиз плагина, первоочередная задача которого работа с релизами). Добавив его (versions плагин) в plugins секцию родительского пома, а затем вызвав mvn versions:update-parent -DallowSnapshots=true я решил свою проблему! :D Опция allowSnapshots=true позволяет работать в том числе и с snapshot версиями, без этой опции snapshot версии будут игнорироваться.