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

Стартап Пишем плагин на Groovy

DPOH-VAR

Старожил
Пользователь
Баллы
153
Добрый день!

Я покажу, как можно написать свой плагин на Groovy.
Необходимо:
- Установленная IntelliJ IDEA не ниже 11й версии
- bukkit плагин GroovyLoader (cкачать с репозитория)
- Умение писать плагины на java
- Желание изучить / использовать язык groovy

Теория:

IntelliJ IDEA лучше всех подходит для groovy.
Можете использовать любую другую IDE на свое усмотрение.

Плагин GroovyLoader необходим для того, чтобы bukkit смог загрузить классы плагина, которые используют groovy. Он скачивает (если необходимо) все библиотеки groovy и подключает к своему загрузчику классов.
Ваш плагин на groovy должен иметь жесткую зависимость от GroovyLoader, иначе он не сможет загрузиться.

Язык groovy очень похож на java, но при этом вносит большое число синтаксического сахара.
В результате этого количество кода уменьшается в несколько раз, а разработка становится проще.
Подробнее тут: http://groovy.codehaus.org/
Из недостатков применения groovy нужно отметить, что становится проще допустить синтаксические ошибки.
Тут спасает только подсветка синтаксиса IDE и всплывающие подсказки.

Практика:

1) Создайте новый проект на Groovy
02-create-groovy.png
* Скачать JDK можно тут: http://www.oracle.com/technetwork/java/javase/downloads/index.html
* Скачать Groovy тут: http://groovy.codehaus.org/Download **
** Устанавливать groovy целиком (GROOVY_HOME, PATH и т.д.) не обязательно, если не хотите его использовать в других целях. Достаточно только наличия библиотек в проекте.

2) Добавьте в список зависимостей библиотеку bukkit
03-libraries.png
Добавить зависимости можно в настройках структуры проекта (Ctrl+Alt+Shift+S) или FIle -> Project structure...
Скачать библиотеку можно отсюда: http://dl.bukkit.org/ или http://repo.bukkit.org/

3) Создайте файл plugin.yml
ПКМ на директории -> New -> File
Желательно, чтобы файл располагался в директории ресурсов

4) заполните plugin.yml
YAML:
name: GroovyPlugin
version: 1.0
description: Плагин для тестирования груви
author: DPOH-VAR
main: ru.dpohvar.groovyplugin.Plugin
depend: [GroovyLoader]

commands:
  test-plugin:
    description: Тестирование работы плагина
Обязательно нужно указать зависимость от плагина GroovyLoader :)6:0)

5) Создайте главный класс плагина
ПКМ на пакете -> New -> Groovy class

После этого структура проекта будет выглядеть примерно так:
04-project-structure.png

Пример плагина:
Код:
package ru.dpohvar.groovyplugin

import org.bukkit.plugin.java.JavaPlugin
import static org.bukkit.ChatColor.*

class Plugin extends JavaPlugin {

    void 'onEnable' () {
        getCommand('test-plugin').executor = { sender, cmd, alias, args ->
            sender.sendMessage "${AQUA}Плагин ${UNDERLINE}работает!"
            return true
        }
    }
}

6) Настройте экспорт плагина
как на картинке ниже:
05-artifact.png
В качестве Output directory можно указать папку plugins локального сервера bukkit.
Архив плагина должен содержать plugin compile output а также plugin.yml
plugin.yml добавляется в архив вручную (значок [+] -> File )

7) Соберите проект
Собрать проект можно с помощью комбинации клавиш Ctrl + F9
jar архив должен собраться автоматически вместе с проектом

8) Запустите сервер bukkit
Теперь плагин на Groovy вполне рабочий.
06-console.png
 
Разработка идет быстрее за счет более легкого синтаксиса? Объясните мне этот момент, пожалуйста! Лично я не вижу ничего сложного в синтаксисе Java.
 
Разработка идет быстрее за счет более легкого синтаксиса? Объясните мне этот момент, пожалуйста! Лично я не вижу ничего сложного в синтаксисе Java.
Синтаксис Java не сложный, но громоздкий.
Java не является функциональным языком (Java 8 еще не у всех), поэтому некоторые действия приходится решать примитивно (преобразование массива, свертка, фильтр, карринг и т.д.).

Вот примеры на груви:
Код:
def blocks = region.getBlocks()
// найти и заменить все блоки воды на лаву
blocks.findAll {it.type == WATER} .each {it.type = LAVA}
Код:
//построчный список игроков в формате: Имя (жизни)
def message = Bukkit.onlinePlayers
    .collect {"$it.name (${it.health as int})"} 
    .join ('\n')
А еще из вкусностей у груви есть перегрузка операторов (спрашивали уже).
К тому же, существующим классам можно добавить новые методы с помощью метаклассов и расширений.
 
@DPOH-VAR
Т.е. получается, что можно взять, скажем класс Creeper добавить ему дополнительные методы, а какие-то существующие переопределить?!

Покажи примером, пожалуйста.

Вообще супер!
 
@DPOH-VAR
Покажи примером, пожалуйста.
Запросто!
Например, добавим ему метод superExplosion(float power)

Вариант 1, с метаклассами:
Код:
Creeper.metaClass.superExplosion = { float power ->
    def loc = (delegate as Creeper).location
    loc.world.createExplosion(loc.x, loc.y, loc.z, power)
}
Можно делать это динамически, в любой момент добавить новый метод и в любой момент его изменить.
Можно и переопределить методы. но тогда другие плагины, использующие груви, тоже начнут его вызывать. Поэтому не рекомендую так делать. Оригинальный метод останется доступен только через рефлексии (или java-код).
Недостаток в том, что IDE не будет подсвечивать синтаксис.

Вариант 2, с модулем расширения:
Нужно создать произвольный класс с одним методом:
Код:
static superExplosion(Creeper creeper, float power){
    def loc = creeper.location
    loc.world.createExplosion(loc.x, loc.y, loc.z, power)
}
А затем его специальным образом зарегистрировать
http://groovy.codehaus.org/Creating an extension module
Подсветка синтаксиса работает, IDE подсказывает эти методы.
Но переопределить оригинальный метод таким способом нельзя.

Bukkit не подгружает их своим загрузчиком (т.е. плевать он хотел на META-INF)
Можно загрузить любую другую jar с расширениями - тогда все отлично будет работать.
Хотелось бы хранить META-INF в самом плагине... над этим бьюсь.
И, собственно, добился.
Чтобы второй метод работал, это нужно вставить в главный класс плагина.
Код:
static {
    Map<CachedClass, List<MetaMethod>> map = [:]
    def classLoader = Plugin.classLoader
    def resources = classLoader.getResources(MetaClassRegistryImpl.MODULE_META_INF_FILE)
    resources.each { url ->
        def properties = new Properties()
        url.withInputStream { input ->
            properties.load input
            def registry = GroovySystem.metaClassRegistry as MetaClassRegistryImpl
            registry.registerExtensionModuleFromProperties properties, classLoader, map
        }
    }
    map.each { cls, methods -> cls.setNewMopMethods methods }
}
PS: спасибо гуглу и разработчикам grails

Вариант 3: с помощью примесей
Очень похож на первый способ, но сначала составляется расширяющий класс, а затем "вливается" в оригинальный. У него куча недостатков, поэтому даже не буду говорить о нем подробнее.
Вообще, в документации описано много способов расширения языка.
http://groovy.codehaus.org/User Guide в разделе Dynamic Groovy
 
Последнее редактирование:
Запросто!
Например, добавим ему метод superExplosion(float power)

Вариант 1, с метаклассами:
Код:
Creeper.metaClass.superExplosion = { float power ->
    def loc = (delegate as Creeper).location
    loc.world.createExplosion(loc.x, loc.y, loc.z, power)
}
Можно делать это динамически, в любой момент добавить новый метод и в любой момент его изменить.
Можно и переопределить методы. но тогда другие плагины, использующие груви, тоже начнут его вызывать. Поэтому не рекомендую так делать. Оригинальный метод останется доступен только через рефлексии (или java-код).
Недостаток в том, что IDE не будет подсвечивать синтаксис.

Вариант 2, с модулем расширения:
Нужно создать произвольный класс с одним методом:
Код:
static superExplosion(Creeper creeper, float power){
    def loc = creeper.location
    loc.world.createExplosion(loc.x, loc.y, loc.z, power)
}
А затем его специальным образом зарегистрировать
http://groovy.codehaus.org/Creating an extension module
Подсветка синтаксиса работает, IDE подсказывает эти методы.
Но переопределить оригинальный метод таким способом нельзя.

Bukkit не подгружает их своим загрузчиком (т.е. плевать он хотел на META-INF)
Можно загрузить любую другую jar с расширениями - тогда все отлично будет работать.
Хотелось бы хранить META-INF в самом плагине... над этим бьюсь.
И, собственно, добился.
Чтобы второй метод работал, это нужно вставить в главный класс плагина.
Код:
static {
    Map<CachedClass, List<MetaMethod>> map = [:]
    def classLoader = Plugin.classLoader
    def resources = classLoader.getResources(MetaClassRegistryImpl.MODULE_META_INF_FILE)
    resources.each { url ->
        def properties = new Properties()
        url.withInputStream { input ->
            properties.load input
            def registry = GroovySystem.metaClassRegistry as MetaClassRegistryImpl
            registry.registerExtensionModuleFromProperties properties, classLoader, map
        }
    }
    map.each { cls, methods -> cls.setNewMopMethods methods }
}
PS: спасибо гуглу и разработчикам grails

Вариант 3: с помощью примесей
Очень похож на первый способ, но сначала составляется расширяющий класс, а затем "вливается" в оригинальный. У него куча недостатков, поэтому даже не буду говорить о нем подробнее.
Вообще, в документации описано много способов расширения языка.
http://groovy.codehaus.org/User Guide в разделе Dynamic Groovy
:good:
 
Aw...
Нравится мне такое, но больше хотелось бы увидеть в чистой Java
А что вы там про 8 говорили?

За тему большое спасибо.
И если не секрет, то что за IDE? Intellij Idea?
Я так понял это Linux
 
А что вы там про 8 говорили?
Java 8 поддерживает лямбды. Это нечто похожее на замыкания в груви.
Лямбда выражения - это очень модно. EcmaScript 6, Java 8, Swift ... скоро они будут везде. Но это совсем другая история.
Чего нового есть в java 8: http://habrahabr.ru/post/216431/
Но я не советую переходить на нее. Рано. Еще не у всех фреймворков/плагинов есть поддержка.
Например, MCPC+ на Java 8 не может грузить многие Bukkit плагины. Grails вообще отказывается работать. Таких примеров множество.
И если не секрет, то что за IDE? Intellij Idea?
Я так понял это Linux
Intellij Idea 13.1.4 с темой Darcula, Ubuntu 14.04. Дело вкуса. Ничто не мешает скачать и установить IntelliJ Idea на Windows.
 
Java 8 поддерживает лямбды. Это нечто похожее на замыкания в груви.
Лямбда выражения - это очень модно. EcmaScript 6, Java 8, Swift ... скоро они будут везде. Но это совсем другая история.
Чего нового есть в java 8: http://habrahabr.ru/post/216431/
Но я не советую переходить на нее. Рано. Еще не у всех фреймворков/плагинов есть поддержка.
Например, MCPC+ на Java 8 не может грузить многие Bukkit плагины. Grails вообще отказывается работать. Таких примеров множество.

Intellij Idea 13.1.4 с темой Darcula, Ubuntu 14.04. Дело вкуса. Ничто не мешает скачать и установить IntelliJ Idea на Windows.
Спасибо за подробный ответ
Возможно ли использовать Груви вместе с Java в одном проекте? (Что-то по типу php в html)
 
Спасибо за подробный ответ
Возможно ли использовать Груви вместе с Java в одном проекте? (Что-то по типу php в html)
Да.
Исходники желательно разнести по разным директориям в зависимости от языка. gradle и maven, например, предлагают сделать так:
src/main/java/ - для java
src/main/groovy/ - для groovy
src/main/resources/ - для ресурсов

Трудности возникнут при сборке проекта с помощью maven (решается плагином, в 20 строк xml)
Если собирать с помощью gradle, нужно всего лишь добавить строчку:
apply plugin: 'groovy'

По типу php в html не получится: внутри .java файла нельзя встраивать код на groovy.

PS:
GroovyShell позволяет динамически подгружать скрипты на groovy и исполнять их.
В него можно передать набор переменных, которые будут в скоупе скрипта.
У него есть куча настроек (предопределенный импорт, права доступа, режим песочницы, и т.д.)
Но это уже идея для новой версии VarScript ;-)
 
Да.
Исходники желательно разнести по разным директориям в зависимости от языка. gradle и maven, например, предлагают сделать так:
src/main/java/ - для java
src/main/groovy/ - для groovy
src/main/resources/ - для ресурсов

Трудности возникнут при сборке проекта с помощью maven (решается плагином, в 20 строк xml)
Если собирать с помощью gradle, нужно всего лишь добавить строчку:
apply plugin: 'groovy'

По типу php в html не получится: внутри .java файла нельзя встраивать код на groovy.

PS:
GroovyShell позволяет динамически подгружать скрипты на groovy и исполнять их.
В него можно передать набор переменных, которые будут в скоупе скрипта.
У него есть куча настроек (предопределенный импорт, права доступа, режим песочницы, и т.д.)
Но это уже идея для новой версии VarScript ;-)
Где кнопка лайк на этом форуме? :rolleyes:
 
Небольшой хинт для тех, кто использует мавен:
Выкладываю примеры помки и plugin.yml

./pom.xml
XML:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- В разделе прапертей указываем некоторые данные -->
    <properties>
        <plugin-version>1.2</plugin-version> <!-- версия плагина -->
        <plugin-description>My simple plugin</plugin-description> <!-- описание плагина -->
        <plugin-author>DPOH-VAR</plugin-author> <!-- автор плагина -->
        <plugin-website>http://varscript.ru/artifactory/simple/release/ru/dpohvar/varscript/BukkitExt/</plugin-website>
        <java-version>1.7</java-version><!-- версия java -->
        <groovy-version>2.2.2</groovy-version><!-- версия groovy -->
        <bukkit-version>1.7.9-R0.2-SNAPSHOT</bukkit-version><!-- версия bukkit -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!-- кодировка файлов -->
    </properties>

    <!-- Ваши groupId, artifactId -->
    <groupId>ru.dpohvar.varscript</groupId>
    <artifactId>BukkitExt</artifactId>
    <version>${plugin-version}</version>
    <packaging>jar</packaging>

    <!-- Нам необходим репозиторий bukkit -->
    <repositories>
        <repository>
            <id>bukkit-repo</id>
            <url>http://repo.bukkit.org/content/groups/public/</url>
        </repository>
    </repositories>

    <!-- список зависимостей -->
    <dependencies>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>${groovy-version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.bukkit</groupId>
            <artifactId>bukkit</artifactId>
            <version>${bukkit-version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>

        <finalName>${artifactId}</finalName>

        <!-- настройка фильтра для ресурсов -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>

        <plugins>

            <!-- плагин для компиляции java -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java-version}</source>
                    <target>${java-version}</target>
                </configuration>
            </plugin>

            <!-- плагин компиляции groovy -->
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <id>compile</id>
                        <phase>compile</phase>
                        <configuration>
                            <tasks>
                                <mkdir dir="${basedir}/src/main/groovy"/>
                                <taskdef name="groovyc"
                                         classname="org.codehaus.groovy.ant.Groovyc">
                                    <classpath refid="maven.compile.classpath"/>
                                </taskdef>
                                <mkdir dir="${project.build.outputDirectory}"/>
                                <groovyc destdir="${project.build.outputDirectory}"
                                         srcdir="${basedir}/src/main/groovy/" listfiles="true">
                                    <classpath refid="maven.compile.classpath"/>
                                </groovyc>
                            </tasks>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <phase>test-compile</phase>
                        <configuration>
                            <tasks>
                                <mkdir dir="${basedir}/src/test/groovy"/>
                                <taskdef name="groovyc"
                                         classname="org.codehaus.groovy.ant.Groovyc">
                                    <classpath refid="maven.test.classpath"/>
                                </taskdef>
                                <mkdir dir="${project.build.testOutputDirectory}"/>
                                <groovyc destdir="${project.build.testOutputDirectory}"
                                         srcdir="${basedir}/src/test/groovy/" listfiles="true">
                                    <classpath refid="maven.test.classpath"/>
                                </groovyc>
                            </tasks>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>

                </executions>
            </plugin>

            <!-- плагин для составления groovydoc -->
            <plugin>
                <groupId>com.bluetrainsoftware.maven</groupId>
                <artifactId>groovydoc-maven-plugin</artifactId>
                <version>1.2</version>
                <executions>
                    <execution>
                        <id>site</id>
                        <phase>site</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <additionalSourceDirectories>
                        <additionalSourceDirectory>src/main/groovy</additionalSourceDirectory>
                    </additionalSourceDirectories>
                </configuration>
            </plugin>

        </plugins>
    </build>


</project>

./src/main/resources/plugin.yml
YAML:
name: ${artifactId}
version: ${version}
description: ${plugin-description}
author: ${plugin-author}
website: ${plugin-website}
database: false
main: ru.dpohvar.package.Plugin

Теперь plugin.yml будет зависеть от настроек прапертей в pom.xml.
Чтобы собрать плагин - достаточно выполнить mvn package, при этом в архив соберутся исходники ./src/main/java, ./src/main/groovy а также ресурсы /src/main/resources после обработки паттерна ${}
Для составления документации выполняем mvn site, но groovydoc иногда ведет себя непредсказуемо.
 
Последнее редактирование:
Есть еще язык Scala.
Очень сильно упрощает коддинг.
 
Есть еще язык Scala.
Очень сильно упрощает коддинг.
раз - http://dev.bukkit.org/bukkit-plugins/scala-loader/
два - http://dev.bukkit.org/bukkit-plugins/scala-plugin-api/
Уже давно есть загрузчики для скалы, может ими кто-то и пользуется.
Лично мне этот язык не приглянулся вообще никак.
Возможно скоро сделаю плагин, работающий с Ivy
чтобы скачивать и загрузать зависимости для остальных плагинов до их загрузки.
Хочу нечто похожее на Groovy Grape
плагин Hedera http://www.rubukkit.org/threads/79466/
Позволяет добавить в зависимости что угодно.
В принципе может заменить GroovyLoader, а также любые другие загрузчики, в том числе и Scala
 

Поделиться этой страницей

Назад
Верх