Xtext 2.1 облегчает доступ к типам Java из вашего DSL; Вы можете найти некоторые параграфы в документации. В частности, новые возможности Xbase делают эту интеграцию еще более мощной!
Этот параграф в документации кратко описывает, как обращаться к элементам Java с использованием типов JVM, а затем посвящает гораздо больше места доступу к типам Java с помощью Xbase.
В этой статье я хотел бы задокументировать некоторые мои эксперименты / опыт доступа к типам JVM без использования Xbase и, в частности, не с использованием хорошего примера модели домена, но что-то еще более простое, чтобы я мог сосредоточиться на проблема использования JVM Types (а не с более сложными функциями самого DSL).
Таким образом, для этого простого эксперимента я буду использовать в качестве отправной точки пример Приветствие, т. Е. Самый базовый DSL, который вы получите при создании проекта Xtext внутри Eclipse. Итак, продолжайте и создайте такой проект (посмотрите строки, которые я использовал на скриншоте):
Значения по умолчанию для созданного проекта уже подходят для доступа к типам JVM, поэтому нам не нужно настраивать файл mwe2.
Теперь, следуя документации Xtext, мы импортируем пакет ecore, определяющий типы JVM, в наш HelloJvmTypes.xtext
import "http://www.eclipse.org/xtext/common/JavaVMTypes" as jvmTypes
и теперь мы готовы к доступу к JvmTypes из нашей грамматики.
Я не хочу иметь полезный DSL, я просто хочу поэкспериментировать с доступом к типам Java, поэтому скажем, я просто хочу закончить DSL, который позволяет мне писать предложения вроде
Hello world from java.util.List, org.eclipse.emf.ecore.EClass! Hello foobar from java.io.IOException!
и из этих предложений сгенерируйте некоторые классы Java, которые просто выводят одинаковые строки.
Поэтому мы изменим наш HelloJvmTypes.xtext следующим образом
grammar org.xtext.example.hellojvmtypes.HelloJvmTypes with org.eclipse.xtext.common.Terminals import "http://www.eclipse.org/xtext/common/JavaVMTypes" as jvmTypes generate helloJvmTypes "http://www.xtext.org/example/hellojvmtypes/HelloJvmTypes" Model: greetings+=Greeting*; Greeting: 'Hello' name=ID 'from' javaTypes+=[jvmTypes::JvmType|QualifiedName] (',' javaTypes+=[jvmTypes::JvmType|QualifiedName])* '!' ; QualifiedName: ID ('.' ID)* ;
Теперь мы можем восстановить все артефакты и запустить другой экземпляр Eclipse; здесь мы создаем новый проект плагина (скажем, «hellojvmtypes»), а в папке с исходным кодом мы создаем файл .hellojvmtypes (скажем, «My.hellojvmtypes»). Мы должны в конечном итоге с редактором, как на скриншоте
Обратите внимание, что также работает помощник по содержимому для типов Java! .
Также обратите внимание, что на самом деле будут видны только те типы Java, которые «достижимы» из пути к классам вашего проекта. Например, попробуйте использовать org.eclipse.emf.ecore.EClass, и вы получите ошибку (если вы создали простой проект плагина).
Теперь попробуйте добавить org.eclipse.emf.ecore в качестве зависимости в ваш файл MANIFEST проекта, и вы сможете получить доступ к его классам Java.
Прежде чем мы продолжим генерацию кода, давайте проведем некоторое модульное тестирование! Создайте класс Xtend2 в проекте плагина тестов org.xtext.example.hellojvmtypes.tests и добавьте в MANIFEST org.eclipse.xtext.xtend2.lib в качестве зависимости.
package org.xtext.example.hellojvmtypes.tests import com.google.inject.Inject import junit.framework.Assert import org.eclipse.xtext.common.types.JvmGenericType import org.eclipse.xtext.junit4.InjectWith import org.eclipse.xtext.junit4.XtextRunner import org.eclipse.xtext.junit4.util.ParseHelper import org.eclipse.xtext.junit4.validation.ValidationTestHelper import org.junit.Test import org.junit.runner.RunWith import org.xtext.example.hellojvmtypes.HelloJvmTypesInjectorProvider import org.xtext.example.hellojvmtypes.helloJvmTypes.Greeting import org.xtext.example.hellojvmtypes.helloJvmTypes.Model @InjectWith(typeof(HelloJvmTypesInjectorProvider)) @RunWith(typeof(XtextRunner)) class ParserTest { @Inject ParseHelper<Model> parser @Inject extension ValidationTestHelper @Test def void testParsingAndLinking() { parser.parse('''Hello foo from java.util.List!''').assertNoErrors } @Test def void testJvmTypeAccess() { val model = parser.parse( "Hello foo from java.util.List!") val greeting = model.greetings.head as Greeting val jvmType = greeting.javaTypes.get(0) as JvmGenericType Assert::assertEquals("java.util.List", jvmType.identifier) } }
Если вы запустите соответствующий сгенерированный класс Java в качестве теста Junit, вы увидите зеленую линию!
Теперь давайте напишем генератор, изменив класс xtend2, который Xtext уже создал для вас в вашем проекте:
package org.xtext.example.hellojvmtypes.generator import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IGenerator import org.eclipse.xtext.generator.IFileSystemAccess import org.xtext.example.hellojvmtypes.helloJvmTypes.Greeting import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.* import org.eclipse.xtext.xbase.compiler.ImportManager class HelloJvmTypesGenerator implements IGenerator { override void doGenerate(Resource resource, IFileSystemAccess fsa) { for(greeting: resource.allContentsIterable.filter(typeof(Greeting))) { fsa.generateFile( greeting.packageName + "/" + // package greeting.className + ".java", // class name greeting.compile) } } def compile(Greeting greeting) ''' «val importManager = new ImportManager(true)» «val mainMethod = compile(greeting, importManager)» package «greeting.packageName»; «IF !importManager.imports.empty» «FOR i : importManager.imports» import «i»; «ENDFOR» «ENDIF» «mainMethod» ''' def compile(Greeting greeting, ImportManager importManager) ''' public class «greeting.className» { public static void main(String args[]) { «FOR javaType : greeting.javaTypes» System.out.println("Hello «greeting.name» from " + «importManager.serialize(javaType)».class.getName()); «ENDFOR» } } ''' def className(Greeting greeting) { greeting.name.toFirstUpper } def packageName(Greeting greeting) { greeting.name.toLowerCase } }
Прежде чем объяснить код выше, давайте попробуем генератор: перезапустите ваш второй экземпляр eclipse и убедитесь, что в проекте, который вы создали с помощью файла hellojvmtypes, у вас есть папка с именем src-gen , и эта папка настроена как исходная папка.
Теперь вы должны увидеть код, сгенерированный автоматически в src-gen: в частности, вы окажетесь в конце каждого элемента Greeting с пакетом и классом, названным в соответствии с функцией имени Greeting (упакуйте все строчные буквы и имя класса с первой буквой капитал). Кроме того, сгенерированный метод main распечатает соответствующее приветствие.
Важными частями в генераторе являются следующие:
def compile(Greeting greeting) ''' «val importManager = new ImportManager(true)» «val mainMethod = compile(greeting, importManager)» package «greeting.packageName»; «IF !importManager.imports.empty» «FOR i : importManager.imports» import «i»; «ENDFOR» «ENDIF» «mainMethod» ''' def compile(Greeting greeting, ImportManager importManager) ''' public class «greeting.className» { public static void main(String args[]) { «FOR javaType : greeting.javaTypes» System.out.println("Hello «greeting.name» from " + «importManager.serialize(javaType)».class.getName()); «ENDFOR» } } '''
Здесь мы используем действительно классный класс, предоставляемый Xtext для генерации операторов импорта и, в частности, для генерации обращений к классам Java: цитирование из документации Xtext:
ImportManager укорачивает полностью квалифицированные имена, отслеживает импортируемые пространства имен, позволяет избежать конфликтов имен
Таким образом, если вы вызовите importManager.serialize (JvmType), у вас будет не только строка для переданного JvmType: ImportManager будет отслеживать оператор импорта, который необходимо будет добавить, чтобы гарантировать, что сгенерированная строка приведет к действительному Java-коду. Конечно, в случае конфликтов ImportManager сгенерирует полное имя Java (и оператор импорта не будет записан). Таким образом, в нашем генераторе кода мы сначала генерируем код для класса и метода main и сохраняем его в String;
def compile(Greeting greeting) ''' «val importManager = new ImportManager(true)» «val mainMethod = compile(greeting, importManager)» ... continued in (2)
во время генерации importManager записал требуемый импорт,
def compile(Greeting greeting, ImportManager importManager) ''' public class «greeting.className» { public static void main(String args[]) { «FOR javaType : greeting.javaTypes» System.out.println("Hello «greeting.name» from " + «importManager.serialize(javaType)».class.getName()); «ENDFOR» } } '''
таким образом, мы затем генерируем все операторы импорта Java, а затем фактический класс Java (который мы буферизировали в строку).
(2)... continuation package «greeting.packageName»; «IF !importManager.imports.empty» «FOR i : importManager.imports» import «i»; «ENDFOR» «ENDIF» «mainMethod» '''
Например, посмотрите, как ImportManager правильно (и прозрачно) обрабатывает возможные конфликты классов Java в сгенерированном коде (из-за URI класса, который появляется в разных пакетах).
Все идет нормально! Но что произойдет, если мы ссылаемся на тип Java, который, случайно, имеет то же имя класса, которое мы генерируем? Например, в папке src создайте класс hello.Foobar и обратитесь к нему в своем My.hellojvmtypes и посмотрите сгенерированный код… argh! Мы получаем ошибку!
Это потому, что ImportManager знает о типах, к которым вы обращаетесь при создании класса Foobar (в данном случае), но не о самом сгенерированном классе Foobar! Мы можем решить эту проблему, используя другую форму конструктора ImportManager
public ImportManager(boolean organizeImports, JvmDeclaredType thisType)
где вы указываете JvmDeclaredType элемента Java, который будет содержать доступ к элементам Java, которые мы генерируем через сам ImportManager. Таким образом, нам нужно только создать на лету JvmDeclaredType, соответствующий классу Java, который мы генерируем для элемента Greeting!
Вот модификация генератора:
def compile(Greeting greeting) ''' «val importManager = new ImportManager(true, createJvmType(greeting))» «val mainMethod = compile(greeting, importManager)» package «greeting.packageName»; «IF !importManager.imports.empty» «FOR i : importManager.imports» import «i»; «ENDFOR» «ENDIF» «mainMethod» ''' def createJvmType(Greeting greeting) { val declaredType = TypesFactory::eINSTANCE.createJvmGenericType declaredType.setSimpleName(greeting.className) declaredType.setPackageName(greeting.packageName) declaredType }
Теперь перезапустите другой экземпляр Eclipse, и заново сгенерируйте код для My.hellojvmtypes , и посмотрите теперь правильный сгенерированный класс Java!
Итак, это полная окончательная версия генератора:
package org.xtext.example.hellojvmtypes.generator import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IGenerator import org.eclipse.xtext.generator.IFileSystemAccess import org.xtext.example.hellojvmtypes.helloJvmTypes.Greeting import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.* import org.eclipse.xtext.xbase.compiler.ImportManager import org.eclipse.xtext.common.types.TypesFactory class HelloJvmTypesGenerator implements IGenerator { override void doGenerate(Resource resource, IFileSystemAccess fsa) { for(greeting: resource.allContentsIterable.filter(typeof(Greeting))) { fsa.generateFile( greeting.packageName + "/" + // package greeting.className + ".java", // class name greeting.compile) } } def compile(Greeting greeting) ''' «val importManager = new ImportManager(true, createJvmType(greeting))» «val mainMethod = compile(greeting, importManager)» package «greeting.packageName»; «IF !importManager.imports.empty» «FOR i : importManager.imports» import «i»; «ENDFOR» «ENDIF» «mainMethod» ''' def createJvmType(Greeting greeting) { val declaredType = TypesFactory::eINSTANCE.createJvmGenericType declaredType.setSimpleName(greeting.className) declaredType.setPackageName(greeting.packageName) declaredType } def compile(Greeting greeting, ImportManager importManager) ''' public class «greeting.className» { public static void main(String args[]) { «FOR javaType : greeting.javaTypes» System.out.println("Hello «greeting.name» from " + «importManager.serialize(javaType)».class.getName()); «ENDFOR» } } ''' def className(Greeting greeting) { greeting.name.toFirstUpper } def packageName(Greeting greeting) { greeting.name.toLowerCase } }
Вы можете найти источники для проекта hellojvmtypes на
https://github.com/LorenzoBettini/Xtext2-experiments
Надеюсь, вы найдете этот пост полезным и следите за новыми сообщениями о Xtext ?