Monthly Archives: July 2014

Понимание Градла ́

Written by elwood

Когда я сталкиваюсь с новым языком, мне очень важно знать, что означает та или иная запись, как её правильно разбирать мозгом. Без этого не достичь понимания языка, всё время придётся копаться и шаманить. В случае с градло ́м это недопустимо, так как тыкания и шаманство могут занимать очень уж много времени. И в конце концов всё же придется плюнуть и досконально разобраться с тем, как работают сценарии сборки. Что со мной и произошло. Целый день я провёл в документации, на сетевых местах типа переполнения стека и в открытых источниках программы градл. Вот краткий пересказ усвоенного.

Итак, основной вопрос градла звучит так: что делает этот кусок кода ? То, как отвечать на этот вопрос, мы и рассмотрим.

База

Для начала чуть-чуть перескажу официальную документацию по DSL. Каждый сценарий суть код конфигурации некоторого объекта. Глобально есть три вида сценариев: Build script, Init script и Settings script. И конфигурируют они соответственно объекты типа Project, Gradle и Settings.

То есть на входе обычного build.gradle мы имеем экземпляр класса Project. У него есть методы и свойства, которые мы можем вызывать. Например, так:

version = '1.0' // Эквивалентно setVersion('1.0)

Или так:

repositories {
    mavenCentral()
}

Что происходит здесь ? Здесь вызывается метод Project.repositories(Closure configureClosure), в качестве Closure передаётся блок кода в фигурных скобках. Но этот блок вызывается уже не в контексте экземпляра Project, а в контексте экземпляра класса RepositoryHandler (это описано в документации к методу). Таким образом, этот блок конфигурирует RepositoryHandler. А у него есть метод mavenCentral(Map<String, ?> args), который добавляет центральный мейвеновский репозиторий.

В этом кусочке заключена основная идея градла – вы через пробел пишете Closure, который конфигурирует что-то. Внутри этого Closure вам доступны все методы и свойства конфигурируемого объекта. А для того, чтобы знать, с объектом какого класса вы имеете дело в конкретном блоке, нужно пользоваться DSL Reference и JavaDocs.

Но кроме этого есть ещё некий дополнительный синтаксис, который определяется специально. Например здесь мы видим, что после task идет hello, хотя, судя по сигнатуре метода, здесь должна быть строка. К сожалению, в JavaDocs это не описано, и нам нужно смотреть DSL Reference.

Погружение

enter image description here

Разберём более сложный пример – сборку WAR с включённой фильтрацией XML-ресурсов. Если вы имели дело с ant или maven, то вы наверняка сталкивались с этой задачей. В градле она записывается так:

apply plugin: 'war'
 
war {
    filesMatching('WEB-INF/*.xml', { FileCopyDetails fileDetails ->
        logger.error("File filtered: " + fileDetails.path)
        filter(ReplaceTokens, tokens: [
                "host"    : project.property("host"),
                "port"    : project.property("port"),
                "username": project.property("username"),
                "password": project.property("password")
        ])
    })
}

Как это работает ? Смотрим документацию по плагину War. Она говорит о том, что при подключении плагина в project добавляется задача war типа War. К сожалению, не указано, что блок после war конфигурирует именно тип War, но из контекста примера можно об этом догадаться. В документации к классу находим метод filesMatching(String pattern, Action<? super FileCopyDetails> action). Action, как и Closure, Groovy позволяет нам определять блоком. Блок конфигурирует объект класса FileCopyDetails (обратите внимание на декларацию в начале блока – она позволяет нам оставить себе на будущее напоминание о том, с объектом какого типа мы работаем в этом блоке). Ну а у класса FileCopyDetails есть метод filter(Map<String,?> properties, Class<? extends FilterReader> filterType). На самом деле там три сигнатуры этого метода, и внимательный читатель заметит, что ни одна из сигнатур не подходит к тому, что мы написали. Здесь срабатывает хитрый алгоритм разрешения методов груви. Дело в том, что вторым параметром мы передали не мапу, а использовали синтаксис именованных параметров. А груви обрабатывает вызовы, в которых использованы именованные параметры, особым образом – выполняя объединение всех именованных параметров в одну мапу и пихая её в метод первым фактическим аргументом. А все остальные аргументы идут дальше (хотя записаны в вызове они могут быть первыми). Таким образом, вызов получается эквивалентен следующему:

filter([tokens: [
    "host"    : project.property("host"),
    "port"    : project.property("port"),
    "username": project.property("username"),
    "password": project.property("password")
]], ReplaceTokens)

Очень непривычно работать с такими вещами, но это груви, и придётся привыкать.

Откуда взялось свойство war ?

И снова внимательные телезрители в недоумении – откуда же взялось свойство war? Ведь нельзя же просто так добавить свойство к классу Project! Ответ мы найдём в документации к DSL :

A project has 5 property ‘scopes’, which it searches for properties. You can access these properties by name in your build file, or by calling the project’s Project.property() method. The scopes are:

The extensions added to the project by the plugins. Each extension is available as a read-only property with the same name as the extension.

Так что плагин просто добавил себя в дополнительный синтаксис, обеспечиваемый встроенным поведением класса Project. Чуть дальше мы видим

The extensions added to the project by the plugins. Each extension is available as a method which takes a closure or Action as a parameter.

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

Оффтоп: почему “градло”??

Локальный мемчик. Один из наших программистов невзлюбил мейвен и называл его “мавно”. Забавное название пошло в народ, и теперь мы не только обзываем мейвен, но и зовём ant антлом, а gradle – градлом.

Подводим итоги

Итак, тезисно:
– Каждый script block что-то конфигурирует
– Что именно – смотрим в DSL Reference и Java Docs
– Может встречаться дополнительный синтаксис (apply plugin:, task myTaskName), инфа по нему должна быть в DSL Reference
– Нужно что-то похакать / внедриться в процесс ? Смотрим документацию и джавадоки по плагину и таскам, шаманим, дергаем методы
– Знание Groovy желательно
– Документация, документация, документация, исходники, документация

Синонимы в SOLR – заметки на бегу

Written by elwood

Прочёл статью http://nolanlawson.com/2012/10/31/better-synonym-handling-in-solr/. И есть некоторые мысли по этому поводу.

Парень пишет что можно включать SynonymFilterFactory на query и index. В query не работают синонимы из нескольких слов и неправильно бустятся документы, содержащие редкие синонимы (которые по идее должны быть внизу). А в index – ну понятно, распухает индекс, плохо ищется всё и подсветка лагает. Дока по солру таки-рекомендует юзать путь через index. В общем ему пришлось патчить query-парсер чтоб заработали синонимы из нескольких слов.

Моё мнение – в index нельзя врубать синонимы, потому что это концептуально неверно, и порет содержимое документов. Думаю, это в общем довольно сильно портит релевантность, и странно, что этот момент он не описал. Синонимы должны отрабатывать на query, конечно. То, что не пашут синонимы из нескольких слов – это баг солра, должно настраиваться так, чтобы такие синонимы тоже работали. Но в принципе этой штукой можно пренебречь, так как синонимов из 2 слов не больно и много (мы ж не Яндекс делаем). Плюс, он почему-то в качестве синонимов приводит странный пример “собака-дворняга” ну это какбэ не больно и синонимы. Синонимы в солре в моем понимании это вещи, которые на 99% эквивалентны. Айфон и iPhone. И для них проблема буста отпадает. А вообще для решения проблемы с бустом редких синонимов можно применить приём – скопировать поле в поле_exact и добавить по нему дополнительный вес. То есть если запрос точно совпадает с содержимым документа, этот документ будет выше. Соответственно, все синонимичные доки будут ниже, что и требуется.

Так что всё норм. Надо только научить солр парсить синонимы-фразы из запроса. Но в нашем случае даже это не нужно. А вот что реально нужно – учёт словоформ (стемминга) – почему-то у него не отражено в статье. Но в принципе у меня стоит стеммер по словарю OpenOffice + стеммер лайтовый, и он умеет понимать, что “айфонов” == “айфон” == “iPhone”.

Scala – первые впечатления

Written by elwood

Несколько дней ковыряю Scala. Делюсь первыми соображениями по этому поводу.

Отмеченные удобства Scala:

  • Лаконичный синтаксис: свойства, очень краткие определения бинов, вывод типов
  • Кортежи – и опять же в замечательном лаконичном синтаксисе
  • Анонимные функции, замыкания, вложенные функции
  • Immutable коллекции из коробки
  • Хороший плагин для IDEA (я использую IDEA 14 EAP)

Неудобства:

  • Медленная компиляция (хотя у меня классики-то микромаленькие ещё)
  • Хоть и хорошая, но всё-таки неидеальная поддержка в IDEA (подсказки включают в себя private методы/конструкторы, часто нет сообщения анализатора кода об ошибках, которые присутствуют, нельзя подсмотреть выведенный тип val/var. иногда кавардак в watches)
  • Нет break/continue (есть кривая эмуляция через бросание исключения)
  • Нет try-with-resources (есть кривая эмуляция, не включённая в стандартную библиотеку)
  • Mutable HashMap и Immutable HashMap – одинаковые имена классов. Неудобно
  • Нельзя сделать return в анонимной функции
  • Не поддерживается старый добрый цикл for со счётчиком
  • Нет операции инкремента и декремента
  • Конструкции вида while ((readed = inputStream.read(buffer)) != -1) не поддерживаются

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