HotLog

среда, 29 декабря 2010 г.

Пара regexp'ов для чистки проекта

Удаление строки author из javadoc:
/\*\*(\n.*$)*?(\n.*author.*$)(\n.*$)*?\n.*\*\/

Удаление пустых многострочных комментов:
\/\*\*\s*(\*\s*)*\*\/

вторник, 16 ноября 2010 г.

Spinner с четырьмя кнопками.

Понадобилось сделать спиннер, но с четырьмя кнопками. Две - для изменения значения на единицу и еще две - для изменения на 10.


Рассматривал два решения: поместить в спиннер в качестве эдитора другой спиннер или переписать BasicSpinnerUI (именно оно отвечает за создание и размещение компонентов спиннера). Остановился на втором.
Если наследоваться от BasicSpinnerUI, то получается все довольно просто. В installUI() добавляем на спиннер еще пару кнопок, а в createLayout() возвращаем наш layout, способный правильно расположить на пару кнопок больше.

И вот так можно протестировать:

пятница, 1 октября 2010 г.

Double-check locking.

Идея довольна проста. Пусть у нас есть Singleton, который дорог в создании. Создавать его может потребоваться, а может и нет. Хорошая идея создавать в тот момент, когда он понадобился в первый раз. Получаем код вроде такого:



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

Неправильное, но интуитивное решение:



Почему же оно неправильное? Причина кроется в специфике Java Memory Model, а именно в том, что обычные (не-volatile) переменные не являются точкой синхронизации.

Пусть у Singletona есть важное для нас поле value:



В худшем случае может происходить следующее:


NNпервый потоквторой поток
1 - getSingleton()
2 - singleton = new Singleton() и value=42
3 - переменная singleton скидывается в основную память
4getSingleton() -
5так как переменная singleton не null, в synchronized блок не заходим
6singleton.value==0 (значение по умолчанию, так как новое значение не было записано еще в основную память)-

Все потому, что для не-volatile переменных не гарантируется, что "happens-before order". Нужна некая точка синхронизации. Так как мы хотим отказаться от использования мониторов, то решение: пометить как volatile переменную singleton.

суббота, 18 сентября 2010 г.

Проверка SwingThreadPolicy в рамках всего приложения.

Идея в использовании JavaAgent и инструментаций. Мы добавляем к коду методов проверяемых классов код, проверяющий поток. В данном случае мы инструментируем все наследники java.awt.Component.

Код написан на скале:





пятница, 3 сентября 2010 г.

Написание собственного TableCellEditor на базе JSpinner.

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

  1. По окончанию редактирования editor обязан вызывать на всех слушателях либо editingStopped либо editingCanceled
  2. Editor должен закончить редактирование (см. предыдущий пункт), если на нем вызвали метод cancelCellEditing или если он возвращает true на вызов метода stopCellEditing
  3. Если editor сигнализирует об окончании редактирования вызывая на слушателях editingStopped, это означает, что он готов предоставить новое значение по вызову метода getCellEditorValue
  4. Если editor сигнализирует об окончании редактирования вызывая на слушателях editingCanceled, это означает, что у него нет нового значения (и в модель ничего нового проставлять не надо)
  5. Так как таблица удалит своего слушателя сразу после того, как будет просигнализировано об окончании редактирования, можно не бояться вызывать на всех слушателях editingStopped или editingCanceled по несколько раз. Это позволит сделать код более понятным и простым.


Таким образом, если на editor'e вызвали метод cancelCellEditing, он обязан уведомить всех слушателей об окончании редактирования до выхода из метода (напоминаю, swing однопоточный). То же верно, если он собирается вернуть true из stopCellEditing.

Класс:


И тест к нему (требуется hamcrest 1.2, jmock, junit, fest-swing):

вторник, 31 августа 2010 г.

Получение данных из Swing UI.

Как известно, SwingWorker предназначен для получения данных в background-потоке и последующей их передаче в EDT-поток.
Часто возникает и обратная задача: находясь в не-EDT потоке получить данные из UI. Типичный пример: отправляем введенные пользователем данные на сервер, происходит ошибка, надо спросить пользователя, что делать дальше: Retry/Ignore.
Для решения этой проблемы есть замечательный класс GuiActionRunner из библиотеки FEST-Swing. Пример использования:



Внимание: библиотека предназначена для тестирования. Как она поведет себя в продакшине - непонятно. Хотя никто не мешает выдрать несколько нужных классов, проревьюить, покрыть тестами и использовать в проекте.

суббота, 28 августа 2010 г.

Java Memory Model

Данная статья является пересказом-переводом JSR-133 FAQ

Что такое Memory Model?

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

Почему все так сложно?

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

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

Если с точки зрения потока, выполняющего метод test ничего не изменилось, то другой поток может заметить, что x стало равным 4 раньше, чем переменной b было присвоено значение 3. Для устранения подобных двусмысленностей введены правила очередности, которые описывают, какие из действий в прошлом окажутся видимыми для данного потока в настоящем.

Правила очередности.

Пусть в момент времени T1 было произведено действие Д1, а в некоторый момент времени T2>T1 произведено действие Д2. Если Д1 было, например, запись в переменную, а Д2 - чтение из этой же переменной, то в общем случае не гарантируется, что Д2 прочитает новое значение. То есть не гарантируется, что выполнено отношение "Д2 после Д1". Следующие правила определяют, в каких конкретных случаях такое отношение гарантируется.

  1. В рамках одного потока: Действие Д1 происходит перед действием Д2, если в программе действие Д2 стоит позже, чем Д1. Это означает, что инструкции могут переставляться только так, что бы из программы это определить было невозможно (речь идет только об одном потоке).Пример

    Порядок присвоения значений переменным a и b может быть произвольным, но гарантируется, что при вычислении значения для переменной x все предыдущие значения будут видны (и x будет равен 5).

  2. Освобождение монитора происходит всегда раньше, чем последующий захват того же самого монитора. Это означает следующее: Пусть поток П1 произвел изменения И1, захватил монитор M, произвел изменения И2 и освободил монитор М. Пусть после этого поток П2 захватил тот же самый монитор M. Тогда потоку П2 будут видны все изменения, которые произвел поток П1: как И1 так и И2.

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

  4. Вызов метода start() у потока происходит всегда до того, как произойдут любые действия в новом потоке. Это означает, что на момент старта потока он синхронизирован с тем потоком, которые его стартовал.

  5. Все инструкции в потоке гарантированно происходят до того, как метод join() на этом потоке успешно возвратится. Это означает, что если поток П1 ожидает поток П2 методом П2.join() и join() успешно завершается, то все изменения, произведенные П2, доступны из П1.

  6. Присвоение значений final-полям происходит до того, как объект будет нормально создан. Слово "нормально" здесь означает, что ссылка на объект не "утечет" из конструктора. Пример:

    Здесь ссылка на класс утекает через публикацию ее в статик-поле instance. В промежутке между (1) и (2) до поля instance.a может дотянуться другой поток и увидеть, что а==0, а еще через некоторое время увидеть, что final поле a изменило свое значение на a==5. Что бы этого не произошло, никогда не публикуйте ссылку на еще не созданный объект.

  7. Если final-поле является ссылкой на объект O1, то так же гарантируется, что после нормального завершения конструктора, все поля объекта O1 так же будут видны всем потокам. Таким образом, если final-поле является ссылкой, например, на массив, не нужно беспокоиться, что другие потоки увидят правильно значение ссылки, но неправильно содержимое массива ("правильное" здесь означает "правильное на момент завершения конструктора", а не "последнее присвоенное").

среда, 25 августа 2010 г.

Bean Validation, JSR 303. и Hibernate Validator.

Прослушал лекцию Васильева Михаила "Спецификация на программный интерфейс JSR 303, "Bean Validation" и ее реализация "Hibernate Validator".

Рассказ показался достаточно интересным.

В настоящий момент можно посмотреть в записи
https://academyit.webex.com/academyit/playback.php?FileName=http%3A//academy.it.ru/ru/videosem/Java.wrf

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

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

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

вторник, 24 августа 2010 г.

JMock. Делегирование mock-методов объекту.

Общее правило организации тестирования такое: Если объект A использует объект B и объект B уже протестирован, то не надо при тестировании объекта А тестировать объект B. Надо тестировать только соблюдение предусловий по отношению к объекту А.

Допустим, A - это какая-то модель таблицы (реализация TableModel). B - контроллер, который заполняет модель А данными.
Если А протестирована, то остается проверить, что контроллер будет общаться с А только в edt-потоке (см. предыдущую статью).

Раз в поведении модели А мы уверены, то не нужно городить кучу Expectations, а достаточно проверить поток и делегировать выполнение метода в А. Для этого создаем вспомогательный Action:



И вот так им воспользуемся:



Небольшая тонкость: что бы случайно не передать в конструктор B не мок, а оригинал, мы требуем, что бы к моку обратились хотя бы один раз.

понедельник, 23 августа 2010 г.

Проверка SwingThreadPolicy с помощью JMock

Как известно, все операции с Swing компонентами надо осуществлять в особом EDT (Event Dispatcher Thread) потоке. Таким образом, при тестировании GUI-приложения необходимо проверять также и соблюдение Swing Thread Policy.
Обратно, обращение ко всем методам, способным вызвать блокировку, необходимо проводить в потоке, отличном от EDT во избежании подвисания графического интерфейса.

Для примера возьму задачу о получении данных откуда-то (возможно зависание) и передача их в swing-подсистему. Для этого определим два интерфейса:

Для отображения данных в swing-компоненте


И для получения данных откуда-то:



А вот и класс, который надо протестировать:




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

Непосредственно для тестов нам понадобятся два вспомогательных класса:






А вот и сам тест:




Собственно и все. Небольшая тонкость: метод doAll(Action... actions) возвращает значение от последнего Action в списке параметров, так что returnValue должен стоять последним.

JMock и Hamcrest 1.2 под maven.

В hamcrest версии 1.2 появились весьма нужные классы, упрощающие тестирование.
Мне к примеру очень полюбился матчер SamePropertyValuesAs, позволяющий легко и просто тестировать прилетающие в листнер события.

Однако, разработчики hamcrest'a не спешат выкладывать версию 1.2 в центральный мавеновский репозиторий. Как следствие, у jmock в зависимостях стоит hamcrest версии 1.1, что грустно.

Но выход есть!




Мы исключаем hamcrest из транзитивных зависимостей для jmock и добавляем его нужной версии в зависимости нашего проекта.
Не забудьте подключить репозитарий (http://forumarchivebuilder.googlecode.com/svn/repository).