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»). Мы должны в конечном итоге с редактором, как на скриншоте




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 класса, который появляется в разных пакетах).


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 ?
