diff --git a/.gitignore b/.gitignore index c561386df8..27d089948c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,3 @@ *.java._trace *.smap *.checkbin -*.xtendbin -/*/xtend-gen/* -!/*/xtend-gen/.gitignore diff --git a/AGENTS.md b/AGENTS.md index bd47b14a61..d4ba2295da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ This document helps AI coding agents work effectively with the DSL DevKit codeba - **Java**: 21+ - **Maven** - **Tycho** -- **Xtext/Xtend** +- **Xtext** ## Key Directories @@ -50,7 +50,7 @@ export WORKSPACE=$(pwd) ### PMD - **Ruleset**: `ddk-configuration/pmd/ruleset.xml` -- Excludes: `src-gen/`, `src-model/`, `xtend-gen/` +- Excludes: `src-gen/`, `src-model/` ### Checkstyle - **Config**: `ddk-configuration/checkstyle/avaloq.xml` @@ -79,10 +79,6 @@ Tests are disabled by default and activated only in test bundles. These directories contain generated code - do not edit manually: - `src-gen/` - Xtext generated sources - `src-model/` - EMF model generated sources -- `xtend-gen/` - Xtend transpiled Java sources - -### Xtend -- `.xtend` files in `src/` compile to Java in `xtend-gen/` ## Common Tasks @@ -95,7 +91,7 @@ These directories contain generated code - do not edit manually: ### Fixing PMD Violations 1. Check ruleset at `ddk-configuration/pmd/ruleset.xml` -2. Violations in generated code (`src-gen/`, `xtend-gen/`) are excluded +2. Violations in generated code (`src-gen/`) are excluded 3. Run `mvn pmd:check -f ./ddk-parent/pom.xml` to verify fixes ### Fixing Checkstyle Violations @@ -116,3 +112,4 @@ xvfb-run mvn verify -f ./ddk-parent/pom.xml -pl :com.avaloq.tools.ddk.xtext.test - **Platform**: GitHub Actions - **Workflow**: `.github/workflows/verify.yml` - Triggers on: push to master, pull requests + diff --git a/HANDOVER.md b/HANDOVER.md new file mode 100644 index 0000000000..541d52a79f --- /dev/null +++ b/HANDOVER.md @@ -0,0 +1,118 @@ +# Handover — Fix All PMD & Checkstyle Violations for Xtend-to-Java Migration + +*Generated: Sun Mar 1 23:26 CET 2026* +*Branch: feature/xtend-to-java-migration* +*Last commit: 645407dd5 — fix: resolve BasicEList compilation error in XbaseGeneratorFragmentTest* + +## What We Were Working On + +Resolving all PMD and Checkstyle violations introduced by the Xtend-to-Java migration on the `feature/xtend-to-java-migration` branch. The migration converted ~90 Xtend files to Java 21 across the entire dsl-devkit project, and the auto-generated Java code had widespread style violations that failed CI. + +The initial plan estimated ~542 violations across 12 files in 4 modules. In practice, violations were discovered iteratively across **10+ modules and 36 files**, totaling roughly 600+ violations. + +## What Got Done + +- [x] Fixed all PMD and Checkstyle violations — CI is fully green (all 3 jobs: maven-verify, pmd, checkstyle) +- [x] 4 commits covering the fixes: + - `18fb5a34e` — bulk fix across 33 files in 10 modules + - `8d4d826d7` — StringToString, UnnecessaryCast, MissingOverride, LooseCoupling + - `ab0d6df10` — UseCollectionIsEmpty in PropertiesInferenceHelper + - `645407dd5` — BasicEList compilation error in XbaseGeneratorFragmentTest +- [x] All pushed and CI confirmed green (run 22551555855) + +### Modules touched: +- `check.core` (8 files) — largest module, ~510 violations +- `check.core.test` (10 files) — ~150 violations +- `check.ui` (2 files) +- `check.ui.test` (1 file) +- `checkcfg.core` (4 files) +- `checkcfg.core.test` (4 files) +- `xtext.format.generator` (1 file) +- `xtext.export.generator` (1 file) +- `xtext.generator.test` (1 file) +- `xtext.generator` (1 file) + +### Fix categories applied: +- **FinalParams** (~354): Added `final` to method parameters +- **UnnecessaryReturn** (~53): Removed trailing `return;` in void dispatch methods +- **MethodName** (~27): Class-level `@SuppressWarnings({"checkstyle:MethodName"})` for dispatch methods (`_format()`, `_scope()`, etc.) +- **AppendCharacterWithChar** (~20): `.append("x")` → `.append('x')` +- **MultipleStringLiterals** (~32): `CHECKSTYLE:CONSTANTS-OFF/ON` wrappers +- **MemberName** (~6): Renamed `_fieldName` → `fieldName` +- **MissingOverride**: Added `@Override` annotations +- **LooseCoupling**: Changed implementation types to interfaces (`BasicEList` → `EList`, `TreeMap` → `Map`, `ArrayList` → `List`) +- **InsufficientStringBufferDeclaration**: Increased `StringBuilder` capacities +- **Various others**: IllegalCatch suppression, JavadocMethod tags, UseIndexOfChar, ExhaustiveSwitchHasDefault, etc. + +## What Worked + +- **Parallel team agents** for the initial bulk work — two agents handled `check.core` (8 files) and the smaller modules (4 files) simultaneously, getting the majority of mechanical fixes done quickly. +- **Iterative verification loop** — running `mvn checkstyle:check pmd:check` after each round of fixes, then fixing what remained. This was essential because new modules kept appearing as CI checks expanded beyond the initial plan. +- **Suppression patterns** for rules that couldn't be fixed (dispatch method names, intentional exception catching): + - `// CHECKSTYLE:CONSTANTS-OFF` / `// CHECKSTYLE:CONSTANTS-ON` + - `// CHECKSTYLE:CHECK-OFF ` / `// CHECKSTYLE:CHECK-ON ` + - `@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter"})` + +## What Didn't Work + +- **Local PMD checks without `compile`** — Running `mvn checkstyle:check pmd:check` without a prior `compile` phase misses PMD rules that require type resolution (MissingOverride, UnnecessaryCast, LooseCoupling, UseCollectionIsEmpty). This caused violations to slip through local verification and only appear in CI. +- **Grepping build output with `head -30`** — When verifying with `--fail-at-end`, the first 30 lines were all `0 Checkstyle violations` messages, hiding the actual `BUILD FAILURE` at the bottom. A compilation error was missed because of this. +- **Initial scope estimation** — The plan identified 4 modules but violations existed in 10+. Each CI run surfaced new modules we hadn't checked locally. +- **Agent-generated fixes sometimes incomplete** — Agents fixed the obvious cases but missed edge cases (e.g., expanding a star import but not checking all usages, changing an import without updating all references). + +## Key Decisions & Rationale + +1. **Suppress vs fix for MethodName violations**: Dispatch methods like `_format()`, `_scope()`, `_computeTypes()` must keep underscore prefixes (Xtext dispatch pattern). Used class-level `@SuppressWarnings` rather than renaming. + +2. **CHECKSTYLE:CONSTANTS-OFF for MultipleStringLiterals**: Template-generating methods that build Java source code via StringBuilder inherently have repeated string literals. Extracting constants would hurt readability. + +3. **CHECKSTYLE:CHECK-OFF for IllegalCatch**: Some methods intentionally catch `Exception` (e.g., in code generation utilities). Suppressed rather than changed. + +4. **Switch to `mvn clean compile pmd:check` for local verification**: After discovering that PMD needs compiled classes for type-resolution rules, this became the correct local verification command. + +## Lessons Learned & Gotchas + +1. **PMD type-resolution rules need compiled code**: `MissingOverride`, `UnnecessaryCast`, `LooseCoupling`, `UseCollectionIsEmpty` all need class files. Always run `mvn clean compile pmd:check` locally, not just `pmd:check`. + +2. **Checkstyle does NOT need compilation** — it works purely on source files. + +3. **`--fail-at-end` hides early failures** — When grepping output, always check the final `BUILD SUCCESS/FAILURE` line, not just intermediate results. + +4. **Xtend dispatch methods produce underscore-prefixed Java methods** — These are a known pattern (`_methodName`) that Xtext's dispatch resolution depends on. They cannot be renamed. + +5. **CI modules are discovered incrementally** — The Maven reactor with `--fail-at-end` skips downstream modules when an upstream module fails. Fixing one module can unblock compilation of others, revealing their violations. + +6. **`ByteArrayInputStream.close()` is a no-op** — When simplifying try-finally around BAIS, safe to just remove the close call entirely. + +7. **PMD's InsufficientStringBufferDeclaration calculates actual string sizes** — A StringBuilder(512) may still trigger if the appended content totals >512 bytes. Some methods needed 2048. + +## Next Steps + +1. **Consider squashing the 4 style-fix commits** before merging to master — they're all part of the same logical change. The commits are `18fb5a34e`, `8d4d826d7`, `ab0d6df10`, `645407dd5`. + +2. **Clean up untracked `xtend-gen/` directories** — 25 untracked `xtend-gen/` directories remain from the migration. These should either be `.gitignore`d or removed. + +3. **Consider adding `mvn clean compile pmd:check` to the local dev workflow** — Document that `pmd:check` alone misses type-resolution rules. + +4. **PR is ready for review** — CI is green. The PR is #1274 on the dsl-devkit repo. + +## Key Files & Locations + +| File | Purpose | +|------|---------| +| `check.core/.../formatting2/CheckFormatter.java` | Largest single file fix (~143 violations). Dispatch-based formatter. | +| `check.core/.../jvmmodel/CheckJvmModelInferrer.java` | JVM model inference with dispatch methods. Had LooseCoupling, UnnecessaryCast, MissingOverride. | +| `check.core/.../generator/CheckGeneratorExtensions.java` | Code generation utilities. Star import expansion, JavadocMethod, IllegalCatch. | +| `check.core/.../generator/CheckGenerator.java` | Main code generator. AppendCharWithChar, InsufficientStringBufferDeclaration. | +| `check.core/.../generator/CheckGeneratorNaming.java` | Naming conventions. StringToString fix (getLastSegment().toString() redundant). | +| `check.core/.../scoping/CheckScopeProvider.java` | Scoping with dispatch methods. UnusedFormalParameter, UseIndexOfChar. | +| `xtext.generator.test/.../XbaseGeneratorFragmentTest.java` | Test file. LooseCoupling (BasicEList→EList), ConstantName renames, ImmutableField. | +| `xtext.generator/.../AnnotationAwareAntlrContentAssistGrammarGenerator.java` | Grammar generator. MissingOverride, UnnecessaryBoxing fixes. | +| `checkcfg.core/.../PropertiesInferenceHelper.java` | Properties inference. ExhaustiveSwitchHasDefault (switch expression), UseCollectionIsEmpty. | +| `check.core.test/.../util/CheckModelUtil.java` | Test model utilities. JavadocMethod, VariableDeclarationUsageDistance, AppendCharWithChar. | + +## Additional Notes + +- The `xtend-gen/` directories in the untracked files list are leftover from the Xtend build infrastructure that was removed in commit `41edd59ab`. They should be added to `.gitignore` or deleted. +- The worktree branches (`worktree-agent-*`) visible in `git branch` are leftovers from agent execution. They can be cleaned up with `git branch -D`. +- You may want to add `HANDOVER.md` to `.gitignore`. diff --git a/com.avaloq.tools.ddk.check.core.test/.classpath b/com.avaloq.tools.ddk.check.core.test/.classpath index 3450ddaac1..325c9ddddf 100644 --- a/com.avaloq.tools.ddk.check.core.test/.classpath +++ b/com.avaloq.tools.ddk.check.core.test/.classpath @@ -15,11 +15,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.check.core.test/build.properties b/com.avaloq.tools.ddk.check.core.test/build.properties index dacebd4595..6013dbdcc5 100644 --- a/com.avaloq.tools.ddk.check.core.test/build.properties +++ b/com.avaloq.tools.ddk.check.core.test/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ src-gen/,\ - xtend-gen/,\ resource/ output.. = bin/ bin.includes = META-INF/,\ diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/generator/IssueCodeValueTest.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/generator/IssueCodeValueTest.java new file mode 100644 index 0000000000..3fb93b21dd --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/generator/IssueCodeValueTest.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Evolution AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.check.core.generator; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.xbase.testing.JavaSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.avaloq.tools.ddk.check.CheckInjectorProvider; +import com.avaloq.tools.ddk.check.core.test.AbstractCheckGenerationTestCase; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@InjectWith(CheckInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class IssueCodeValueTest extends AbstractCheckGenerationTestCase { + + private static final String PACKAGE_NAME = "mypackage"; + private static final String CATALOG_NAME = "MyCatalog"; + + /** + * Test the map generated from a catalog with checks. + */ + @Test + public void testIssueCodeValue() throws Exception { + // ARRANGE + // @Format-Off + String source = String.format(""" + package %s + + import com.avaloq.tools.ddk.check.check.Check + import com.avaloq.tools.ddk.check.check.Context + import com.avaloq.tools.ddk.check.check.Documented + + catalog %s + for grammar com.avaloq.tools.ddk.check.Check { + + live error MyCheck1 "Label 1" + message "Message 1" { + for Documented elem { + switch elem { + Context : issue on elem + Check : issue on elem + } + } + } + + live error MyCheck_2 "Label 2" + message "Message 2" { + for Documented elem { + switch elem { + Context : issue on elem + Check : issue on elem + } + } + } + + live error MYCheck3 "Label 3" + message "Message 3" { + for Documented elem { + switch elem { + Context : issue on elem + Check : issue on elem + } + } + } + } + """, PACKAGE_NAME, CATALOG_NAME); + // @Format-On + + Map expectedIssueCodeValues = Map.of( + "MY_CHECK_1", "MyCheck1", + "MY_CHECK_2", "MyCheck2", + "MY_CHECK_3", "MyCheck3"); + + // ACT + List compiledClassesList; + try (ByteArrayInputStream sourceStream = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8))) { + compiledClassesList = generateAndCompile(sourceStream); + } + + // ASSERT + String issueCodesClassName = CATALOG_NAME + ISSUE_CODES_SUFFIX; + + String issueCodesClass = compiledClassesList.stream() + .filter(s -> s.getFileName().equals(issueCodesClassName)) + .findFirst() + .orElse(null) + .getCode(); + + for (Map.Entry issueCode : expectedIssueCodeValues.entrySet()) { + String expectedIssueCodeAssignment = "public static final String " + issueCode.getKey() + + " = \"" + PACKAGE_NAME + "." + CATALOG_NAME + ISSUE_CODES_SUFFIX + "." + issueCode.getValue() + "\";"; + assertTrue(issueCodesClass.contains(expectedIssueCodeAssignment), + issueCodesClassName + " contains correct initialization of " + issueCode.getKey()); + } + } + +} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/generator/IssueCodeValueTest.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/generator/IssueCodeValueTest.xtend deleted file mode 100644 index 7a53cffd08..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/generator/IssueCodeValueTest.xtend +++ /dev/null @@ -1,104 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Evolution AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.check.core.generator - -import org.eclipse.xtext.testing.InjectWith -import com.avaloq.tools.ddk.check.CheckInjectorProvider -import com.avaloq.tools.ddk.check.core.test.AbstractCheckGenerationTestCase -import java.util.List -import org.eclipse.xtext.xbase.testing.JavaSource -import java.io.ByteArrayInputStream -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue - -@InjectWith(CheckInjectorProvider) -@ExtendWith(InjectionExtension) -class IssueCodeValueTest extends AbstractCheckGenerationTestCase { - - static final String PACKAGE_NAME = "mypackage" - static final String CATALOG_NAME = "MyCatalog" - - /** - * Test the map generated from a catalog with checks. - */ - @Test - def void testIssueCodeValue() { - // ARRANGE - // @Format-Off - val source = ''' - package «PACKAGE_NAME» - - import com.avaloq.tools.ddk.check.check.Check - import com.avaloq.tools.ddk.check.check.Context - import com.avaloq.tools.ddk.check.check.Documented - - catalog «CATALOG_NAME» - for grammar com.avaloq.tools.ddk.check.Check { - - live error MyCheck1 "Label 1" - message "Message 1" { - for Documented elem { - switch elem { - Context : issue on elem - Check : issue on elem - } - } - } - - live error MyCheck_2 "Label 2" - message "Message 2" { - for Documented elem { - switch elem { - Context : issue on elem - Check : issue on elem - } - } - } - - live error MYCheck3 "Label 3" - message "Message 3" { - for Documented elem { - switch elem { - Context : issue on elem - Check : issue on elem - } - } - } - } - '''; - // @Format-On - - val expectedIssueCodeValues = #{'MY_CHECK_1' -> 'MyCheck1', 'MY_CHECK_2' -> 'MyCheck2', 'MY_CHECK_3' -> 'MyCheck3'} - - // ACT - var List compiledClassesList - val sourceStream = new ByteArrayInputStream(source.getBytes()); - try { - compiledClassesList = generateAndCompile(sourceStream); - } finally { - sourceStream.close - } - - // ASSERT - val issueCodesClassName = '''«CATALOG_NAME»«ISSUE_CODES_SUFFIX»''' - - val issueCodesClass = compiledClassesList.findFirst[s | s.fileName.equals(issueCodesClassName)].code; - - for (issueCode: expectedIssueCodeValues.entrySet) { - val expectedIssueCodeAssignment = '''public static final String «issueCode.key» = "«PACKAGE_NAME».«CATALOG_NAME»«ISSUE_CODES_SUFFIX».«issueCode.value»";''' - assertTrue(issueCodesClass.contains(expectedIssueCodeAssignment), '''«issueCodesClassName» contains correct initialization of «issueCode.key»''') - } - } - -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BasicModelTest.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BasicModelTest.java new file mode 100644 index 0000000000..c905fff00f --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BasicModelTest.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.core.test; + +import com.avaloq.tools.ddk.check.CheckUiInjectorProvider; +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.avaloq.tools.ddk.check.core.test.util.CheckModelUtil; +import com.avaloq.tools.ddk.check.core.test.util.CheckTestUtil; +import com.google.inject.Inject; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +// CHECKSTYLE:CONSTANTS-OFF +@InjectWith(CheckUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class BasicModelTest { + + @Inject + private ParseHelper parser; + + @Inject + private CheckTestUtil util; + + @Inject + private CheckModelUtil modelUtil; + + /** + * Tests that an empty model based on EPackage model reference can be created. + */ + @Test + public void testCreateEmptyModelWithPackageReference() throws Exception { + CheckCatalog model = parser.parse("package p catalog c {}"); + assertNotNull(model, "CheckCatalog with EPackage reference successfully created"); + } + + /** + * Tests that an empty model based on model reference by Xtext Grammar can be created. + */ + @Test + public void testCreateEmptyModelWithGrammarReference() throws Exception { + CheckCatalog model = parser.parse("package p catalog c for grammar com.avaloq.tools.ddk.check.Check {}"); + assertNotNull(model, "CheckCatalog with Grammar reference successfully created"); + } + + /* Tests that an XIssueExpression takes message parameters. */ + @Test + public void testXIssueExpressionMessageParameters() throws Exception { + CheckCatalog model = parser.parse(modelUtil.modelWithContext() + "issue A on it bind (\"mp0\", \"mp1\")"); + assertEquals(2, util.getFirstInstanceOf(model, XIssueExpression.class).getMessageParameters().size()); + } + + /* Tests that an XIssueExpression takes message parameters when a marker feature has been specified. */ + @Test + public void testXIssueExpressionWithMarkerFeatureMessageParameters() throws Exception { + CheckCatalog model = parser.parse(modelUtil.modelWithContext() + "issue A on x#name bind (\"mp0\", \"mp1\")"); + assertEquals(2, util.getFirstInstanceOf(model, XIssueExpression.class).getMessageParameters().size()); + } + + /* Tests that Checks documented with ML_COMMENTs have an inferred description field. */ + @Test + @Disabled("Fails because DocumentedImplCustom uses the null resource description provider to get the document provider") + public void testInferingOfDescription() throws Exception { + Check check = util.getFirstInstanceOf(parser.parse(modelUtil.modelWithCheck()), Check.class); + assertEquals(check.getDescription(), "No documentation."); + } + + /* Tests that Checks have an implicit name which matches the ID. */ + @Test + public void testCheckNameIDIsPresent() throws Exception { + String id = "CheckID"; + Check check = util.getFirstInstanceOf(parser.parse(modelUtil.modelWithCheck(id)), Check.class); + assertEquals(check.getId(), check.getName(), "Check name field matches ID field"); + assertEquals(id, check.getName(), "Check name field matches supplied ID"); + } + + /* Tests that Checks have an implicit name which matches the ID even when the ID is missing. */ + @Test + public void testCheckNameIDIsMissing() throws Exception { + Check check = util.getFirstInstanceOf(parser.parse(modelUtil.modelWithCheck("")), Check.class); + assertNull(check.getName(), "Check name is null"); + } + + /* Tests that multi- and single-line comments are parsed in a model. */ + @Test + public void testCommentsInModelParse() throws Exception { + CheckCatalog model = parser.parse(modelUtil.modelWithComments()); + assertFalse(((XtextResource) model.eResource()).getParseResult().hasSyntaxErrors(), + "Syntax errors not expected but occurred"); + } + + /* Tests that t.message is allowed, despite "message" being a keyword. */ + @Test + public void testKeywordAsIdentifier() throws Exception { + CheckCatalog model = parser.parse( + modelUtil.modelWithContext() + + "try { issue bind (\"mp0\", \"mp1\"); } catch (java.lang.Throwable t) { issue bind (t.message, \"foo\"); }" + + "}}}}"); + assertFalse(((XtextResource) model.eResource()).getParseResult().hasSyntaxErrors(), + "Syntax errors not expected but occurred"); + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BasicModelTest.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BasicModelTest.xtend deleted file mode 100644 index 42aad415be..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BasicModelTest.xtend +++ /dev/null @@ -1,116 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.core.test - -import com.avaloq.tools.ddk.check.CheckUiInjectorProvider -import com.avaloq.tools.ddk.check.check.Check -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.XIssueExpression -import com.avaloq.tools.ddk.check.core.test.util.CheckModelUtil -import com.avaloq.tools.ddk.check.core.test.util.CheckTestUtil -import com.google.inject.Inject -import org.eclipse.xtext.resource.XtextResource -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.util.ParseHelper -import org.junit.jupiter.api.Test -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.^extension.ExtendWith -import static org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Disabled - -@InjectWith(typeof(CheckUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class BasicModelTest { - - @Inject - ParseHelper parser - - @Inject - CheckTestUtil util - - @Inject - CheckModelUtil modelUtil - - /** - * Tests that an empty model based on EPackage model reference can be created. - */ - @Test - def void testCreateEmptyModelWithPackageReference() { - val model = parser.parse("package p catalog c {}") - assertNotNull(model, "CheckCatalog with EPackage reference successfully created") - } - - /** - * Tests that an empty model based on model reference by Xtext Grammar can be created. - */ - @Test - def void testCreateEmptyModelWithGrammarReference() { - val model = parser.parse("package p catalog c for grammar com.avaloq.tools.ddk.check.Check {}") - assertNotNull(model, "CheckCatalog with Grammar reference successfully created") - } - - /* Tests that an XIssueExpression takes message parameters. */ - @Test - def void testXIssueExpressionMessageParameters() { - val model = parser.parse(modelUtil.modelWithContext + "issue A on it bind (\"mp0\", \"mp1\")") - assertEquals(2, util.getFirstInstanceOf(model, typeof(XIssueExpression)).messageParameters.size) - } - - /* Tests that an XIssueExpression takes message parameters when a marker feature has been specified. */ - @Test - def void testXIssueExpressionWithMarkerFeatureMessageParameters() { - val model = parser.parse(modelUtil.modelWithContext + "issue A on x#name bind (\"mp0\", \"mp1\")") - assertEquals(2, util.getFirstInstanceOf(model, typeof(XIssueExpression)).messageParameters.size) - } - - /* Tests that Checks documented with ML_COMMENTs have an inferred description field. */ - @Test - @Disabled("Fails because DocumentedImplCustom uses the null resource description provider to get the document provider") - def void testInferingOfDescription() { - val check = util.getFirstInstanceOf(parser.parse(modelUtil.modelWithCheck), typeof(Check)) - assertEquals(check.description, "No documentation.") - } - - /* Tests that Checks have an implicit name which matches the ID. */ - @Test - def void testCheckNameIDIsPresent() { - val id = "CheckID" - val check = util.getFirstInstanceOf(parser.parse(modelUtil.modelWithCheck(id)), typeof(Check)) - assertEquals(check.id, check.name, "Check name field matches ID field") - assertEquals(id, check.name, "Check name field matches supplied ID") - } - - /* Tests that Checks have an implicit name which matches the ID even when the ID is missing. */ - @Test - def void testCheckNameIDIsMissing() { - val check = util.getFirstInstanceOf(parser.parse(modelUtil.modelWithCheck("")), typeof(Check)) - assertNull(check.name, "Check name is null") - } - - /* Tests that multi- and single-line comments are parsed in a model. */ - @Test - def void testCommentsInModelParse() { - val model = parser.parse(modelUtil.modelWithComments) - assertFalse((model.eResource as XtextResource).parseResult.hasSyntaxErrors, - "Syntax errors not expected but occurred") - } - - /* Tests that t.message is allowed, despite "message" being a keyword. */ - @Test - def void testKeywordAsIdentifier() { - val model = parser.parse( - modelUtil.modelWithContext + - "try { issue bind (\"mp0\", \"mp1\"); } catch (java.lang.Throwable t) { issue bind (t.message, \"foo\"); }" + - "}}}}"); - assertFalse((model.eResource as XtextResource).parseResult.hasSyntaxErrors, - "Syntax errors not expected but occurred") - } -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BugAig830.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BugAig830.java new file mode 100644 index 0000000000..36c34a23fd --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BugAig830.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.core.test; + +import com.avaloq.tools.ddk.check.CheckUiInjectorProvider; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.google.inject.Inject; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.xbase.XbasePackage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@InjectWith(CheckUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class BugAig830 { + + @Inject + private ParseHelper parser; + + private String getModel() { + return """ + package abc + import org.eclipse.xtext.xbase.XVariableDeclaration + catalog Abc + for grammar com.avaloq.tools.ddk.check.Check { + live error "Test" { + for XVariableDeclaration v { + issue on v#name + } + } + } + """; + } + + /* Tests that EPackages which are not of declared target language can be referenced. */ + @Test + public void bugAig830() throws Exception { + final CheckCatalog model = parser.parse(getModel()); + final XIssueExpression issue = EcoreUtil2.getAllContentsOfType(model, XIssueExpression.class).get(0); + assertNotNull(issue.getMarkerFeature()); + assertFalse(issue.getMarkerFeature().eIsProxy()); + assertEquals(XbasePackage.Literals.XVARIABLE_DECLARATION, issue.getMarkerFeature().getEContainingClass()); + } +} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BugAig830.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BugAig830.xtend deleted file mode 100644 index 854dace54c..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/BugAig830.xtend +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.core.test - -import com.avaloq.tools.ddk.check.CheckUiInjectorProvider -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.XIssueExpression -import com.google.inject.Inject -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.util.ParseHelper -import org.eclipse.xtext.xbase.XbasePackage -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.^extension.ExtendWith -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.* - -@InjectWith(typeof(CheckUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class BugAig830 { - - @Inject - ParseHelper parser - - def private getModel() ''' - package abc - import org.eclipse.xtext.xbase.XVariableDeclaration - catalog Abc - for grammar com.avaloq.tools.ddk.check.Check { - live error "Test" { - for XVariableDeclaration v { - issue on v#name - } - } - } - ''' - - /* Tests that EPackages which are not of declared target language can be referenced. */ - @Test - def void bugAig830() { - val model = parser.parse(getModel()) - val issue = EcoreUtil2::getAllContentsOfType(model, typeof(XIssueExpression)).get(0) - assertNotNull(issue.markerFeature) - assertFalse(issue.markerFeature.eIsProxy) - assertEquals(XbasePackage.Literals::XVARIABLE_DECLARATION, issue.markerFeature.EContainingClass) - } - -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/CheckScopingTest.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/CheckScopingTest.java new file mode 100644 index 0000000000..b8723c2239 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/CheckScopingTest.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.core.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.avaloq.tools.ddk.check.CheckUiInjectorProvider; +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.Implementation; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.avaloq.tools.ddk.check.core.test.util.CheckTestUtil; +import com.google.common.collect.Lists; +import com.google.inject.Inject; +import java.util.List; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +// CHECKSTYLE:CONSTANTS-OFF +@InjectWith(CheckUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckScopingTest extends AbstractCheckTestCase { + + @Inject + private CheckTestUtil util; + + /* + * The model names are deliberately not including file extension, the actual resources + * are also missing them. The reason for this is that if current development instance of + * Eclipse has the Check runtime plugins installed, code will automatically be generated + * for those resources. In order to avoid that the file extensions have been omitted. + */ + public List getRequiredSourceFileNames() { + return Lists.newArrayList("CommonChecks", "SampleChecks"); + } + + public void initializeTestProject() { + // sources are copied into the project and then built by the Xtext builder + addSourcesToWorkspace(CheckScopingTest.class, getRequiredSourceFileNames()); + // wait for build to finish, otherwise included catalog may not be resolvable + IResourcesSetupUtil.waitForBuild(); + } + + /* + * Tests that a catalog may not reference checks (in implementations, 'def') which are + * neither local nor included. + */ + @Test + public void testIllegalDirectionOfReference() throws Exception { + initializeTestProject(); + + // test that our model is available + final CheckCatalog model = (CheckCatalog) getModel("CommonChecks"); + + final Implementation illegalRefImpl = util.getFirstInstanceOf(model, Implementation.class); + final XIssueExpression issueExpr = util.getFirstInstanceOf(illegalRefImpl, XIssueExpression.class); + + assertTrue(issueExpr.getCheck().eIsProxy(), "Referenced check cannot be resolved"); + assertNull(issueExpr.getCheck().getName(), "Referenced check name is null"); + } + + /* + * Tests that a check may be documented using a ML_COMMENT. The documentation is inferred + * in the description field of a check. + */ + @Test + public void testCheckDescriptionIsInferred() throws Exception { + initializeTestProject(); + final Check check = util.getFirstInstanceOf(getModel("CommonChecks"), Check.class); + assertEquals("This check is javadoc-like commented.", check.getDescription(), "Referenced check cannot be resolved"); + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/CheckScopingTest.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/CheckScopingTest.xtend deleted file mode 100644 index fd8572269a..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/CheckScopingTest.xtend +++ /dev/null @@ -1,81 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.core.test - -import com.avaloq.tools.ddk.check.CheckUiInjectorProvider -import com.avaloq.tools.ddk.check.check.Check -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.Implementation -import com.avaloq.tools.ddk.check.check.XIssueExpression -import com.avaloq.tools.ddk.check.core.test.util.CheckTestUtil -import com.google.common.collect.Lists -import com.google.inject.Inject -import java.util.List -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.* - -@InjectWith(typeof(CheckUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class CheckScopingTest extends AbstractCheckTestCase { - - @Inject CheckTestUtil util - - /* - * The model names are deliberately not including file extension, the actual resources - * are also missing them. The reason for this is that if current development instance of - * Eclipse has the Check runtime plugins installed, code will automatically be generated - * for those resources. In order to avoid that the file extensions have been omitted. - */ - def List getRequiredSourceFileNames() { - Lists::newArrayList("CommonChecks", "SampleChecks") - } - - def void initializeTestProject() { - // sources are copied into the project and then built by the Xtext builder - addSourcesToWorkspace(typeof(CheckScopingTest), requiredSourceFileNames) - - // wait for build to finish, otherwise included catalog may not be resolvable - IResourcesSetupUtil.waitForBuild - } - - /* - * Tests that a catalog may not reference checks (in implementations, 'def') which are - * neither local nor included. - */ - @Test - def void testIllegalDirectionOfReference() { - initializeTestProject - - // test that our model is available - val model = getModel("CommonChecks") as CheckCatalog - - val illegalRefImpl = util.getFirstInstanceOf(model, typeof(Implementation)) - val issueExpr = util.getFirstInstanceOf(illegalRefImpl, typeof(XIssueExpression)) - - assertTrue(issueExpr.check.eIsProxy, "Referenced check cannot be resolved") - assertNull(issueExpr.check.name, "Referenced check name is null") - } - - /* - * Tests that a check may be documented using a ML_COMMENT. The documentation is inferred - * in the description field of a check. - */ - @Test - def void testCheckDescriptionIsInferred() { - initializeTestProject - val check = util.getFirstInstanceOf(getModel("CommonChecks"), typeof(Check)) - assertEquals("This check is javadoc-like commented.", check.description, "Referenced check cannot be resolved") - } -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/IssueCodeToLabelMapGenerationTest.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/IssueCodeToLabelMapGenerationTest.java new file mode 100644 index 0000000000..7cac84859d --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/IssueCodeToLabelMapGenerationTest.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.check.core.test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.xbase.testing.JavaSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.avaloq.tools.ddk.check.CheckInjectorProvider; + + +/** + * Unit test for auto generation of check issue code to label map. + */ +// CHECKSTYLE:CONSTANTS-OFF +@InjectWith(CheckInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class IssueCodeToLabelMapGenerationTest extends AbstractCheckGenerationTestCase { + + private static final String PACKAGE_NAME = "mypackage"; + + private static final String CATALOG_NAME = "MyCatalog"; + + /** + * Test the map generated from a catalog with no checks. + */ + @Test + public void testMapGenerationWithNoChecks() { + // ARRANGE + final String source = String.format(""" + package %s + + catalog %s + for grammar com.avaloq.tools.ddk.check.Check { + + } + """, PACKAGE_NAME, CATALOG_NAME); + + // check for the construction of an empty map + final List expectedCatalog = List.of("ImmutableMap.builderWithExpectedSize(0).build()"); + + // ACT AND ASSERT + testMapGeneration(source, expectedCatalog); + } + + /** + * Test the map generated from a catalog with checks. + */ + @Test + public void testMapGeneration() { + // ARRANGE + final String source = String.format(""" + package %s + + import com.avaloq.tools.ddk.check.check.Check + import com.avaloq.tools.ddk.check.check.Context + import com.avaloq.tools.ddk.check.check.Documented + + catalog %s + for grammar com.avaloq.tools.ddk.check.Check { + + live error ID1 "Label 1" + message "Message 1" { + for Documented elem { + switch elem { + Context : issue on elem + Check : issue on elem + } + } + } + + live error ID2 "Label 2" + message "Message 2" { + for Documented elem { + switch elem { + Context : issue on elem + Check : issue on elem + } + } + } + } + """, PACKAGE_NAME, CATALOG_NAME); + + final List expectedCatalog = List.of("put(MyCatalogIssueCodes.ID_1,\"Label1\")", "put(MyCatalogIssueCodes.ID_2,\"Label2\")"); + + // ACT AND ASSERT + testMapGeneration(source, expectedCatalog); + } + + /** + * Test the map generated from a catalog. + * + * @param source + * the catalog, must not be {@code null} + * @param expectedCatalog + * the expected map, may be {@code null} + */ + public void testMapGeneration(final String source, final List expectedCatalog) { + // ACT + final ByteArrayInputStream sourceStream = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8)); + final List compiledClassesList = generateAndCompile(sourceStream); + + // ASSERT + final String catalogClassName = CATALOG_NAME + CATALOG_NAME_SUFFIX; + + final String catalogClass = compiledClassesList.stream() + .filter(s -> s.getFileName().equals(catalogClassName)) + .findFirst() + .orElseThrow() + .getCode(); + + for (final String code : expectedCatalog) { + assertTrue(catalogClass.replaceAll("\\s+", "").contains(code), catalogClassName + " was generated correctly"); + } + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/IssueCodeToLabelMapGenerationTest.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/IssueCodeToLabelMapGenerationTest.xtend deleted file mode 100644 index 064a7b8ceb..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/IssueCodeToLabelMapGenerationTest.xtend +++ /dev/null @@ -1,130 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.check.core.test - -import com.avaloq.tools.ddk.check.CheckInjectorProvider -import java.io.ByteArrayInputStream -import java.util.List -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.eclipse.xtext.xbase.testing.JavaSource -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith - -import static org.junit.jupiter.api.Assertions.* - -/** - * Unit test for auto generation of check issue code to label map. - */ -@InjectWith(CheckInjectorProvider) -@ExtendWith(InjectionExtension) -class IssueCodeToLabelMapGenerationTest extends AbstractCheckGenerationTestCase { - - static final String PACKAGE_NAME = "mypackage" - static final String CATALOG_NAME = "MyCatalog" - - /** - * Test the map generated from a catalog with no checks. - */ - @Test - def void testMapGenerationWithNoChecks() { - // ARRANGE - val source = ''' - package «PACKAGE_NAME» - - catalog «CATALOG_NAME» - for grammar com.avaloq.tools.ddk.check.Check { - - } - '''; - - // check for the construction of an empty map - val expectedCatalog = #['ImmutableMap.builderWithExpectedSize(0).build()'] - - // ACT AND ASSERT - testMapGeneration(source, expectedCatalog) - } - - /** - * Test the map generated from a catalog with checks. - */ - @Test - def void testMapGeneration() { - // ARRANGE - // @Format-Off - val source = ''' - package «PACKAGE_NAME» - - import com.avaloq.tools.ddk.check.check.Check - import com.avaloq.tools.ddk.check.check.Context - import com.avaloq.tools.ddk.check.check.Documented - - catalog «CATALOG_NAME» - for grammar com.avaloq.tools.ddk.check.Check { - - live error ID1 "Label 1" - message "Message 1" { - for Documented elem { - switch elem { - Context : issue on elem - Check : issue on elem - } - } - } - - live error ID2 "Label 2" - message "Message 2" { - for Documented elem { - switch elem { - Context : issue on elem - Check : issue on elem - } - } - } - } - '''; - // @Format-On - - val expectedCatalog = #['put(MyCatalogIssueCodes.ID_1,"Label1")','put(MyCatalogIssueCodes.ID_2,"Label2")'] - - // ACT AND ASSERT - testMapGeneration(source, expectedCatalog) - } - - /** - * Test the map generated from a catalog. - * @param source - * the catalog, must not be {@code null} - * @param expectedMap - * the expected map, may be {@code null} - */ - def void testMapGeneration(String source, List expectedCatalog) { - // ACT - var List compiledClassesList - val sourceStream = new ByteArrayInputStream(source.getBytes()); - try { - compiledClassesList = generateAndCompile(sourceStream); - } finally { - sourceStream.close - } - - // ASSERT - val catalogClassName = '''«CATALOG_NAME»«CATALOG_NAME_SUFFIX»''' - - val catalogClass = compiledClassesList.findFirst[s | s.fileName.equals(catalogClassName)].code; - - for (code: expectedCatalog) { - assertTrue( catalogClass.replaceAll("\\s+","").contains(code), '''«catalogClassName» was generated correctly''') - } - } - -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/ProjectBasedTests.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/ProjectBasedTests.java new file mode 100644 index 0000000000..2d290c1119 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/ProjectBasedTests.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.core.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.avaloq.tools.ddk.check.CheckUiInjectorProvider; +import com.google.common.collect.Lists; + + +@InjectWith(CheckUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class ProjectBasedTests extends AbstractCheckTestCase { + + private boolean initialized; + + public List getRequiredSourceFileNames() { + // No file extension to prevent code generation in development workbench. Will get .check extension when copied into + // runtime workspace the test runs in. + return Lists.newArrayList("bugdsl27/BugDsl27", "bugdsl281/BugDsl281"); + } + + @Override + protected String getFullFileName(final String fileName) { + // Make sure it is put into the src folder even if the name contains a dash! + return getSourceFolderPath() + getFileName(fileName); + } + + public void initializeTestProject() { + if (!initialized) { + initialized = true; + // sources are copied into the project and then built by the Xtext builder + addSourcesToWorkspace(ProjectBasedTests.class, getRequiredSourceFileNames()); + + // wait for build to finish, otherwise included catalog may not be resolvable + IResourcesSetupUtil.waitForBuild(); + } + } + + private boolean isEmpty(final IFile file) throws CoreException { + try (InputStream s = file.getContents()) { + return s.read() < 0; + } catch (CoreException e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private void assertGeneratedJavaCodeHasNoErrorMarkers(final String fileName) throws CoreException { + final IProject project = getOrCreatePluginProject(); + final IFile file = project.getFile(fileName); + // enumerateContents(project); + assertTrue(file.exists(), "Generated file should exist"); + assertFalse(isEmpty(file), "Generated file should not be empty"); + assertTrue(file.findMaxProblemSeverity(null, true, IResource.DEPTH_INFINITE) < IMarker.SEVERITY_ERROR, "Generated file should not have errors"); + } + + @Test + public void testBugDsl27CanCompile() throws CoreException { + initializeTestProject(); + + assertGeneratedJavaCodeHasNoErrorMarkers("src-gen/bugdsl27/BugDsl27CheckImpl.java"); + } + + @Test + public void testBugDsl281EmptyList() throws CoreException { + initializeTestProject(); + + assertGeneratedJavaCodeHasNoErrorMarkers("src-gen/bugdsl281/BugDsl281PreferenceInitializer.java"); + } +} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/ProjectBasedTests.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/ProjectBasedTests.xtend deleted file mode 100644 index 540d405e8c..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/ProjectBasedTests.xtend +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.core.test - -import com.avaloq.tools.ddk.check.CheckUiInjectorProvider -import com.google.common.collect.Lists -import java.io.InputStream -import java.util.List -import org.eclipse.core.resources.IFile -import org.eclipse.core.resources.IMarker -import org.eclipse.core.resources.IProject -import org.eclipse.core.resources.IResource -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.* - -@InjectWith(typeof(CheckUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class ProjectBasedTests extends AbstractCheckTestCase { - - boolean initialized; - - def List getRequiredSourceFileNames() { - // No file extension to prevent code generation in development workbench. Will get .check extension when copied into - // runtime workspace the test runs in. - Lists::newArrayList("bugdsl27/BugDsl27", "bugdsl281/BugDsl281") - } - - override getFullFileName(String fileName) { - // Make sure it is put into the src folder even if the name contains a dash! - return getSourceFolderPath() + getFileName(fileName); - } - - def void initializeTestProject() { - if (!initialized) { - initialized = true; - // sources are copied into the project and then built by the Xtext builder - addSourcesToWorkspace(typeof(ProjectBasedTests), requiredSourceFileNames) - - // wait for build to finish, otherwise included catalog may not be resolvable - IResourcesSetupUtil.waitForBuild - } - } - - private def boolean isEmpty(IFile file) { - val InputStream s = file.getContents(); - try { - return s.read < 0; - } finally { - s.close; - } - } - - def private void assertGeneratedJavaCodeHasNoErrorMarkers(String fileName) { - val IProject project = getOrCreatePluginProject() - val IFile file = project.getFile(fileName); - // enumerateContents(project); - assertTrue( file.exists,"Generated file should exist") - assertFalse( isEmpty(file),"Generated file should not be empty") - assertTrue( file.findMaxProblemSeverity(null, true, IResource::DEPTH_INFINITE) < IMarker::SEVERITY_ERROR,"Generated file should not have errors") - - } - - @Test - def void testBugDsl27CanCompile() { - initializeTestProject(); - - assertGeneratedJavaCodeHasNoErrorMarkers("src-gen/bugdsl27/BugDsl27CheckImpl.java"); - } - - @Test - def void testBugDsl281EmptyList() { - initializeTestProject(); - - assertGeneratedJavaCodeHasNoErrorMarkers("src-gen/bugdsl281/BugDsl281PreferenceInitializer.java"); - } -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckModelUtil.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckModelUtil.java new file mode 100644 index 0000000000..9b96f6aa8d --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckModelUtil.java @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.core.test.util; + +import java.util.List; + + +// CHECKSTYLE:CONSTANTS-OFF +/** + * Provides utility operations for Check model stubs. Only partial models + * are returned as strings. + */ +@SuppressWarnings("nls") +public class CheckModelUtil { + + /** + * Returns a base model stub with package (com.test), catalog (c) and grammar (g). + * + * @return the model stub string + */ + public String modelWithGrammar() { + return "package com.test\n" + "catalog c for grammar g {"; + } + + /** + * Returns a base model stub with a default category. + * + * @return the model stub string + */ + public String modelWithCategory() { + return this.modelWithGrammar() + "category \"Default Category\" {"; + } + + /** + * Returns a dummy category with given ID. + * + * @param id + * the category ID + * @param label + * the category label + * @return the category string + */ + public String emptyCategory(final String id, final String label) { + return String.format("category %s \"%s\" {\n}", id, label); + } + + /** + * Returns a base model stub with a severity range. + * + * @param min + * the minimum severity + * @param max + * the maximum severity + * @param severity + * the default severity + * @return the model stub string + */ + public String modelWithSeverityRange(final String min, final String max, final String severity) { + return this.modelWithCategory() + String.format("@SeverityRange(%s .. %s)\n %s ID \"My Check\" ()\n message \"My Message\"", min, max, severity); + } + + /** + * Returns a base model stub with a severity range and a default check. + * + * @param min + * the minimum severity + * @param max + * the maximum severity + * @return the model stub string + */ + public String modelWithSeverityRange(final String min, final String max) { + return this.modelWithCategory() + String.format("@SeverityRange(%s .. %s)\n", min, max) + modelWithCheck(); + } + + /** + * Returns a base model stub with a check of given ID. + * + * @param id + * the check ID + * @return the model stub string + */ + public String modelWithCheck(final String id) { + return this.modelWithCategory() + String.format("error %s \"Some Error\" ()\nmessage \"My Message\" {", id); + } + + /** + * Returns a base model stub with a check (SomeError) with severity 'error' + * and message (MyMessage). + * + * @return the model stub string + */ + public String modelWithCheck() { + return this.modelWithCheck("ID"); + } + + /** + * Returns a dummy check with given ID. + * + * @param id + * the check ID + * @return the check string + */ + public String emptyCheck(final String id) { + return String.format("error %s \"Some Error\" ()\nmessage \"My message\" {\n}", id); + } + + /** + * Returns a base model stub with a context using context type ContextType + * 'ctx'. + * + * @return the model stub string + */ + public String modelWithContext() { + return this.modelWithCheck() + "for ContextType ctx {"; + } + + /** + * Returns a base model stub with a give collection of contexts. + * + * @param contexts + * the list of context strings + * @return the model stub string + */ + public String modelWithContexts(final List contexts) { + String modelWithCheck = this.modelWithCheck(); + StringBuilder builder = new StringBuilder(512); + for (final String c : contexts) { + builder.append(c); + builder.append('\n'); + builder.append(" "); + } + return modelWithCheck + builder.toString(); + } + + /** + * Returns a complete Check model with multiple SL_ and ML_COMMENTS. + * + * @return the model stub string + */ + public String modelWithComments() { + return """ + package com.test // SL1 + /* ML1 */ + catalog c /* ML2 */ for grammar g { + // SL2 + category "My cat" { + /* ML3 */ + // SL3 + error MYerr "My Err" (int Abc = 23) message "A" { + for Atype thisName { + val x = 3 // SL4 + // SL5 + /* ML5 */ issue /* ML4 */ + // SL6 + } + } + } // SL7 + }"""; + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckModelUtil.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckModelUtil.xtend deleted file mode 100644 index a6a60b60bc..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckModelUtil.xtend +++ /dev/null @@ -1,113 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.core.test.util - -import java.util.List - -/* - * Provides utility operations for Check model stubs. Only partial models - * are returned as strings. - */ -class CheckModelUtil { - - /* Returns a base model stub with package (com.test), catalog (c) and grammar (g). */ - def String modelWithGrammar () {''' - package com.test - catalog c for grammar g {'''.toString - } - - /* Returns a base model stub with a default category. */ - def String modelWithCategory () { - modelWithGrammar+ ''' - category "Default Category" {'''.toString - } - - /* Returns a dummy category with given ID. */ - def String emptyCategory (String id, String label) { ''' - category «id» "«label»" { - }'''.toString - } - - /* Returns a base model stub with a severity range. */ - def String modelWithSeverityRange (String min, String max, String severity) { - modelWithCategory + '''@SeverityRange(«min» .. «max») - «severity» ID "My Check" () - message "My Message"'''.toString - } - - /* Returns a base model stub with a severity range and a default check. */ - def String modelWithSeverityRange (String min, String max) { - modelWithCategory + '''@SeverityRange(«min» .. «max») - '''.toString + modelWithCheck - } - - /* Returns a base model stub with a check of given ID. */ - def String modelWithCheck (String id) { - modelWithCategory + ''' - error «id» "Some Error" () - message "My Message" {'''.toString - } - - /* - * Returns a base model stub with a check (SomeError) with severity 'error' - * and message (MyMessage). - */ - def String modelWithCheck () { - modelWithCheck("ID") - } - - /* Returns a dummy check with given ID. */ - def String emptyCheck(String id) { ''' - error «id» "Some Error" () - message "My message" { - }'''.toString - } - - /* - * Returns a base model stub with a context using context type ContextType - * 'ctx'. - */ - def String modelWithContext() { - modelWithCheck + ''' - for ContextType ctx {'''.toString - } - - /* Returns a base model stub with a give collection of contexts. */ - def String modelWithContexts(List contexts) { - modelWithCheck + ''' - «FOR c:contexts» - «c.toString» - «ENDFOR»'''.toString - } - - /* Returns a complete Check model with multiple SL_ and ML_COMMENTS */ - def String modelWithComments() {''' - package com.test // SL1 - /* ML1 */ - catalog c /* ML2 */ for grammar g { - // SL2 - category "My cat" { - /* ML3 */ - // SL3 - error MYerr "My Err" (int Abc = 23) message "A" { - for Atype thisName { - val x = 3 // SL4 - // SL5 - /* ML5 */ issue /* ML4 */ - // SL6 - } - } - } // SL7 - }'''.toString() - - } - -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckTestUtil.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckTestUtil.java new file mode 100644 index 0000000000..7290a60488 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckTestUtil.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.core.test.util; + +import com.google.common.collect.Lists; +import java.util.List; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtext.EcoreUtil2; + +public class CheckTestUtil { + + /* + * Gets the first instance of given type in given context object. + */ + public T getFirstInstanceOf(final EObject context, final Class type) { + return getInstanceOf(context, type, null, null, 1); + } + + /* + * Gets any instance of given type containing a given structural feature with given value using a given context object. + */ + public T getInstanceOf(final EObject context, final Class type, final int instance) { + return getInstanceOf(context, type, null, null, instance); + } + + /* + * Gets the all instances of given type type having given value value on structural feature feature. + */ + public Iterable getAllInstancesOf(final EObject context, final Class type, final EStructuralFeature feature, final Object value) { + final List result = Lists.newArrayList(); + for (final T candidate : EcoreUtil2.getAllContentsOfType(context, type)) { + Object valueOfFeature = candidate.eGet(feature); + if (valueOfFeature != null && valueOfFeature.equals(value)) { + result.add(candidate); + } + } + return result; + } + + /* + * Gets the first instance of given type containing a given structural feature with given value using a given context object. + */ + public T getFirstInstanceOf(final EObject context, final Class type, final EStructuralFeature feature, final Object value) { + return getInstanceOf(context, type, feature, value, 1); + } + + /* + * Gets any instance of given type containing a given structural feature with given value using a given context object. + */ + public T getInstanceOf(final EObject context, final Class type, final EStructuralFeature feature, final Object value, final int instance) { + int skip = instance - 1; + for (final T candiadate : EcoreUtil2.getAllContentsOfType(context, type)) { + if ((feature == null && value == null) || candiadate.eGet(feature).equals(value)) { + if (skip == 0) { + return candiadate; + } else { + skip = skip - 1; + } + } + } + return null; + } +} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckTestUtil.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckTestUtil.xtend deleted file mode 100644 index 9dc87019a4..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/core/test/util/CheckTestUtil.xtend +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.core.test.util - -import com.google.common.collect.Lists -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EStructuralFeature -import org.eclipse.xtext.EcoreUtil2 - -class CheckTestUtil { - - /* - * Gets the first instance of given type in given context object. - */ - def T getFirstInstanceOf(EObject context, Class type) { - return getInstanceOf(context, type, null, null, 1) - } - - /* - * Gets any instance of given type containing a given structural feature with given value using a given context object. - */ - def T getInstanceOf(EObject context, Class type, int instance) { - return getInstanceOf(context, type, null, null, instance) - } - - /* - * Gets the all instances of given type type having given value value on structural feature feature. - */ - def Iterable getAllInstancesOf(EObject context, Class type, EStructuralFeature feature, Object value) { - val result = Lists::newArrayList - for (candidate : EcoreUtil2::getAllContentsOfType(context, type)) { - var valueOfFeature = candidate.eGet(feature); - if (valueOfFeature !== null && valueOfFeature.equals(value)) { - result.add(candidate); - } - } - return result; - } - - /* - * Gets the first instance of given type containing a given structural feature with given value using a given context object. - */ - def T getFirstInstanceOf(EObject context, Class type, EStructuralFeature feature, Object value) { - return getInstanceOf(context, type, feature, value, 1); - } - - /* - * Gets any instance of given type containing a given structural feature with given value using a given context object. - */ - def T getInstanceOf(EObject context, Class type, EStructuralFeature feature, Object value, int instance) { - var skip = instance - 1; - for (candiadate : EcoreUtil2::getAllContentsOfType(context, type)) { - if ((feature === null && value === null) || candiadate.eGet(feature).equals(value)) { - if (skip == 0) { - return candiadate; - } else { - skip = skip-1 - } - } - } - return null; - } - -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/formatting/CheckFormattingTest.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/formatting/CheckFormattingTest.java new file mode 100644 index 0000000000..177d956c47 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/formatting/CheckFormattingTest.java @@ -0,0 +1,1104 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.check.formatting; + +import com.avaloq.tools.ddk.check.CheckUiInjectorProvider; +import com.google.inject.Inject; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtext.formatting2.FormatterPreferenceKeys; +import org.eclipse.xtext.preferences.MapBasedPreferenceValues; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.formatter.FormatterTestHelper; +import org.eclipse.xtext.testing.formatter.FormatterTestRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +// CHECKSTYLE:CONSTANTS-OFF +@InjectWith(CheckUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckFormattingTest { + + @Inject + private FormatterTestHelper formatterTestHelper; + + /** + * Test that correctly formatted Check sources are not modified. + */ + @Test + public void testFormattedSource() { + // CHECKSTYLE:CHECK-OFF VariableDeclarationUsageDistance + StringConcatenation builder = new StringConcatenation(); + builder.append("package com.avaloq.tools.ddk.check.formatting"); + builder.newLine(); + builder.newLine(); + builder.append("import com.avaloq.tools.ddk.check.check.*"); + builder.newLine(); + builder.newLine(); + builder.append("catalog CheckFormattingTest"); + builder.newLine(); + builder.append("for grammar com.avaloq.tools.ddk.check.Check {"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("category \"Label\" {"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append("* @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890"); + builder.newLine(); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.append(" "); + builder.append("live error UniqueID \"Label\""); + builder.newLine(); + builder.append(" "); + builder.append("message \"message\" {"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("live error anotherid \"Label\""); + builder.newLine(); + builder.append(" "); + builder.append("message \"message {0}, {1}\" {"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("def Name"); + builder.newLine(); + builder.append(" "); + builder.append("for Category list {"); + builder.newLine(); + builder.append(" "); + builder.append("issue anotherid on list bind (3, 2) data (\"\")"); + builder.newLine(); + builder.append(" "); + builder.append("val size ="); + builder.newLine(); + builder.append(" "); + builder.append("if (list.checks !== null) list.checks.size else 0"); + builder.newLine(); + builder.append(" "); + builder.append("if (list.checks?.size > 1) {"); + builder.newLine(); + builder.append(" "); + builder.append("// SL: string value of list"); + builder.newLine(); + builder.append(" "); + builder.append("String::valueOf(list)"); + builder.newLine(); + builder.append(" "); + builder.append("issue UniqueID on list#checks[0]"); + builder.newLine(); + builder.append(" "); + builder.append("} else {"); + builder.newLine(); + builder.append(" "); + builder.append("/* ML: string value of size */"); + builder.newLine(); + builder.append(" "); + builder.append("String::valueOf(size)"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@SeverityRange(warning .. error)"); + builder.newLine(); + builder.append(" "); + builder.append("onSave error lastID \"Label\""); + builder.newLine(); + builder.append(" "); + builder.append("message \"message\" {"); + builder.newLine(); + builder.append(" "); + builder.append("// single line comment"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append("* This check is javadoc-like commented."); + builder.newLine(); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.append(" "); + builder.append("warning CategoryNamedW \"Category named W\" (boolean foo = false, boolean bar = true)"); + builder.newLine(); + builder.append(" "); + builder.append("message \"Category named \'w\'\" {"); + builder.newLine(); + builder.append(" "); + builder.append("for Category c {"); + builder.newLine(); + builder.append(" "); + builder.append("if (\'w\'.equalsIgnoreCase(c.name)) {"); + builder.newLine(); + builder.append(" "); + builder.append("issue"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.append("}"); + builder.newLine(); + final String input = builder.toString(); + + formatterTestHelper.assertFormatted((FormatterTestRequest it) -> { + // these preferences are usually picked up from the resource, but we are not using a resource here. + MapBasedPreferenceValues prefs = new MapBasedPreferenceValues(); + prefs.put(FormatterPreferenceKeys.indentation, " "); + it.getRequest().setPreferences(prefs); + it.setToBeFormatted(input); + it.setExpectation(input); + }); + // CHECKSTYLE:CHECK-ON VariableDeclarationUsageDistance + } + + /** + * Test that spaces and new lines are added where expected. + */ + @Test + public void testWSAdded() { + // CHECKSTYLE:CHECK-OFF VariableDeclarationUsageDistance + StringConcatenation builder = new StringConcatenation(); + builder.append("package com.avaloq.tools.ddk.check.formatting import com.avaloq.tools.ddk.check.check.* catalog CheckFormattingTest"); + builder.newLine(); + builder.append("for grammar com.avaloq.tools.ddk.check.Check{category \"Label\"{/**"); + builder.newLine(); + builder.append(" "); + builder.append("* @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890"); + builder.newLine(); + builder.append(" "); + builder.append("*/live error UniqueID \"Label\""); + builder.newLine(); + builder.append(" "); + builder.append("message \"message\"{}live error anotherid \"Label\""); + builder.newLine(); + builder.append(" "); + builder.append("message \"message {0}, {1}\" {}}"); + builder.newLine(); + builder.append(" "); + builder.append("def Name for Category list{issue anotherid on list bind(3,2)data(\"\")"); + builder.newLine(); + builder.append(" "); + builder.append("val size=if(list.checks !== null)list.checks.size else 0"); + builder.newLine(); + builder.append(" "); + builder.append("if(list.checks?.size>1){"); + builder.newLine(); + builder.append(" "); + builder.append("// SL: string value of list"); + builder.newLine(); + builder.append(" "); + builder.append("String::valueOf(list)issue UniqueID on list#checks[0]}else{/* ML: string value of size */String::valueOf(size)}}"); + builder.newLine(); + builder.append(" "); + builder.append("@SeverityRange(warning..error)onSave error lastID\"Label\"message \"message\" {"); + builder.newLine(); + builder.append(" "); + builder.append("// single line comment"); + builder.newLine(); + builder.append(" "); + builder.append("}/**"); + builder.newLine(); + builder.append(" "); + builder.append("* This check is javadoc-like commented."); + builder.newLine(); + builder.append(" "); + builder.append("*/warning CategoryNamedW \"Category named W\"(boolean foo=false,boolean bar=true)message \"Category named \'w\'\"{"); + builder.newLine(); + builder.append(" "); + builder.append("for Category c {"); + builder.newLine(); + builder.append(" "); + builder.append("if(\'w\'.equalsIgnoreCase(c.name)){issue}"); + builder.newLine(); + builder.append("}}}"); + builder.newLine(); + final String input = builder.toString(); + + StringConcatenation builder1 = new StringConcatenation(); + builder1.append("package com.avaloq.tools.ddk.check.formatting"); + builder1.newLine(); + builder1.append("import com.avaloq.tools.ddk.check.check.*"); + builder1.newLine(); + builder1.append("catalog CheckFormattingTest"); + builder1.newLine(); + builder1.append("for grammar com.avaloq.tools.ddk.check.Check {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("category \"Label\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("/**"); + builder1.newLine(); + builder1.append(" "); + builder1.append("* @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890"); + builder1.newLine(); + builder1.append(" "); + builder1.append("*/"); + builder1.newLine(); + builder1.append(" "); + builder1.append("live error UniqueID \"Label\""); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"message\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("live error anotherid \"Label\""); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"message {0}, {1}\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("def Name"); + builder1.newLine(); + builder1.append(" "); + builder1.append("for Category list {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("issue anotherid on list bind (3, 2) data (\"\")"); + builder1.newLine(); + builder1.append(" "); + builder1.append("val size ="); + builder1.newLine(); + builder1.append(" "); + builder1.append("if (list.checks !== null) list.checks.size else 0"); + builder1.newLine(); + builder1.append(" "); + builder1.append("if (list.checks?.size > 1) {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("// SL: string value of list"); + builder1.newLine(); + builder1.append(" "); + builder1.append("String::valueOf(list)"); + builder1.newLine(); + builder1.append(" "); + builder1.append("issue UniqueID on list#checks[0]"); + builder1.newLine(); + builder1.append(" "); + builder1.append("} else { /* ML: string value of size */"); + builder1.newLine(); + builder1.append(" "); + builder1.append("String::valueOf(size)"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("@SeverityRange(warning .. error)"); + builder1.newLine(); + builder1.append(" "); + builder1.append("onSave error lastID \"Label\""); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"message\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("// single line comment"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("/**"); + builder1.newLine(); + builder1.append(" "); + builder1.append("* This check is javadoc-like commented."); + builder1.newLine(); + builder1.append(" "); + builder1.append("*/"); + builder1.newLine(); + builder1.append(" "); + builder1.append("warning CategoryNamedW \"Category named W\" (boolean foo = false, boolean bar = true)"); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"Category named \'w\'\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("for Category c {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("if (\'w\'.equalsIgnoreCase(c.name)) {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("issue"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append("}"); + builder1.newLine(); + final String expected = builder1.toString(); + + formatterTestHelper.assertFormatted((FormatterTestRequest it) -> { + // these preferences are usually picked up from the resource, but we are not using a resource here. + MapBasedPreferenceValues prefs = new MapBasedPreferenceValues(); + prefs.put(FormatterPreferenceKeys.indentation, " "); + it.getRequest().setPreferences(prefs); + it.setToBeFormatted(input); + it.setExpectation(expected); + }); + // CHECKSTYLE:CHECK-ON VariableDeclarationUsageDistance + } + + /** + * Test that additional spaces and new lines are removed + */ + @Test + public void testWSRemoved() { + // CHECKSTYLE:CHECK-OFF VariableDeclarationUsageDistance + StringConcatenation builder = new StringConcatenation(); + builder.append(" "); + builder.append("package"); + builder.newLine(); + builder.append(" "); + builder.append("com.avaloq.tools.ddk.check.formatting"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append("import"); + builder.newLine(); + builder.append(" "); + builder.append("com.avaloq.tools.ddk.check.check.*"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append("catalog"); + builder.newLine(); + builder.append(" "); + builder.append("CheckFormattingTest"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append("for"); + builder.newLine(); + builder.append(" "); + builder.append("grammar"); + builder.newLine(); + builder.append(" "); + builder.append("com.avaloq.tools.ddk.check.Check"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("{"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("category"); + builder.newLine(); + builder.append(" "); + builder.append("\"Label\""); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("{"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append("* @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890"); + builder.newLine(); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("live"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("error"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("UniqueID"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("\"Label\""); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("message"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("\"message\""); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("{"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("live error anotherid \"Label\""); + builder.newLine(); + builder.append(" "); + builder.append("message \"message {0}, {1}\" {"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("def"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("Name"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("for"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("Category"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("list"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("{"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("issue"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("anotherid"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("on"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("list"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("bind"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("("); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("3"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(","); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("2"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(")"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("data"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("("); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("\"\""); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(")"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("val"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("size"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("="); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("if"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("("); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("list"); + builder.newLine(); + builder.append(" "); + builder.append("."); + builder.newLine(); + builder.append(" "); + builder.append("checks"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("!=="); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("null"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(")"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("list . checks . size"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("else"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("0"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("if"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("("); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("list.checks ?. size"); + builder.newLine(); + builder.append(" "); + builder.append("> 1)"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("{"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("// SL: string value of list"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("String"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("::"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("valueOf"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("("); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("list"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(")"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("issue UniqueID on list # checks"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("["); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("0"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("]"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("else"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("{"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("/* ML: string value of size */"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("String :: valueOf ( size )"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("SeverityRange"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("("); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("warning"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(".."); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("error"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(")"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("onSave error lastID \"Label\""); + builder.newLine(); + builder.append(" "); + builder.append("message \"message\" {"); + builder.newLine(); + builder.append(" "); + builder.append("// single line comment"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append("* This check is javadoc-like commented."); + builder.newLine(); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("warning CategoryNamedW \"Category named W\""); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("("); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("boolean"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("foo"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("="); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("false"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(","); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("boolean"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("bar"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("="); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("true"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(")"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("message \"Category named \'w\'\" {"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("for Category c {"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("if (\'w\'.equalsIgnoreCase(c.name)) {"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("issue"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.newLine(); + final String input = builder.toString(); + + StringConcatenation builder1 = new StringConcatenation(); + builder1.append("package com.avaloq.tools.ddk.check.formatting"); + builder1.newLine(); + builder1.newLine(); + builder1.append("import com.avaloq.tools.ddk.check.check.*"); + builder1.newLine(); + builder1.newLine(); + builder1.append("catalog CheckFormattingTest"); + builder1.newLine(); + builder1.newLine(); + builder1.append("for grammar com.avaloq.tools.ddk.check.Check {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("category \"Label\" {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("/**"); + builder1.newLine(); + builder1.append(" "); + builder1.append("* @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890"); + builder1.newLine(); + builder1.append(" "); + builder1.append("*/"); + builder1.newLine(); + builder1.append(" "); + builder1.append("live error UniqueID \"Label\""); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"message\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("live error anotherid \"Label\""); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"message {0}, {1}\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("def Name"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("for Category list {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("issue anotherid on list bind (3, 2) data (\"\")"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("val size ="); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("if (list.checks !== null) list.checks.size else 0"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("if (list.checks?.size > 1) {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("// SL: string value of list"); + builder1.newLine(); + builder1.append(" "); + builder1.append("String::valueOf("); + builder1.newLine(); + builder1.append(" "); + builder1.append("list"); + builder1.newLine(); + builder1.append(" "); + builder1.append(")"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("issue UniqueID on list#checks[0]"); + builder1.newLine(); + builder1.append(" "); + builder1.append("} else {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("/* ML: string value of size */"); + builder1.newLine(); + builder1.append(" "); + builder1.append("String::valueOf(size)"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("@SeverityRange(warning .. error)"); + builder1.newLine(); + builder1.append(" "); + builder1.append("onSave error lastID \"Label\""); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"message\" {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("// single line comment"); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("/**"); + builder1.newLine(); + builder1.append(" "); + builder1.append("* This check is javadoc-like commented."); + builder1.newLine(); + builder1.append(" "); + builder1.append("*/"); + builder1.newLine(); + builder1.append(" "); + builder1.append("warning CategoryNamedW \"Category named W\" (boolean foo = false,"); + builder1.newLine(); + builder1.append(" "); + builder1.append("boolean bar = true)"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("message \"Category named \'w\'\" {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("for Category c {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("if (\'w\'.equalsIgnoreCase(c.name)) {"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("issue"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.newLine(); + builder1.append(" "); + builder1.append("}"); + builder1.newLine(); + builder1.append("}"); + builder1.newLine(); + final String expected = builder1.toString(); + + formatterTestHelper.assertFormatted((FormatterTestRequest it) -> { + // these preferences are usually picked up from the resource, but we are not using a resource here. + MapBasedPreferenceValues prefs = new MapBasedPreferenceValues(); + prefs.put(FormatterPreferenceKeys.indentation, " "); + it.getRequest().setPreferences(prefs); + it.setToBeFormatted(input); + it.setExpectation(expected); + }); + // CHECKSTYLE:CHECK-ON VariableDeclarationUsageDistance + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/formatting/CheckFormattingTest.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/formatting/CheckFormattingTest.xtend deleted file mode 100644 index 027a376929..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/formatting/CheckFormattingTest.xtend +++ /dev/null @@ -1,554 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.check.formatting - -import com.avaloq.tools.ddk.check.CheckUiInjectorProvider -import com.google.inject.Inject -import org.eclipse.xtext.formatting2.FormatterPreferenceKeys -import org.eclipse.xtext.preferences.MapBasedPreferenceValues -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.formatter.FormatterTestHelper -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test - -@InjectWith(typeof(CheckUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class CheckFormattingTest { - - @Inject - extension FormatterTestHelper - - /** - * Test that correctly formatted Check sources are not modified. - */ - @Test - def void testFormattedSource() { - val input = ''' - package com.avaloq.tools.ddk.check.formatting - - import com.avaloq.tools.ddk.check.check.* - - catalog CheckFormattingTest - for grammar com.avaloq.tools.ddk.check.Check { - - category "Label" { - - /** - * @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 - */ - live error UniqueID "Label" - message "message" { - } - - live error anotherid "Label" - message "message {0}, {1}" { - } - } - - def Name - for Category list { - issue anotherid on list bind (3, 2) data ("") - val size = - if (list.checks !== null) list.checks.size else 0 - if (list.checks?.size > 1) { - // SL: string value of list - String::valueOf(list) - issue UniqueID on list#checks[0] - } else { - /* ML: string value of size */ - String::valueOf(size) - } - } - - @SeverityRange(warning .. error) - onSave error lastID "Label" - message "message" { - // single line comment - } - - /** - * This check is javadoc-like commented. - */ - warning CategoryNamedW "Category named W" (boolean foo = false, boolean bar = true) - message "Category named 'w'" { - for Category c { - if ('w'.equalsIgnoreCase(c.name)) { - issue - } - } - } - } - ''' - - assertFormatted[ - // these preferences are usually picked up from the resource, but we are not using a resource here. - request.preferences = new MapBasedPreferenceValues() => [put(FormatterPreferenceKeys.indentation, ' ')] - - toBeFormatted = input - expectation = input - ]; - } - - /** - * Test that spaces and new lines are added where expected. - */ - @Test - def void testWSAdded() { - val input = ''' - package com.avaloq.tools.ddk.check.formatting import com.avaloq.tools.ddk.check.check.* catalog CheckFormattingTest - for grammar com.avaloq.tools.ddk.check.Check{category "Label"{/** - * @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 - */live error UniqueID "Label" - message "message"{}live error anotherid "Label" - message "message {0}, {1}" {}} - def Name for Category list{issue anotherid on list bind(3,2)data("") - val size=if(list.checks !== null)list.checks.size else 0 - if(list.checks?.size>1){ - // SL: string value of list - String::valueOf(list)issue UniqueID on list#checks[0]}else{/* ML: string value of size */String::valueOf(size)}} - @SeverityRange(warning..error)onSave error lastID"Label"message "message" { - // single line comment - }/** - * This check is javadoc-like commented. - */warning CategoryNamedW "Category named W"(boolean foo=false,boolean bar=true)message "Category named 'w'"{ - for Category c { - if('w'.equalsIgnoreCase(c.name)){issue} - }}} - ''' - - val expected = ''' - package com.avaloq.tools.ddk.check.formatting - import com.avaloq.tools.ddk.check.check.* - catalog CheckFormattingTest - for grammar com.avaloq.tools.ddk.check.Check { - category "Label" { - /** - * @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 - */ - live error UniqueID "Label" - message "message" { - } - live error anotherid "Label" - message "message {0}, {1}" { - } - } - def Name - for Category list { - issue anotherid on list bind (3, 2) data ("") - val size = - if (list.checks !== null) list.checks.size else 0 - if (list.checks?.size > 1) { - // SL: string value of list - String::valueOf(list) - issue UniqueID on list#checks[0] - } else { /* ML: string value of size */ - String::valueOf(size) - } - } - @SeverityRange(warning .. error) - onSave error lastID "Label" - message "message" { - // single line comment - } - /** - * This check is javadoc-like commented. - */ - warning CategoryNamedW "Category named W" (boolean foo = false, boolean bar = true) - message "Category named 'w'" { - for Category c { - if ('w'.equalsIgnoreCase(c.name)) { - issue - } - } - } - } - ''' - - assertFormatted[ - // these preferences are usually picked up from the resource, but we are not using a resource here. - request.preferences = new MapBasedPreferenceValues() => [put(FormatterPreferenceKeys.indentation, ' ')] - toBeFormatted = input - expectation = expected - ]; - } - - /** - * Test that additional spaces and new lines are removed - */ - @Test - def void testWSRemoved() { - val input = ''' - package - com.avaloq.tools.ddk.check.formatting - - - - import - com.avaloq.tools.ddk.check.check.* - - - - catalog - CheckFormattingTest - - - for - grammar - com.avaloq.tools.ddk.check.Check - - - { - - - - category - "Label" - - { - - - /** - * @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 - */ - - - - live - - error - - UniqueID - - "Label" - - - message - - - "message" - - { - - - } - - live error anotherid "Label" - message "message {0}, {1}" { - - - } - - - } - - - - def - - Name - - - for - - Category - - list - - { - - - issue - - - anotherid - - - on - - - list - - - bind - - - ( - - 3 - - , - - 2 - - ) - - data - - ( - - "" - - ) - - - val - - size - - = - - - if - - ( - - list - . - checks - - !== - - null - - ) - - list . checks . size - - - else - - 0 - - if - - ( - - list.checks ?. size - > 1) - - - { - - - - // SL: string value of list - - - String - - :: - - valueOf - - ( - - list - - ) - - issue UniqueID on list # checks - - [ - - 0 - - ] - } - - - else - - - { - - - - /* ML: string value of size */ - - - String :: valueOf ( size ) - - - } - - - } - - - - @ - - SeverityRange - - ( - - warning - - .. - - error - - ) - - - onSave error lastID "Label" - message "message" { - // single line comment - } - - - - /** - * This check is javadoc-like commented. - */ - - - warning CategoryNamedW "Category named W" - - - ( - - boolean - - foo - - = - - false - - , - - boolean - - bar - - = - - true - - ) - - - message "Category named 'w'" { - - - for Category c { - - - if ('w'.equalsIgnoreCase(c.name)) { - - - issue - - - } - - - } - - - } - - - } - - - ''' - - val expected = ''' - package com.avaloq.tools.ddk.check.formatting - - import com.avaloq.tools.ddk.check.check.* - - catalog CheckFormattingTest - - for grammar com.avaloq.tools.ddk.check.Check { - - category "Label" { - - /** - * @todo Document check. 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 - */ - live error UniqueID "Label" - - message "message" { - } - - live error anotherid "Label" - message "message {0}, {1}" { - } - } - - def Name - - for Category list { - - issue anotherid on list bind (3, 2) data ("") - - val size = - - if (list.checks !== null) list.checks.size else 0 - - if (list.checks?.size > 1) { - - // SL: string value of list - String::valueOf( - list - ) - - issue UniqueID on list#checks[0] - } else { - - /* ML: string value of size */ - String::valueOf(size) - - } - - } - - @SeverityRange(warning .. error) - onSave error lastID "Label" - message "message" { - // single line comment - } - - /** - * This check is javadoc-like commented. - */ - warning CategoryNamedW "Category named W" (boolean foo = false, - boolean bar = true) - - message "Category named 'w'" { - - for Category c { - - if ('w'.equalsIgnoreCase(c.name)) { - - issue - - } - - } - - } - } - ''' - - assertFormatted[ - // these preferences are usually picked up from the resource, but we are not using a resource here. - request.preferences = new MapBasedPreferenceValues() => [put(FormatterPreferenceKeys.indentation, ' ')] - toBeFormatted = input - expectation = expected - ]; - } -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckApiAccessValidationsTest.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckApiAccessValidationsTest.java new file mode 100644 index 0000000000..92e4191ff6 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckApiAccessValidationsTest.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * Contributors: + * Avaloq Group AG - initial API and implementation + */ +package com.avaloq.tools.ddk.check.validation; + +import com.avaloq.tools.ddk.check.CheckUiInjectorProvider; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.google.inject.Inject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.eclipse.xtext.xtype.XtypePackage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@InjectWith(CheckUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckApiAccessValidationsTest { + + @Inject + private ParseHelper parser; + + @Inject + private ValidationTestHelper helper; + + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + private CheckCatalog getTestSource(final String importText) throws Exception { + return parser.parse(String.format(""" + package com.avaloq.example.stuff.checks + import %s + catalog CommonChecks + for grammar com.avaloq.tools.ddk.check.TestLanguage + { + } + """, importText)); + } + + @Test + public void testNonApiAccessDisallowed() throws Exception { + final CheckCatalog model = getTestSource("com.avaloq.tools.ddk.check.check.impl.CheckImpl"); //Not OK - impl not defined as API. + helper.assertWarning(model, XtypePackage.Literals.XIMPORT_DECLARATION, IssueCodes.NON_API_IMPORTED); + } + + @Test + public void testDefinedApiAccessable() throws Exception { + final CheckCatalog model = getTestSource("com.avaloq.tools.ddk.check.check.Check"); //OK! There's an API spec in this plugin's plugin.xml + helper.assertNoIssue(model, XtypePackage.Literals.XIMPORT_DECLARATION, IssueCodes.NON_API_IMPORTED); + } + + @Test + public void testNonAvaloqTypeAccessable() throws Exception { + final CheckCatalog model = getTestSource("java.util.HashMap"); //OK! Not in com.avaloq.* + helper.assertNoIssue(model, XtypePackage.Literals.XIMPORT_DECLARATION, IssueCodes.NON_API_IMPORTED); + } +} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckApiAccessValidationsTest.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckApiAccessValidationsTest.xtend deleted file mode 100644 index 0c9f980703..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckApiAccessValidationsTest.xtend +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * Contributors: - * Avaloq Group AG - initial API and implementation - */ -package com.avaloq.tools.ddk.check.validation - -import org.eclipse.xtext.testing.InjectWith -import com.avaloq.tools.ddk.check.CheckUiInjectorProvider -import com.google.inject.Inject -import org.eclipse.xtext.testing.util.ParseHelper -import com.avaloq.tools.ddk.check.check.CheckCatalog -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.eclipse.xtext.xtype.XtypePackage -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test - -@InjectWith(typeof(CheckUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class CheckApiAccessValidationsTest{ - - @Inject - ParseHelper parser - - @Inject - ValidationTestHelper helper - - def private getTestSource(String importText){ - return parser.parse(''' - package com.avaloq.example.stuff.checks - import «importText» - catalog CommonChecks - for grammar com.avaloq.tools.ddk.check.TestLanguage - { - }''' - ) - } - - @Test - def void testNonApiAccessDisallowed() { - val model = getTestSource("com.avaloq.tools.ddk.check.check.impl.CheckImpl") //Not OK - impl not defined as API. - helper.assertWarning(model, XtypePackage.Literals.XIMPORT_DECLARATION, IssueCodes.NON_API_IMPORTED) - } - - @Test - def void testDefinedApiAccessable() { - val model = getTestSource("com.avaloq.tools.ddk.check.check.Check") //OK! There's an API spec in this plugin's plugin.xml - helper.assertNoIssue(model, XtypePackage.Literals.XIMPORT_DECLARATION, IssueCodes.NON_API_IMPORTED) - } - - @Test - def void testNonAvaloqTypeAccessable() { - val model = getTestSource("java.util.HashMap") //OK! Not in com.avaloq.* - helper.assertNoIssue(model, XtypePackage.Literals.XIMPORT_DECLARATION, IssueCodes.NON_API_IMPORTED) - } -} diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckValidationTest.java b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckValidationTest.java new file mode 100644 index 0000000000..7760493162 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckValidationTest.java @@ -0,0 +1,335 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.validation; + +import com.avaloq.tools.ddk.check.CheckUiInjectorProvider; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.CheckPackage; +import com.avaloq.tools.ddk.check.core.test.util.CheckModelUtil; +import com.google.common.collect.Lists; +import com.google.inject.Inject; + +import java.util.List; + +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.eclipse.xtext.xbase.XbasePackage; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +/* + * Tests for various check validations as implemented in the validation classes + *
    + *
  • com.avaloq.tools.ddk.check.validation.CheckJavaValidator + *
  • com.avaloq.tools.ddk.check.validation.ClasspathBasedChecks + *
+ */ +// CHECKSTYLE:CONSTANTS-OFF +@InjectWith(CheckUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckValidationTest { + + @Inject + private ValidationTestHelper helper; + + @Inject + private ParseHelper parser; + + @Inject + private CheckModelUtil modelUtil; + + /* Tests checkReturnExpressions(XReturnExpression) */ + @Test + public void testReturnExpressions() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithGrammar() + "def SomeDef for X { return null;"); + helper.assertError(model, XbasePackage.Literals.XRETURN_EXPRESSION, IssueCodes.RETURN_IN_IMPL); + } + + /* Tests checkIssuedCheck(XIssueExpression) */ + @Test + public void testIssuedCheck() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithGrammar() + "def SomeDef for X { issue on"); + helper.assertError(model, CheckPackage.Literals.XISSUE_EXPRESSION, IssueCodes.ISSUED_CHECK); + } + + /* Tests checkImplicitIssuedCheck(XIssueExpression) */ + @Test + public void testImplicitIssuedCheck() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithCheck("A") + "for X { issue A"); + helper.assertWarning(model, CheckPackage.Literals.XISSUE_EXPRESSION, IssueCodes.IMPLICIT_ISSUED_CHECK); + final CheckCatalog model2 = parser.parse(modelUtil.modelWithCheck("A") + "for X { issue A"); + helper.assertWarning(model2, CheckPackage.Literals.XISSUE_EXPRESSION, IssueCodes.IMPLICIT_ISSUED_CHECK); + } + + // TODO check included catalogs + + /* Tests checkFileNamingConventions(CheckCatalog) */ + @Test + public void testFileNamingConventions() throws Exception { + final CheckCatalog model = parser.parse("package p catalog c "); + helper.assertError(model, CheckPackage.Literals.CHECK_CATALOG, IssueCodes.WRONG_FILE); + helper.assertError(model, CheckPackage.Literals.CHECK_CATALOG, IssueCodes.WRONG_PACKAGE); + } + + /* Tests checkPackageName(CheckCatalog). All given package names are valid. */ + @Test + public void testPackageNameIsValid() throws Exception { + helper.assertNoError(parser.parse("package p "), IssueCodes.INVALID_PACKAGE_NAME); + helper.assertNoError(parser.parse("package p.q.r "), IssueCodes.INVALID_PACKAGE_NAME); + } + + /* Tests checkPackageName(CheckCatalog). All given package names are invalid. */ + @Test + public void testPackageNameIsInvalid() throws Exception { + // Cannot check package names such as '.p' or 'p..q' because they will cause a parsing error preventing the Java + // check from being executed + helper.assertError(parser.parse("package P.p.P. "), CheckPackage.Literals.CHECK_CATALOG, IssueCodes.INVALID_PACKAGE_NAME); + helper.assertError(parser.parse("package P.package.P. "), CheckPackage.Literals.CHECK_CATALOG, IssueCodes.INVALID_PACKAGE_NAME); + } + + /* Tests checkContextTypeIsUnique(Check) */ + @Test + @Disabled("Tests do not work because of scoping issues at run-time") + public void testContextTypeIsUnique() throws Exception { + // should fail + List contexts = Lists.newArrayList("for C c {issue}", "for C d {issue}"); + CheckCatalog model = parser.parse(modelUtil.modelWithContexts(contexts)); + helper.assertError(model, CheckPackage.Literals.CONTEXT, IssueCodes.CONTEXT_TYPES_NOT_UNIQUE); + + // should fail + contexts = Lists.newArrayList("for C c {issue}", "for D d {issue}", "for C e {issue}"); + model = parser.parse(modelUtil.modelWithContexts(contexts)); + helper.assertError(model, CheckPackage.Literals.CONTEXT, IssueCodes.CONTEXT_TYPES_NOT_UNIQUE); + + // should not fail + contexts = Lists.newArrayList("for org.eclipse.emf.ecore.EObject e {issue}", "for E e {issue}"); + model = parser.parse(modelUtil.modelWithContexts(contexts)); + helper.assertNoError(model, IssueCodes.CONTEXT_TYPES_NOT_UNIQUE); + } + + /* Tests checkGuardsFirstInBlockExpression(Context) */ + @Test + public void testGuardsPrecedeIssues() throws Exception { + // basic case: should not fail + CheckCatalog model = parser.parse(modelUtil.modelWithContext() + "guard(false) issue"); + helper.assertNoError(model, IssueCodes.GUARDS_COME_FIRST); + + // multiple guards: should not fail + model = parser.parse(modelUtil.modelWithContext() + "guard(false) guard(true) if(false) {null} issue"); + helper.assertNoError(model, IssueCodes.GUARDS_COME_FIRST); + + // should not fail + model = parser.parse(modelUtil.modelWithContext() + "guard(false) val x = 1 var y = 2 issue"); + helper.assertNoError(model, IssueCodes.GUARDS_COME_FIRST); + + // guard not first: should fail + model = parser.parse(modelUtil.modelWithContext() + "issue guard(false)"); + helper.assertError(model, CheckPackage.Literals.XGUARD_EXPRESSION, IssueCodes.GUARDS_COME_FIRST); + + // should fail + model = parser.parse(modelUtil.modelWithContext() + "guard(true) issue guard(false)"); + helper.assertError(model, CheckPackage.Literals.XGUARD_EXPRESSION, IssueCodes.GUARDS_COME_FIRST); + + // should fail + model = parser.parse(modelUtil.modelWithContext() + "guard(true) if(true){issue} guard(true)"); + helper.assertError(model, CheckPackage.Literals.XGUARD_EXPRESSION, IssueCodes.GUARDS_COME_FIRST); + } + + /* Tests org.eclipse.xtext.xbase.validation.EarlyExitValidator.checkDeadCode(XBlockExpression) */ + @Test + public void testDeadCode() throws Exception { + // should not fail + CheckCatalog model = parser.parse(modelUtil.modelWithContext() + "issue"); + helper.assertNoError(model, IssueCodes.DEAD_CODE); + + // should fail + model = parser.parse(modelUtil.modelWithContext() + "if(false) {return} else {return} issue"); + helper.assertError(model, XbasePackage.Literals.XEXPRESSION, org.eclipse.xtext.xbase.validation.IssueCodes.UNREACHABLE_CODE); + } + + /* Tests checkIssueExpressionExists(Context) */ + @Test + public void testIssueExpressionExists() throws Exception { + CheckCatalog model = parser.parse(modelUtil.modelWithContext() + "guard(false)"); + helper.assertError(model, CheckPackage.Literals.CONTEXT, IssueCodes.MISSING_ISSUE_EXPRESSION); + + // should not fail + model = parser.parse(modelUtil.modelWithContext() + "if (false) { null } else { issue"); + helper.assertNoError(model, IssueCodes.MISSING_ISSUE_EXPRESSION); + + // should fail + model = parser.parse(modelUtil.modelWithContext() + "if (false) { null } else { null } "); + helper.assertError(model, CheckPackage.Literals.CONTEXT, IssueCodes.MISSING_ISSUE_EXPRESSION); + + // should not fail + model = parser.parse(modelUtil.modelWithContext() + "if (false) { null } else { null } issue"); + helper.assertNoError(model, IssueCodes.MISSING_ISSUE_EXPRESSION); + + // should not fail + model = parser.parse(modelUtil.modelWithContext() + "if (false) { issue "); + helper.assertNoError(model, IssueCodes.MISSING_ISSUE_EXPRESSION); + } + + /* Test checkCheckName(Check). ID is missing. */ + @Test + public void testCheckIDIsMissing() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithCheck("")); + helper.assertError(model, CheckPackage.Literals.CHECK, IssueCodes.MISSING_ID_ON_CHECK); + helper.assertNoErrors(model, CheckPackage.Literals.CHECK, IssueCodes.INVALID_CHECK_NAME); + helper.assertNoWarnings(model, CheckPackage.Literals.CHECK, IssueCodes.INVALID_CHECK_NAME); + } + + /* Test checkCheckName(Check). ID is valid. */ + @Test + public void testCheckIDIsValid() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithCheck("ID")); + helper.assertNoErrors(model, CheckPackage.Literals.CHECK, IssueCodes.MISSING_ID_ON_CHECK); + helper.assertNoErrors(model, CheckPackage.Literals.CHECK, IssueCodes.INVALID_CHECK_NAME); + helper.assertNoWarnings(model, CheckPackage.Literals.CHECK, IssueCodes.INVALID_CHECK_NAME); + } + + // Cannot test with an invalid ID because that would cause a syntax error. + // But CheckJavaValidatorUtilTest.checkNameIsInvalid() tests the relevant logic in CheckJavaValidatorUtil.checkCheckName(String). + + /* Test checkCheckName(Check). ID is discouraged. */ + @Test + public void testCheckIDIsDiscouraged() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithCheck("iD")); + helper.assertNoErrors(model, CheckPackage.Literals.CHECK, IssueCodes.MISSING_ID_ON_CHECK); + helper.assertNoErrors(model, CheckPackage.Literals.CHECK, IssueCodes.INVALID_CHECK_NAME); + helper.assertWarning(model, CheckPackage.Literals.CHECK, IssueCodes.INVALID_CHECK_NAME); + } + + /* Test checkCheckNamesAreUnique(CheckCatalog). IDs are unique. */ + @Test + public void testCheckIDsAreUnique() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithCategory() + modelUtil.emptyCheck("ID1") + modelUtil.emptyCheck("ID2")); + helper.assertNoErrors(model, CheckPackage.Literals.CHECK, IssueCodes.DUPLICATE_CHECK); + } + + /* Test checkCheckNamesAreUnique(CheckCatalog). IDs are not unique. */ + @Test + public void testCheckIDsAreNotUnique() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithCategory() + modelUtil.emptyCheck("ID1") + modelUtil.emptyCheck("ID1")); + helper.assertError(model, CheckPackage.Literals.CHECK, IssueCodes.DUPLICATE_CHECK); + } + + /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are unique. */ + @Test + public void testCategoryIDsAreUnique() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithGrammar() + modelUtil.emptyCategory("ID1", "Label") + modelUtil.emptyCategory("ID2", "Label")); + helper.assertNoErrors(model, CheckPackage.Literals.CATEGORY, IssueCodes.DUPLICATE_CATEGORY); + } + + /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not unique. */ + @Test + public void testCategoryIDsAreNotUnique() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithCategory() + modelUtil.emptyCategory("ID1", "Label") + modelUtil.emptyCategory("ID1", "Label")); + helper.assertError(model, CheckPackage.Literals.CATEGORY, IssueCodes.DUPLICATE_CATEGORY); + } + + /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not present; labels are unique. */ + @Test + public void testCategoryLabelsAreUnique() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithGrammar() + modelUtil.emptyCategory("", "Label1") + modelUtil.emptyCategory("", "Label2")); + helper.assertNoErrors(model, CheckPackage.Literals.CATEGORY, IssueCodes.DUPLICATE_CATEGORY); + } + + /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not present; labels are not unique. */ + @Test + public void testCategoryLabelsAreNotUnique() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithGrammar() + modelUtil.emptyCategory("", "Label1") + modelUtil.emptyCategory("", "Label1")); + helper.assertError(model, CheckPackage.Literals.CATEGORY, IssueCodes.DUPLICATE_CATEGORY); + } + + /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not present; labels are unique but create non-unique names. */ + @Test + public void testCategoryLabelsAreNotUniqueOnceConverted() throws Exception { + final CheckCatalog model = parser.parse(modelUtil.modelWithGrammar() + modelUtil.emptyCategory("", "LabelOne") + modelUtil.emptyCategory("", "Label one")); + helper.assertError(model, CheckPackage.Literals.CATEGORY, IssueCodes.DUPLICATE_CATEGORY); + } + + /* Tests checkSeverityRangeOrder(Check) */ + @Test + public void testSeverityRangeOrder_1() throws Exception { + helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange("warning", "error")), + IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER); + } + + /* Tests checkSeverityRangeOrder(Check) */ + @Test + public void testSeverityRangeOrder_2() throws Exception { + helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange("ignore", "warning")), + IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER); + } + + /* Tests checkSeverityRangeOrder(Check) */ + @Test + public void testSeverityRangeOrder_3() throws Exception { + helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange("info", "info")), + IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER); + } + + /* Tests checkSeverityRangeOrder(Check) */ + @Test + public void testSeverityRangeOrder_4() throws Exception { + helper.assertError(parser.parse(modelUtil.modelWithSeverityRange("info", "ignore")), + CheckPackage.Literals.SEVERITY_RANGE, IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER); + } + + /* Tests checkSeverityRangeOrder(Check) */ + @Test + public void testSeverityRangeOrder_5() throws Exception { + helper.assertError(parser.parse(modelUtil.modelWithSeverityRange("error", "info")), + CheckPackage.Literals.SEVERITY_RANGE, IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER); + } + + /* Tests checkDefaultSeverityInRange(Check) */ + @Test + public void testDefaultSeverityInRange_1() throws Exception { + helper.assertError(parser.parse(modelUtil.modelWithSeverityRange("warning", "error", "info")), + CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE); + } + + /* Tests checkDefaultSeverityInRange(Check) */ + @Test + public void testDefaultSeverityInRange_2() throws Exception { + helper.assertError(parser.parse(modelUtil.modelWithSeverityRange("error", "info", "ignore")), + CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE); + } + + /* Tests checkDefaultSeverityInRange(Check) */ + @Test + public void testDefaultSeverityInRange_3() throws Exception { + helper.assertError(parser.parse(modelUtil.modelWithSeverityRange("error", "error", "ignore")), + CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE); + } + + /* Tests checkDefaultSeverityInRange(Check) */ + @Test + public void testDefaultSeverityInRange_4() throws Exception { + helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange("error", "error", "error")), + IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE); + } + + /* Tests checkDefaultSeverityInRange(Check) */ + @Test + public void testDefaultSeverityInRange_5() throws Exception { + helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange("info", "error", "warning")), + IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE); + } + +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckValidationTest.xtend b/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckValidationTest.xtend deleted file mode 100644 index 42420211ec..0000000000 --- a/com.avaloq.tools.ddk.check.core.test/src/com/avaloq/tools/ddk/check/validation/CheckValidationTest.xtend +++ /dev/null @@ -1,342 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.validation - -import com.avaloq.tools.ddk.check.CheckUiInjectorProvider -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.core.test.util.CheckModelUtil -import com.google.common.collect.Lists -import com.google.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.util.ParseHelper -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.eclipse.xtext.xbase.XbasePackage$Literals -import com.avaloq.tools.ddk.check.validation.IssueCodes -import com.avaloq.tools.ddk.check.check.CheckPackage -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Disabled - -/* - * Tests for various check validations as implemented in the validation classes - *
    - *
  • com.avaloq.tools.ddk.check.validation.CheckJavaValidator - *
  • com.avaloq.tools.ddk.check.validation.ClasspathBasedChecks - *
- */ -@InjectWith(typeof(CheckUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class CheckValidationTest { - - @Inject - ValidationTestHelper helper - - @Inject - ParseHelper parser - - @Inject - CheckModelUtil modelUtil - - /* Tests checkReturnExpressions(XReturnExpression) */ - @Test - def void testReturnExpressions() { - val model = parser.parse(modelUtil.modelWithGrammar + "def SomeDef for X { return null;") - helper.assertError(model, XbasePackage$Literals::XRETURN_EXPRESSION, IssueCodes::RETURN_IN_IMPL) - } - - /* Tests checkIssuedCheck(XIssueExpression) */ - @Test - def void testIssuedCheck() { - val model = parser.parse(modelUtil.modelWithGrammar + "def SomeDef for X { issue on") - helper.assertError(model, CheckPackage$Literals::XISSUE_EXPRESSION, IssueCodes::ISSUED_CHECK) - } - - /* Tests checkImplicitIssuedCheck(XIssueExpression) */ - @Test - def void testImplicitIssuedCheck() { - val model = parser.parse(modelUtil.modelWithCheck("A") + "for X { issue A") - helper.assertWarning(model, CheckPackage$Literals::XISSUE_EXPRESSION, IssueCodes::IMPLICIT_ISSUED_CHECK) - val model2 = parser.parse(modelUtil.modelWithCheck("A") + "for X { issue A") - helper.assertWarning(model2, CheckPackage$Literals::XISSUE_EXPRESSION, IssueCodes::IMPLICIT_ISSUED_CHECK) - } - -//TODO check included catalogs - - /* Tests checkFileNamingConventions(CheckCatalog) */ - @Test - def void testFileNamingConventions() { - val model = parser.parse("package p catalog c ") - helper.assertError(model, CheckPackage$Literals::CHECK_CATALOG, IssueCodes::WRONG_FILE) - helper.assertError(model, CheckPackage$Literals::CHECK_CATALOG, IssueCodes::WRONG_PACKAGE) - } - - /* Tests checkPackageName(CheckCatalog). All given package names are valid. */ - @Test - def void testPackageNameIsValid() { - helper.assertNoError(parser.parse("package p "), IssueCodes::INVALID_PACKAGE_NAME) - helper.assertNoError(parser.parse("package p.q.r "), IssueCodes::INVALID_PACKAGE_NAME) - } - - /* Tests checkPackageName(CheckCatalog). All given package names are invalid. */ - @Test - def void testPackageNameIsInvalid() { - // Cannot check package names such as '.p' or 'p..q' because they will cause a parsing error preventing the Java - // check from being executed - helper.assertError(parser.parse("package P.p.P. "), CheckPackage$Literals::CHECK_CATALOG, IssueCodes::INVALID_PACKAGE_NAME) - helper.assertError(parser.parse("package P.package.P. "), CheckPackage$Literals::CHECK_CATALOG, IssueCodes::INVALID_PACKAGE_NAME) - } - - /* Tests checkContextTypeIsUnique(Check) */ - @Test - @Disabled("Tests do not work because of scoping issues at run-time") - def void testContextTypeIsUnique() { - // should fail - var contexts = Lists::newArrayList("for C c {issue}", "for C d {issue}") - var model = parser.parse(modelUtil.modelWithContexts(contexts)) - helper.assertError(model, CheckPackage$Literals::CONTEXT, IssueCodes::CONTEXT_TYPES_NOT_UNIQUE) - - // should fail - contexts = Lists::newArrayList("for C c {issue}", "for D d {issue}", "for C e {issue}") - model = parser.parse(modelUtil.modelWithContexts(contexts)) - helper.assertError(model, CheckPackage$Literals::CONTEXT, IssueCodes::CONTEXT_TYPES_NOT_UNIQUE) - - // should not fail - contexts = Lists::newArrayList("for org.eclipse.emf.ecore.EObject e {issue}", "for E e {issue}") - model = parser.parse(modelUtil.modelWithContexts(contexts)) - helper.assertNoError(model, IssueCodes::CONTEXT_TYPES_NOT_UNIQUE) - } - - /* Tests checkGuardsFirstInBlockExpression(Context) */ - @Test - def void testGuardsPrecedeIssues() { - // basic case: should not fail - var model = parser.parse(modelUtil.modelWithContext + "guard(false) issue") - helper.assertNoError(model, IssueCodes::GUARDS_COME_FIRST) - - // multiple guards: should not fail - model = parser.parse(modelUtil.modelWithContext + "guard(false) guard(true) if(false) {null} issue") - helper.assertNoError(model, IssueCodes::GUARDS_COME_FIRST) - - // should not fail - model = parser.parse(modelUtil.modelWithContext + "guard(false) val x = 1 var y = 2 issue") - helper.assertNoError(model, IssueCodes::GUARDS_COME_FIRST) - - // guard not first: should fail - model = parser.parse(modelUtil.modelWithContext + "issue guard(false)") - helper.assertError(model, CheckPackage$Literals::XGUARD_EXPRESSION, IssueCodes::GUARDS_COME_FIRST) - - // should fail - model = parser.parse(modelUtil.modelWithContext + "guard(true) issue guard(false)") - helper.assertError(model, CheckPackage$Literals::XGUARD_EXPRESSION, IssueCodes::GUARDS_COME_FIRST) - - // should fail - model = parser.parse(modelUtil.modelWithContext + "guard(true) if(true){issue} guard(true)") - helper.assertError(model, CheckPackage$Literals::XGUARD_EXPRESSION, IssueCodes::GUARDS_COME_FIRST) - } - - /* Tests org.eclipse.xtext.xbase.validation.EarlyExitValidator.checkDeadCode(XBlockExpression) */ - @Test - def void testDeadCode() { - // should not fail - var model = parser.parse(modelUtil.modelWithContext + "issue") - helper.assertNoError(model, IssueCodes::DEAD_CODE) - - // should fail - model = parser.parse(modelUtil.modelWithContext + "if(false) {return} else {return} issue") - helper.assertError(model, XbasePackage$Literals::XEXPRESSION, org::eclipse::xtext::xbase::validation::IssueCodes::UNREACHABLE_CODE) - } - - - - /* Tests checkIssueExpressionExists(Context) */ - @Test - def void testIssueExpressionExists() { - var model = parser.parse(modelUtil.modelWithContext + "guard(false)") - helper.assertError(model, CheckPackage$Literals::CONTEXT, IssueCodes::MISSING_ISSUE_EXPRESSION) - - // should not fail - model = parser.parse(modelUtil.modelWithContext + "if (false) { null } else { issue") - helper.assertNoError(model, IssueCodes::MISSING_ISSUE_EXPRESSION) - - // should fail - model = parser.parse(modelUtil.modelWithContext + "if (false) { null } else { null } ") - helper.assertError(model, CheckPackage$Literals::CONTEXT, IssueCodes::MISSING_ISSUE_EXPRESSION) - - // should not fail - model = parser.parse(modelUtil.modelWithContext + "if (false) { null } else { null } issue") - helper.assertNoError(model, IssueCodes::MISSING_ISSUE_EXPRESSION) - - // should not fail - model = parser.parse(modelUtil.modelWithContext + "if (false) { issue ") - helper.assertNoError(model, IssueCodes::MISSING_ISSUE_EXPRESSION) - } - - /* Test checkCheckName(Check). ID is missing. */ - @Test - def void testCheckIDIsMissing() { - val model = parser.parse(modelUtil.modelWithCheck("")) - helper.assertError(model, CheckPackage$Literals::CHECK, IssueCodes::MISSING_ID_ON_CHECK) - helper.assertNoErrors(model, CheckPackage$Literals::CHECK, IssueCodes::INVALID_CHECK_NAME) - helper.assertNoWarnings(model, CheckPackage$Literals::CHECK, IssueCodes::INVALID_CHECK_NAME) - } - - /* Test checkCheckName(Check). ID is valid. */ - @Test - def void testCheckIDIsValid() { - val model = parser.parse(modelUtil.modelWithCheck("ID")) - helper.assertNoErrors(model, CheckPackage$Literals::CHECK, IssueCodes::MISSING_ID_ON_CHECK) - helper.assertNoErrors(model, CheckPackage$Literals::CHECK, IssueCodes::INVALID_CHECK_NAME) - helper.assertNoWarnings(model, CheckPackage$Literals::CHECK, IssueCodes::INVALID_CHECK_NAME) - } - - // Cannot test with an invalid ID because that would cause a syntax error. - // But CheckJavaValidatorUtilTest.checkNameIsInvalid() tests the relevant logic in CheckJavaValidatorUtil.checkCheckName(String). - - /* Test checkCheckName(Check). ID is discouraged. */ - @Test - def void testCheckIDIsDiscouraged() { - val model = parser.parse(modelUtil.modelWithCheck("iD")) - helper.assertNoErrors(model, CheckPackage$Literals::CHECK, IssueCodes::MISSING_ID_ON_CHECK) - helper.assertNoErrors(model, CheckPackage$Literals::CHECK, IssueCodes::INVALID_CHECK_NAME) - helper.assertWarning(model, CheckPackage$Literals::CHECK, IssueCodes::INVALID_CHECK_NAME) - } - - /* Test checkCheckNamesAreUnique(CheckCatalog). IDs are unique. */ - @Test - def void testCheckIDsAreUnique() { - val model = parser.parse(modelUtil.modelWithCategory + modelUtil.emptyCheck("ID1") + modelUtil.emptyCheck("ID2")) - helper.assertNoErrors(model, CheckPackage$Literals::CHECK, IssueCodes::DUPLICATE_CHECK) - } - - /* Test checkCheckNamesAreUnique(CheckCatalog). IDs are not unique. */ - @Test - def void testCheckIDsAreNotUnique() { - val model = parser.parse(modelUtil.modelWithCategory + modelUtil.emptyCheck("ID1") + modelUtil.emptyCheck("ID1")) - helper.assertError(model, CheckPackage$Literals::CHECK, IssueCodes::DUPLICATE_CHECK) - } - - /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are unique. */ - @Test - def void testCategoryIDsAreUnique() { - val model = parser.parse(modelUtil.modelWithGrammar + modelUtil.emptyCategory("ID1", "Label") + modelUtil.emptyCategory("ID2", "Label")) - helper.assertNoErrors(model, CheckPackage$Literals::CATEGORY, IssueCodes::DUPLICATE_CATEGORY) - } - - /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not unique. */ - @Test - def void testCategoryIDsAreNotUnique() { - val model = parser.parse(modelUtil.modelWithCategory + modelUtil.emptyCategory("ID1", "Label") + modelUtil.emptyCategory("ID1", "Label")) - helper.assertError(model, CheckPackage$Literals::CATEGORY, IssueCodes::DUPLICATE_CATEGORY) - } - - /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not present; labels are unique. */ - @Test - def void testCategoryLabelsAreUnique() { - val model = parser.parse(modelUtil.modelWithGrammar + modelUtil.emptyCategory("", "Label1") + modelUtil.emptyCategory("", "Label2")) - helper.assertNoErrors(model, CheckPackage$Literals::CATEGORY, IssueCodes::DUPLICATE_CATEGORY) - } - - /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not present; labels are not unique. */ - @Test - def void testCategoryLabelsAreNotUnique() { - val model = parser.parse(modelUtil.modelWithGrammar + modelUtil.emptyCategory("", "Label1") + modelUtil.emptyCategory("", "Label1")) - helper.assertError(model, CheckPackage$Literals::CATEGORY, IssueCodes::DUPLICATE_CATEGORY) - } - - /* Test checkCategoryNamesAreUnique(CheckCatalog). IDs are not present; labels are unique but create non-unique names. */ - @Test - def void testCategoryLabelsAreNotUniqueOnceConverted() { - val model = parser.parse(modelUtil.modelWithGrammar + modelUtil.emptyCategory("", "LabelOne") + modelUtil.emptyCategory("", "Label one")) - helper.assertError(model, CheckPackage$Literals::CATEGORY, IssueCodes::DUPLICATE_CATEGORY) - } - - /* Tests checkSeverityRangeOrder(Check) */ - @Test - def void testSeverityRangeOrder_1() { - helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange('warning', 'error')), - IssueCodes::ILLEGAL_SEVERITY_RANGE_ORDER - ) - } - - /* Tests checkSeverityRangeOrder(Check) */ - @Test - def void testSeverityRangeOrder_2() { - helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange('ignore', 'warning')), - IssueCodes::ILLEGAL_SEVERITY_RANGE_ORDER - ) - } - - /* Tests checkSeverityRangeOrder(Check) */ - @Test - def void testSeverityRangeOrder_3() { - helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange('info', 'info')), - IssueCodes::ILLEGAL_SEVERITY_RANGE_ORDER - ) - } - - /* Tests checkSeverityRangeOrder(Check) */ - @Test - def void testSeverityRangeOrder_4() { - helper.assertError(parser.parse(modelUtil.modelWithSeverityRange('info', 'ignore')), - CheckPackage$Literals::SEVERITY_RANGE, IssueCodes::ILLEGAL_SEVERITY_RANGE_ORDER - ) - } - - /* Tests checkSeverityRangeOrder(Check) */ - @Test - def void testSeverityRangeOrder_5() { - helper.assertError(parser.parse(modelUtil.modelWithSeverityRange('error', 'info')), - CheckPackage$Literals::SEVERITY_RANGE, IssueCodes::ILLEGAL_SEVERITY_RANGE_ORDER - ) - } - - /* Tests checkDefaultSeverityInRange(Check) */ - @Test - def void testDefaultSeverityInRange_1() { - helper.assertError(parser.parse(modelUtil.modelWithSeverityRange('warning', 'error', 'info')), - CheckPackage$Literals::CHECK, IssueCodes::DEFAULT_SEVERITY_NOT_IN_RANGE - ) - } - - /* Tests checkDefaultSeverityInRange(Check) */ - @Test - def void testDefaultSeverityInRange_2() { - helper.assertError(parser.parse(modelUtil.modelWithSeverityRange('error', 'info', 'ignore')), - CheckPackage$Literals::CHECK, IssueCodes::DEFAULT_SEVERITY_NOT_IN_RANGE - ) - } - - /* Tests checkDefaultSeverityInRange(Check) */ - @Test - def void testDefaultSeverityInRange_3() { - helper.assertError(parser.parse(modelUtil.modelWithSeverityRange('error', 'error', 'ignore')), - CheckPackage$Literals::CHECK, IssueCodes::DEFAULT_SEVERITY_NOT_IN_RANGE - ) - } - - /* Tests checkDefaultSeverityInRange(Check) */ - @Test - def void testDefaultSeverityInRange_4() { - helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange('error', 'error', 'error')), - IssueCodes::DEFAULT_SEVERITY_NOT_IN_RANGE - ) - } - - /* Tests checkDefaultSeverityInRange(Check) */ - @Test - def void testDefaultSeverityInRange_5() { - helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange('info', 'error', 'warning')), - IssueCodes::DEFAULT_SEVERITY_NOT_IN_RANGE - ) - } - -} diff --git a/com.avaloq.tools.ddk.check.core.test/xtend-gen/.gitignore b/com.avaloq.tools.ddk.check.core.test/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.check.core/.classpath b/com.avaloq.tools.ddk.check.core/.classpath index 0c82b24373..e7a6322800 100644 --- a/com.avaloq.tools.ddk.check.core/.classpath +++ b/com.avaloq.tools.ddk.check.core/.classpath @@ -6,11 +6,6 @@
- - - - - diff --git a/com.avaloq.tools.ddk.check.core/build.properties b/com.avaloq.tools.ddk.check.core/build.properties index 5839f7a848..b533d62244 100644 --- a/com.avaloq.tools.ddk.check.core/build.properties +++ b/com.avaloq.tools.ddk.check.core/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ .,\ plugin.xml,\ diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/compiler/CheckGeneratorConfig.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/compiler/CheckGeneratorConfig.java new file mode 100644 index 0000000000..be32e36cec --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/compiler/CheckGeneratorConfig.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.compiler; + +import org.eclipse.xtext.xbase.compiler.GeneratorConfig; + +@SuppressWarnings("nls") +public class CheckGeneratorConfig extends GeneratorConfig { + + private static final String GENERATE_DOCUMENTATION_PROPERTY = "com.avaloq.tools.ddk.check.GenerateDocumentationForAllChecks"; + + private boolean generateLanguageInternalChecks; + + public boolean isGenerateLanguageInternalChecks() { + return generateLanguageInternalChecks; + } + + public void setGenerateLanguageInternalChecks(final boolean generateLanguageInternalChecks) { + this.generateLanguageInternalChecks = generateLanguageInternalChecks; + } + + public boolean doGenerateDocumentationForAllChecks() { + return Boolean.parseBoolean(System.getProperty(GENERATE_DOCUMENTATION_PROPERTY)); + } +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/compiler/CheckGeneratorConfig.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/compiler/CheckGeneratorConfig.xtend deleted file mode 100644 index 321ecdd7d9..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/compiler/CheckGeneratorConfig.xtend +++ /dev/null @@ -1,27 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.check.compiler - -import org.eclipse.xtext.xbase.compiler.GeneratorConfig -import org.eclipse.xtend.lib.annotations.Accessors - -class CheckGeneratorConfig extends GeneratorConfig { - - val String GENERATE_DOCUMENTATION_PROPERTY = "com.avaloq.tools.ddk.check.GenerateDocumentationForAllChecks" - - @Accessors - boolean generateLanguageInternalChecks = false - - def doGenerateDocumentationForAllChecks() { - return Boolean.parseBoolean(System.getProperty(GENERATE_DOCUMENTATION_PROPERTY)); - } -} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/formatting2/CheckFormatter.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/formatting2/CheckFormatter.java new file mode 100644 index 0000000000..636bb410a0 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/formatting2/CheckFormatter.java @@ -0,0 +1,575 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.formatting2; + +import java.util.Arrays; + +import com.avaloq.tools.ddk.check.check.Category; +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.Context; +import com.avaloq.tools.ddk.check.check.ContextVariable; +import com.avaloq.tools.ddk.check.check.FormalParameter; +import com.avaloq.tools.ddk.check.check.Implementation; +import com.avaloq.tools.ddk.check.check.Member; +import com.avaloq.tools.ddk.check.check.SeverityRange; +import com.avaloq.tools.ddk.check.check.XGuardExpression; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.avaloq.tools.ddk.check.services.CheckGrammarAccess; +import com.google.inject.Inject; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.common.types.JvmFormalParameter; +import org.eclipse.xtext.common.types.JvmGenericArrayTypeReference; +import org.eclipse.xtext.common.types.JvmParameterizedTypeReference; +import org.eclipse.xtext.common.types.JvmTypeConstraint; +import org.eclipse.xtext.common.types.JvmTypeParameter; +import org.eclipse.xtext.common.types.JvmWildcardTypeReference; +import org.eclipse.xtext.formatting2.IFormattableDocument; +import org.eclipse.xtext.formatting2.IHiddenRegionFormatter; +import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.xbase.XAssignment; +import org.eclipse.xtext.xbase.XBasicForLoopExpression; +import org.eclipse.xtext.xbase.XBinaryOperation; +import org.eclipse.xtext.xbase.XBlockExpression; +import org.eclipse.xtext.xbase.XCastedExpression; +import org.eclipse.xtext.xbase.XClosure; +import org.eclipse.xtext.xbase.XCollectionLiteral; +import org.eclipse.xtext.xbase.XConstructorCall; +import org.eclipse.xtext.xbase.XDoWhileExpression; +import org.eclipse.xtext.xbase.XExpression; +import org.eclipse.xtext.xbase.XFeatureCall; +import org.eclipse.xtext.xbase.XForLoopExpression; +import org.eclipse.xtext.xbase.XIfExpression; +import org.eclipse.xtext.xbase.XInstanceOfExpression; +import org.eclipse.xtext.xbase.XListLiteral; +import org.eclipse.xtext.xbase.XMemberFeatureCall; +import org.eclipse.xtext.xbase.XPostfixOperation; +import org.eclipse.xtext.xbase.XReturnExpression; +import org.eclipse.xtext.xbase.XSwitchExpression; +import org.eclipse.xtext.xbase.XSynchronizedExpression; +import org.eclipse.xtext.xbase.XThrowExpression; +import org.eclipse.xtext.xbase.XTryCatchFinallyExpression; +import org.eclipse.xtext.xbase.XTypeLiteral; +import org.eclipse.xtext.xbase.XUnaryOperation; +import org.eclipse.xtext.xbase.XVariableDeclaration; +import org.eclipse.xtext.xbase.XWhileExpression; +import org.eclipse.xtext.xbase.annotations.formatting2.XbaseWithAnnotationsFormatter; +import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotation; +import org.eclipse.xtext.xbase.lib.XbaseGenerated; +import org.eclipse.xtext.xtype.XFunctionTypeRef; +import org.eclipse.xtext.xtype.XImportDeclaration; +import org.eclipse.xtext.xtype.XImportSection; + +@SuppressWarnings({"checkstyle:MethodName", "nls"}) +public class CheckFormatter extends XbaseWithAnnotationsFormatter { + + @Inject + private CheckGrammarAccess checkGrammarAccess; + + /** + * Common formatting for curly brackets that are not handled by the parent formatter. + * + * @param semanticElement + * the element containing '{' and '}' keywords. + * @param document + * the formattable document. + */ + // CHECKSTYLE:CHECK-OFF MagicNumber + private void formatCurlyBracket(final EObject semanticElement, final IFormattableDocument document) { + // low priority so that it can be overridden by other custom formatting rules. + final ISemanticRegion open = regionFor(semanticElement).keyword("{"); + final ISemanticRegion close = regionFor(semanticElement).keyword("}"); + document.interior(open, close, (IHiddenRegionFormatter it) -> { + it.lowPriority(); + it.indent(); + }); + document.append(open, (IHiddenRegionFormatter it) -> { + it.lowPriority(); + it.newLine(); + }); + document.prepend(close, (IHiddenRegionFormatter it) -> { + it.lowPriority(); + it.newLine(); + }); + } + + /** + * Global formatting to be applied across the whole source. + * + * @param requestRoot + * the top level check catalog element. + * @param document + * the formattable document. + */ + private void globalFormatting(final IEObjectRegion requestRoot, final IFormattableDocument document) { + // autowrap everywhere. default to one-space between semantic regions. + // low priority so that it can be overridden by other custom formatting rules. + boolean firstRegion = true; + for (ISemanticRegion region : requestRoot.getAllSemanticRegions()) { + if (firstRegion) { + document.prepend(region, (IHiddenRegionFormatter it) -> { + it.lowPriority(); + it.autowrap(132); + }); + firstRegion = false; + } else { + document.prepend(region, (IHiddenRegionFormatter it) -> { + it.lowPriority(); + it.oneSpace(); + it.autowrap(132); + }); + } + } + } + // CHECKSTYLE:CHECK-ON MagicNumber + + protected void _format(final CheckCatalog checkcatalog, final IFormattableDocument document) { + document.prepend(checkcatalog, (IHiddenRegionFormatter it) -> { + it.noSpace(); + it.setNewLines(0); + }); + document.append(checkcatalog, (IHiddenRegionFormatter it) -> { + it.noSpace(); + it.setNewLines(0, 0, 1); + }); + final ISemanticRegion finalKw = regionFor(checkcatalog).keyword("final"); + final ISemanticRegion catalog = regionFor(checkcatalog).keyword("catalog"); + if (finalKw != null) { + document.prepend(finalKw, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 2, 2); + }); + } else { + document.prepend(catalog, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 1, 2); + }); + } + final ISemanticRegion forKw = regionFor(checkcatalog).keyword("for"); + document.prepend(forKw, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 1, 2); + }); + formatCurlyBracket(checkcatalog, document); + + // Generated model traversal + this.format(checkcatalog.getImports(), document); + for (Category categories : checkcatalog.getCategories()) { + this.format(categories, document); + } + for (Implementation implementations : checkcatalog.getImplementations()) { + this.format(implementations, document); + } + for (Check checks : checkcatalog.getChecks()) { + this.format(checks, document); + } + for (Member members : checkcatalog.getMembers()) { + this.format(members, document); + } + + // ADDED: only fill in the gaps after any high priority formatting has been applied. + IEObjectRegion rootRegion = getTextRegionAccess().regionForRootEObject(); + if (rootRegion != null) { + globalFormatting(rootRegion, document); + } + } + + @Override + protected void _format(final XImportSection ximportsection, final IFormattableDocument document) { + // Generated model traversal + for (XImportDeclaration importDeclarations : ximportsection.getImportDeclarations()) { + // ADDED: formatting added before each import + document.prepend(importDeclarations, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 1, 2); + }); + + this.format(importDeclarations, document); + } + } + + protected void _format(final Category category, final IFormattableDocument document) { + document.prepend(category, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 2, 2); + }); + formatCurlyBracket(category, document); + + // Generated model traversal + for (Check checks : category.getChecks()) { + this.format(checks, document); + } + } + + protected void _format(final Check check, final IFormattableDocument document) { + document.prepend(check, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 2, 2); + }); + final ISemanticRegion open = regionFor(check).keyword("("); + final ISemanticRegion close = regionFor(check).keyword(")"); + document.interior(open, close, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); // High priority to override formatting from adjacent regions and parent formatter. + final ISemanticRegion message = regionFor(check).keyword("message"); + document.prepend(message, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 1, 2); + }); + formatCurlyBracket(check, document); + + // Generated model traversal + this.format(check.getSeverityRange(), document); + for (FormalParameter formalParameters : check.getFormalParameters()) { + // ADDED: formatting added around comma. + // High priority to override formatting from adjacent regions and parent formatter. + final ISemanticRegion comma = immediatelyFollowing(formalParameters).keyword(","); + document.prepend(comma, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + document.append(comma, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.setNewLines(0, 0, 1); + }); + + this.format(formalParameters, document); + } + for (Context contexts : check.getContexts()) { + this.format(contexts, document); + } + } + + protected void _format(final SeverityRange severityrange, final IFormattableDocument document) { + final ISemanticRegion range = regionFor(severityrange).keyword("SeverityRange"); + document.surround(range, (IHiddenRegionFormatter it) -> { + it.noSpace(); + }); + final ISemanticRegion open = regionFor(severityrange).keyword("("); + document.append(open, (IHiddenRegionFormatter it) -> { + it.noSpace(); + }); + final ISemanticRegion close = regionFor(severityrange).keyword(")"); + document.prepend(close, (IHiddenRegionFormatter it) -> { + it.noSpace(); + }); + document.append(close, (IHiddenRegionFormatter it) -> { + it.newLine(); + }); + } + + protected void _format(final Member member, final IFormattableDocument document) { + // Generated model traversal + for (XAnnotation annotations : member.getAnnotations()) { + this.format(annotations, document); + } + this.format(member.getType(), document); + this.format(member.getValue(), document); + } + + protected void _format(final Implementation implementation, final IFormattableDocument document) { + document.prepend(implementation, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 2, 2); + }); + + // Generated model traversal + this.format(implementation.getContext(), document); + } + + protected void _format(final FormalParameter formalparameter, final IFormattableDocument document) { + // Generated model traversal + this.format(formalparameter.getType(), document); + this.format(formalparameter.getRight(), document); + } + + protected void _format(final XUnaryOperation xunaryoperation, final IFormattableDocument document) { + // Generated model traversal + this.format(xunaryoperation.getOperand(), document); + } + + protected void _format(final XListLiteral xlistliteral, final IFormattableDocument document) { + // Generated model traversal + for (XExpression elements : xlistliteral.getElements()) { + this.format(elements, document); + } + } + + protected void _format(final Context context, final IFormattableDocument document) { + document.surround(context, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 2, 2); + }); + + // Generated model traversal + this.format(context.getContextVariable(), document); + this.format(context.getConstraint(), document); + } + + protected void _format(final ContextVariable contextvariable, final IFormattableDocument document) { + // Generated model traversal + this.format(contextvariable.getType(), document); + } + + protected void _format(final XGuardExpression xguardexpression, final IFormattableDocument document) { + document.prepend(xguardexpression, (IHiddenRegionFormatter it) -> { + it.setNewLines(1, 2, 2); + }); + + // Generated model traversal + this.format(xguardexpression.getGuard(), document); + } + + protected void _format(final XIssueExpression xissueexpression, final IFormattableDocument document) { + // High priority to override formatting from adjacent regions and parent formatter. + document.prepend(xissueexpression, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.setNewLines(1, 2, 2); + }); + checkGrammarAccess.getXIssueExpressionAccess().findKeywords("#").forEach((Keyword kw) -> { + final ISemanticRegion hash = regionFor(xissueexpression).keyword(kw); + document.surround(hash, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + }); + final ISemanticRegion openSquare = regionFor(xissueexpression).keyword("["); + document.surround(openSquare, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + final ISemanticRegion closeSquare = regionFor(xissueexpression).keyword("]"); + document.prepend(closeSquare, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + checkGrammarAccess.getXIssueExpressionAccess().findKeywords("(").forEach((Keyword kw) -> { + final ISemanticRegion open = regionFor(xissueexpression).keyword(kw); + document.append(open, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + }); + checkGrammarAccess.getXIssueExpressionAccess().findKeywords(")").forEach((Keyword kw) -> { + final ISemanticRegion close = regionFor(xissueexpression).keyword(kw); + document.prepend(close, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + }); + + // Generated model traversal + this.format(xissueexpression.getMarkerObject(), document); + this.format(xissueexpression.getMarkerIndex(), document); + this.format(xissueexpression.getMessage(), document); + for (XExpression messageParameters : xissueexpression.getMessageParameters()) { + // ADDED: formatting added around comma + final ISemanticRegion comma = immediatelyFollowing(messageParameters).keyword(","); + document.prepend(comma, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + document.append(comma, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.oneSpace(); + }); + + this.format(messageParameters, document); + } + for (XExpression issueData : xissueexpression.getIssueData()) { + // ADDED: formatting added around comma + final ISemanticRegion comma = immediatelyFollowing(issueData).keyword(","); + document.prepend(comma, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + document.append(comma, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.oneSpace(); + }); + + this.format(issueData, document); + } + } + + @Override + protected void _format(final XIfExpression xifexpression, final IFormattableDocument document) { + // High priority to override formatting from adjacent regions and parent formatter. + document.prepend(xifexpression, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.setNewLines(1, 1, 2); + }); + final ISemanticRegion open = regionFor(xifexpression).keyword("("); + final ISemanticRegion close = regionFor(xifexpression).keyword(")"); + document.prepend(open, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.oneSpace(); + }); + document.append(open, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + document.prepend(close, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + document.append(close, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.setNewLines(0); + it.oneSpace(); + }); + final ISemanticRegion elseKw = regionFor(xifexpression).keyword("else"); + document.surround(elseKw, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.setNewLines(0); + it.oneSpace(); + }); + + // defer to super class for model traversal + super._format(xifexpression, document); + } + + @Override + protected void _format(final XMemberFeatureCall xfeaturecall, final IFormattableDocument document) { + // set no space after '::' in CheckUtil::hasQualifiedName(..., and also not after plain "." or "?." + // High priority to override formatting from adjacent regions and parent formatter. + checkGrammarAccess.getXMemberFeatureCallAccess().findKeywords(".").forEach((Keyword kw) -> { + final ISemanticRegion dot = regionFor(xfeaturecall).keyword(kw); + document.append(dot, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + }); + checkGrammarAccess.getXMemberFeatureCallAccess().findKeywords("?.").forEach((Keyword kw) -> { + final ISemanticRegion queryDot = regionFor(xfeaturecall).keyword(kw); + document.append(queryDot, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + }); + checkGrammarAccess.getXMemberFeatureCallAccess().findKeywords("::").forEach((Keyword kw) -> { + final ISemanticRegion colonColon = regionFor(xfeaturecall).keyword(kw); + document.append(colonColon, (IHiddenRegionFormatter it) -> { + it.highPriority(); + it.noSpace(); + }); + }); + + // defer to super class for model traversal + super._format(xfeaturecall, document); + } + + @Override + @XbaseGenerated + public void format(final Object xlistliteral, final IFormattableDocument document) { + if (xlistliteral instanceof JvmTypeParameter) { + _format((JvmTypeParameter) xlistliteral, document); + } else if (xlistliteral instanceof JvmFormalParameter) { + _format((JvmFormalParameter) xlistliteral, document); + } else if (xlistliteral instanceof XtextResource) { + _format((XtextResource) xlistliteral, document); + } else if (xlistliteral instanceof XAssignment) { + _format((XAssignment) xlistliteral, document); + } else if (xlistliteral instanceof XBinaryOperation) { + _format((XBinaryOperation) xlistliteral, document); + } else if (xlistliteral instanceof XDoWhileExpression) { + _format((XDoWhileExpression) xlistliteral, document); + } else if (xlistliteral instanceof XFeatureCall) { + _format((XFeatureCall) xlistliteral, document); + } else if (xlistliteral instanceof XListLiteral) { + _format((XListLiteral) xlistliteral, document); + } else if (xlistliteral instanceof XMemberFeatureCall) { + _format((XMemberFeatureCall) xlistliteral, document); + } else if (xlistliteral instanceof XPostfixOperation) { + _format((XPostfixOperation) xlistliteral, document); + } else if (xlistliteral instanceof XUnaryOperation) { + _format((XUnaryOperation) xlistliteral, document); + } else if (xlistliteral instanceof XWhileExpression) { + _format((XWhileExpression) xlistliteral, document); + } else if (xlistliteral instanceof XFunctionTypeRef) { + _format((XFunctionTypeRef) xlistliteral, document); + } else if (xlistliteral instanceof Category) { + _format((Category) xlistliteral, document); + } else if (xlistliteral instanceof Check) { + _format((Check) xlistliteral, document); + } else if (xlistliteral instanceof CheckCatalog) { + _format((CheckCatalog) xlistliteral, document); + } else if (xlistliteral instanceof Context) { + _format((Context) xlistliteral, document); + } else if (xlistliteral instanceof Implementation) { + _format((Implementation) xlistliteral, document); + } else if (xlistliteral instanceof Member) { + _format((Member) xlistliteral, document); + } else if (xlistliteral instanceof XGuardExpression) { + _format((XGuardExpression) xlistliteral, document); + } else if (xlistliteral instanceof XIssueExpression) { + _format((XIssueExpression) xlistliteral, document); + } else if (xlistliteral instanceof JvmGenericArrayTypeReference) { + _format((JvmGenericArrayTypeReference) xlistliteral, document); + } else if (xlistliteral instanceof JvmParameterizedTypeReference) { + _format((JvmParameterizedTypeReference) xlistliteral, document); + } else if (xlistliteral instanceof JvmWildcardTypeReference) { + _format((JvmWildcardTypeReference) xlistliteral, document); + } else if (xlistliteral instanceof XBasicForLoopExpression) { + _format((XBasicForLoopExpression) xlistliteral, document); + } else if (xlistliteral instanceof XBlockExpression) { + _format((XBlockExpression) xlistliteral, document); + } else if (xlistliteral instanceof XCastedExpression) { + _format((XCastedExpression) xlistliteral, document); + } else if (xlistliteral instanceof XClosure) { + _format((XClosure) xlistliteral, document); + } else if (xlistliteral instanceof XCollectionLiteral) { + _format((XCollectionLiteral) xlistliteral, document); + } else if (xlistliteral instanceof XConstructorCall) { + _format((XConstructorCall) xlistliteral, document); + } else if (xlistliteral instanceof XForLoopExpression) { + _format((XForLoopExpression) xlistliteral, document); + } else if (xlistliteral instanceof XIfExpression) { + _format((XIfExpression) xlistliteral, document); + } else if (xlistliteral instanceof XInstanceOfExpression) { + _format((XInstanceOfExpression) xlistliteral, document); + } else if (xlistliteral instanceof XReturnExpression) { + _format((XReturnExpression) xlistliteral, document); + } else if (xlistliteral instanceof XSwitchExpression) { + _format((XSwitchExpression) xlistliteral, document); + } else if (xlistliteral instanceof XSynchronizedExpression) { + _format((XSynchronizedExpression) xlistliteral, document); + } else if (xlistliteral instanceof XThrowExpression) { + _format((XThrowExpression) xlistliteral, document); + } else if (xlistliteral instanceof XTryCatchFinallyExpression) { + _format((XTryCatchFinallyExpression) xlistliteral, document); + } else if (xlistliteral instanceof XTypeLiteral) { + _format((XTypeLiteral) xlistliteral, document); + } else if (xlistliteral instanceof XVariableDeclaration) { + _format((XVariableDeclaration) xlistliteral, document); + } else if (xlistliteral instanceof XAnnotation) { + _format((XAnnotation) xlistliteral, document); + } else if (xlistliteral instanceof ContextVariable) { + _format((ContextVariable) xlistliteral, document); + } else if (xlistliteral instanceof FormalParameter) { + _format((FormalParameter) xlistliteral, document); + } else if (xlistliteral instanceof SeverityRange) { + _format((SeverityRange) xlistliteral, document); + } else if (xlistliteral instanceof JvmTypeConstraint) { + _format((JvmTypeConstraint) xlistliteral, document); + } else if (xlistliteral instanceof XExpression) { + _format((XExpression) xlistliteral, document); + } else if (xlistliteral instanceof XImportDeclaration) { + _format((XImportDeclaration) xlistliteral, document); + } else if (xlistliteral instanceof XImportSection) { + _format((XImportSection) xlistliteral, document); + } else if (xlistliteral instanceof EObject) { + _format((EObject) xlistliteral, document); + } else if (xlistliteral == null) { + _format((Void) null, document); + } else if (xlistliteral != null) { + _format(xlistliteral, document); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(xlistliteral, document).toString()); + } + } +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/formatting2/CheckFormatter.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/formatting2/CheckFormatter.xtend deleted file mode 100644 index 4c6c39eeab..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/formatting2/CheckFormatter.xtend +++ /dev/null @@ -1,301 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.formatting2; - -import com.avaloq.tools.ddk.check.check.Category -import com.avaloq.tools.ddk.check.check.Check -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.Context -import com.avaloq.tools.ddk.check.check.ContextVariable -import com.avaloq.tools.ddk.check.check.FormalParameter -import com.avaloq.tools.ddk.check.check.Implementation -import com.avaloq.tools.ddk.check.check.Member -import com.avaloq.tools.ddk.check.check.SeverityRange -import com.avaloq.tools.ddk.check.check.XGuardExpression -import com.avaloq.tools.ddk.check.check.XIssueExpression -import com.avaloq.tools.ddk.check.services.CheckGrammarAccess -import com.google.inject.Inject -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtext.formatting2.IFormattableDocument -import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion -import org.eclipse.xtext.xbase.XExpression -import org.eclipse.xtext.xbase.XIfExpression -import org.eclipse.xtext.xbase.XListLiteral -import org.eclipse.xtext.xbase.XMemberFeatureCall -import org.eclipse.xtext.xbase.XUnaryOperation -import org.eclipse.xtext.xbase.annotations.formatting2.XbaseWithAnnotationsFormatter -import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotation -import org.eclipse.xtext.xtype.XImportDeclaration -import org.eclipse.xtext.xtype.XImportSection - -class CheckFormatter extends XbaseWithAnnotationsFormatter { - - @Inject extension CheckGrammarAccess - - /** - * Common formatting for curly brackets that are not handled by the parent formatter. - * - * @param semanticElement - * the element containing '{' and '}' keywords. - * @param document - * the formattable document. - */ - def private void formatCurlyBracket(EObject semanticElement, extension IFormattableDocument document) { - // low priority so that it can be overridden by other custom formatting rules. - val open = semanticElement.regionFor.keyword('{') - val close = semanticElement.regionFor.keyword('}') - interior(open, close)[lowPriority indent] - append(open)[lowPriority newLine] - prepend(close)[lowPriority newLine] - } - - /** - * Global formatting to be applied across the whole source. - * - * @param checkcatalog - * the top level check catalog element. - * @param document - * the formattable document. - */ - def private void globalFormatting(IEObjectRegion requestRoot, extension IFormattableDocument document) { - // autowrap everywhere. default to one-space between semantic regions. - // low priority so that it can be overridden by other custom formatting rules. - var firstRegion = true - for(region : requestRoot.allSemanticRegions) { - if (firstRegion) { - region.prepend[lowPriority autowrap(132)] - firstRegion = false - } else { - region.prepend[lowPriority oneSpace autowrap(132)] - } - } - } - - def dispatch void format(CheckCatalog checkcatalog, extension IFormattableDocument document) { - prepend(checkcatalog)[noSpace newLines=0] - append(checkcatalog)[noSpace setNewLines(0, 0, 1)] - val finalKw = checkcatalog.regionFor.keyword('final') - val catalog = checkcatalog.regionFor.keyword('catalog') - if (finalKw !== null) { - prepend(finalKw)[setNewLines(1, 2, 2)] - } else { - prepend(catalog)[setNewLines(1, 1, 2)] - } - val forKw = checkcatalog.regionFor.keyword('for') - prepend(forKw)[setNewLines(1, 1, 2)] - formatCurlyBracket(checkcatalog, document) - - // Generated model traversal - format(checkcatalog.getImports(), document); - for (Category categories : checkcatalog.getCategories()) { - format(categories, document); - } - for (Implementation implementations : checkcatalog.getImplementations()) { - format(implementations, document); - } - for (Check checks : checkcatalog.getChecks()) { - format(checks, document); - } - for (Member members : checkcatalog.getMembers()) { - format(members, document); - } - - // ADDED: only fill in the gaps after any high priority formatting has been applied. - textRegionAccess.regionForRootEObject?.globalFormatting(document) - } - - override dispatch void format(XImportSection ximportsection, extension IFormattableDocument document) { - // Generated model traversal - for (XImportDeclaration importDeclarations : ximportsection.getImportDeclarations()) { - // ADDED: formatting added before each import - prepend(importDeclarations)[setNewLines(1, 1, 2)] - - format(importDeclarations, document); - } - } - - def dispatch void format(Category category, extension IFormattableDocument document) { - prepend(category)[setNewLines(1, 2, 2)] - formatCurlyBracket(category, document) - - // Generated model traversal - for (Check checks : category.getChecks()) { - format(checks, document); - } - } - - def dispatch void format(Check check, extension IFormattableDocument document) { - prepend(check)[setNewLines(1, 2, 2)] - val open = check.regionFor.keyword('(') - val close = check.regionFor.keyword(')') - interior(open, close)[highPriority noSpace] // High priority to override formatting from adjacent regions and parent formatter. - val message = check.regionFor.keyword('message') - prepend(message)[setNewLines(1, 1, 2)] - formatCurlyBracket(check, document) - - // Generated model traversal - format(check.getSeverityRange(), document); - for (FormalParameter formalParameters : check.getFormalParameters()) { - // ADDED: formatting added around comma. - // High priority to override formatting from adjacent regions and parent formatter. - val comma = immediatelyFollowing(formalParameters).keyword(',') - prepend(comma)[highPriority noSpace] - append(comma)[highPriority setNewLines(0, 0, 1)] - - format(formalParameters, document); - } - for (Context contexts : check.getContexts()) { - format(contexts, document); - } - } - - def dispatch void format(SeverityRange severityrange, extension IFormattableDocument document) { - val range = severityrange.regionFor.keyword('SeverityRange') - surround(range)[noSpace] - val open = severityrange.regionFor.keyword('(') - append(open)[noSpace] - val close = severityrange.regionFor.keyword(')') - prepend(close)[noSpace] - append(close)[newLine] - } - - def dispatch void format(Member member, extension IFormattableDocument document) { - // Generated model traversal - for (XAnnotation annotations : member.getAnnotations()) { - format(annotations, document); - } - format(member.getType(), document); - format(member.getValue(), document); - } - - def dispatch void format(Implementation implementation, extension IFormattableDocument document) { - prepend(implementation)[setNewLines(1, 2, 2)] - - // Generated model traversal - format(implementation.getContext(), document); - } - - def dispatch void format(FormalParameter formalparameter, extension IFormattableDocument document) { - // Generated model traversal - format(formalparameter.getType(), document); - format(formalparameter.getRight(), document); - } - - def dispatch void format(XUnaryOperation xunaryoperation, extension IFormattableDocument document) { - // Generated model traversal - format(xunaryoperation.getOperand(), document); - } - - def dispatch void format(XListLiteral xlistliteral, extension IFormattableDocument document) { - // Generated model traversal - for (XExpression elements : xlistliteral.getElements()) { - format(elements, document); - } - } - - def dispatch void format(Context context, extension IFormattableDocument document) { - surround(context)[setNewLines(1, 2, 2)] - - // Generated model traversal - format(context.getContextVariable(), document); - format(context.getConstraint(), document); - } - - def dispatch void format(ContextVariable contextvariable, extension IFormattableDocument document) { - // Generated model traversal - format(contextvariable.getType(), document); - } - - def dispatch void format(XGuardExpression xguardexpression, extension IFormattableDocument document) { - prepend(xguardexpression)[setNewLines(1, 2, 2)] - - // Generated model traversal - format(xguardexpression.getGuard(), document); - } - - def dispatch void format(XIssueExpression xissueexpression, extension IFormattableDocument document) { - // High priority to override formatting from adjacent regions and parent formatter. - prepend(xissueexpression)[highPriority setNewLines(1, 2, 2)] - XIssueExpressionAccess.findKeywords('#').forEach[ - val hash = xissueexpression.regionFor.keyword(it) - surround(hash)[highPriority noSpace] - ] - val openSquare = xissueexpression.regionFor.keyword('[') - surround(openSquare)[highPriority noSpace] - val closeSquare = xissueexpression.regionFor.keyword(']') - prepend(closeSquare)[highPriority noSpace] - XIssueExpressionAccess.findKeywords('(').forEach[ - val open = xissueexpression.regionFor.keyword(it) - append(open)[highPriority noSpace] - ] - XIssueExpressionAccess.findKeywords(')').forEach[ - val close = xissueexpression.regionFor.keyword(it) - prepend(close)[highPriority noSpace] - ] - - // Generated model traversal - format(xissueexpression.getMarkerObject(), document); - format(xissueexpression.getMarkerIndex(), document); - format(xissueexpression.getMessage(), document); - for (XExpression messageParameters : xissueexpression.getMessageParameters()) { - // ADDED: formatting added around comma - val comma = immediatelyFollowing(messageParameters).keyword(',') - prepend(comma)[highPriority noSpace] - append(comma)[highPriority oneSpace] - - format(messageParameters, document); - } - for (XExpression issueData : xissueexpression.getIssueData()) { - // ADDED: formatting added around comma - val comma = immediatelyFollowing(issueData).keyword(',') - prepend(comma)[highPriority noSpace] - append(comma)[highPriority oneSpace] - - format(issueData, document); - } - } - - override dispatch void format(XIfExpression xifexpression, extension IFormattableDocument document) { - // High priority to override formatting from adjacent regions and parent formatter. - prepend(xifexpression)[highPriority setNewLines(1, 1, 2)] - val open = xifexpression.regionFor.keyword('(') - val close = xifexpression.regionFor.keyword(')') - prepend(open)[highPriority oneSpace] - append(open)[highPriority noSpace] - prepend(close)[highPriority noSpace] - append(close)[highPriority newLines=0 oneSpace] - val elseKw = xifexpression.regionFor.keyword('else') - surround(elseKw)[highPriority newLines=0 oneSpace] - - // defer to super class for model traversal - super._format(xifexpression, document) - } - - override dispatch void format(XMemberFeatureCall xfeaturecall, extension IFormattableDocument document) { - // set no space after '::' in CheckUtil::hasQualifiedName(..., and also not after plain "." or "?." - // High priority to override formatting from adjacent regions and parent formatter. - XMemberFeatureCallAccess.findKeywords('.').forEach[ - val dot = xfeaturecall.regionFor.keyword(it) - append(dot)[highPriority noSpace] - ] - XMemberFeatureCallAccess.findKeywords('?.').forEach[ - val queryDot = xfeaturecall.regionFor.keyword(it) - append(queryDot)[highPriority noSpace] - ] - XMemberFeatureCallAccess.findKeywords('::').forEach[ - val colonColon = xfeaturecall.regionFor.keyword(it) - append(colonColon)[highPriority noSpace] - ] - - // defer to super class for model traversal - super._format(xfeaturecall, document) - } -} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.java new file mode 100644 index 0000000000..49d7ba480a --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.java @@ -0,0 +1,278 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.generator; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.avaloq.tools.ddk.check.check.Category; +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.FormalParameter; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.avaloq.tools.ddk.check.compiler.CheckGeneratorConfig; +import com.avaloq.tools.ddk.check.compiler.ICheckGeneratorConfigProvider; +import com.avaloq.tools.ddk.check.util.CheckUtil; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.common.types.JvmField; +import org.eclipse.xtext.generator.AbstractFileSystemAccess; +import org.eclipse.xtext.generator.IFileSystemAccess; +import org.eclipse.xtext.generator.OutputConfiguration; +import org.eclipse.xtext.xbase.compiler.GeneratorConfig; +import org.eclipse.xtext.xbase.compiler.JvmModelGenerator; +import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable; +import org.eclipse.xtext.xbase.lib.Functions.Function1; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.IteratorExtensions; +import org.eclipse.xtext.xbase.lib.StringExtensions; + +@SuppressWarnings({"checkstyle:MethodName", "nls"}) +public class CheckGenerator extends JvmModelGenerator { + + @Inject + private CheckGeneratorExtensions generatorExtensions; + + @Inject + private CheckGeneratorNaming checkGeneratorNaming; + + @Inject + private CheckCompiler compiler; + + @Inject + private ICheckGeneratorConfigProvider generatorConfigProvider; + + @Override + public void doGenerate(final Resource resource, final IFileSystemAccess fsa) { + super.doGenerate(resource, fsa); // Generate validator, catalog, and preference initializer from inferred Jvm models. + URI uri = null; + if (resource != null) { + uri = resource.getURI(); + } + final CheckGeneratorConfig config = generatorConfigProvider.get(uri); + Iterable catalogs = Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), CheckCatalog.class); + for (CheckCatalog catalog : catalogs) { + fsa.generateFile(checkGeneratorNaming.issueCodesFilePath(catalog), compileIssueCodes(catalog)); + fsa.generateFile(checkGeneratorNaming.standaloneSetupPath(catalog), compileStandaloneSetup(catalog)); + + // change output path for service registry + fsa.generateFile( + CheckUtil.serviceRegistryClassName(), + CheckGeneratorConstants.CHECK_REGISTRY_OUTPUT, + generateServiceRegistry(catalog, CheckUtil.serviceRegistryClassName(), fsa)); + // generate documentation for SCA-checks only + if (config != null && (config.doGenerateDocumentationForAllChecks() || !config.isGenerateLanguageInternalChecks())) { + // change output path for html files to docs/ + fsa.generateFile(checkGeneratorNaming.docFileName(catalog), CheckGeneratorConstants.CHECK_DOC_OUTPUT, compileDoc(catalog)); + } + } + } + + // CHECKSTYLE:CONSTANTS-OFF + /* Documentation compiler, generates HTML output. */ + public CharSequence compileDoc(final CheckCatalog catalog) { + final CharSequence body = bodyDoc(catalog); + final String formattedDescription = generatorExtensions.formatDescription(catalog.getDescription()); + final String descriptionHtml = formattedDescription != null ? "

" + formattedDescription + "

\n" : ""; + final String name = catalog.getName(); + return String.format(""" + + + + + + %1$s + + + +

Check Catalog %1$s

+ %2$s %3$s + + + + """, name, descriptionHtml, body); + } + + public CharSequence bodyDoc(final CheckCatalog catalog) { + final StringBuilder sb = new StringBuilder(512); + for (Check check : catalog.getChecks()) { + sb.append("

").append(check.getLabel()) + .append(" (").append(check.getDefaultSeverity().name().toLowerCase()) + .append(")

\n"); + final String formattedCheckDescription = generatorExtensions.formatDescription(check.getDescription()); + if (formattedCheckDescription != null) { + sb.append(formattedCheckDescription).append('\n'); + } + sb.append("

Message: ").append(generatorExtensions.replacePlaceholder(check.getMessage())) + .append("


\n"); + } + for (Category category : catalog.getCategories()) { + sb.append("
\n"); + sb.append("

").append(category.getLabel()).append("

\n"); + final String formattedCategoryDescription = generatorExtensions.formatDescription(category.getDescription()); + if (formattedCategoryDescription != null) { + sb.append(" ").append(formattedCategoryDescription).append('\n'); + } + for (Check check : category.getChecks()) { + sb.append("
\n"); + sb.append("

").append(check.getLabel()) + .append(" (").append(check.getDefaultSeverity().name().toLowerCase()) + .append(")

\n"); + final String formattedCheckDescription = generatorExtensions.formatDescription(check.getDescription()); + if (formattedCheckDescription != null) { + sb.append(" ").append(formattedCheckDescription).append('\n'); + } + sb.append("

Message: ").append(generatorExtensions.replacePlaceholder(check.getMessage())) + .append("

\n"); + sb.append("
\n"); + } + sb.append("
\n"); + } + return sb; + } + + /* + * Creates an IssueCodes file for a Check Catalog. Every Check Catalog will have its own file + * of issue codes. + */ + public CharSequence compileIssueCodes(final CheckCatalog catalog) { + final Iterable allIssues = generatorExtensions.checkAndImplementationIssues(catalog); + final Function1 keyFunction = (XIssueExpression issue) -> { + return CheckGeneratorExtensions.issueCode(issue); + }; + final Function1 valueFunction = (XIssueExpression issue) -> { + return CheckGeneratorExtensions.issueName(issue); + }; + final Map allIssueNames = IterableExtensions.toMap(allIssues, keyFunction, valueFunction); + final StringBuilder sb = new StringBuilder(512); + if (!StringExtensions.isNullOrEmpty(catalog.getPackageName())) { + sb.append("package ").append(catalog.getPackageName()).append(";\n"); + } + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Issue codes which may be used to address validation issues (for instance in quickfixes).\n"); + sb.append(" */\n"); + sb.append("@SuppressWarnings(\"all\")\n"); + sb.append("public final class ").append(CheckGeneratorNaming.issueCodesClassName(catalog)).append(" {\n"); + sb.append('\n'); + final List sortedKeys = IterableExtensions.sort(allIssueNames.keySet()); + for (String issueCode : sortedKeys) { + sb.append(" public static final String ").append(issueCode) + .append(" = \"").append(CheckGeneratorExtensions.issueCodeValue(catalog, allIssueNames.get(issueCode))) + .append("\";\n"); + } + sb.append('\n'); + sb.append(" private ").append(CheckGeneratorNaming.issueCodesClassName(catalog)).append("() {\n"); + sb.append(" // Prevent instantiation.\n"); + sb.append(" }\n"); + sb.append("}\n"); + return sb; + } + + /* + * Generates the Java standalone setup class which will be called by the ServiceRegistry. + */ + public CharSequence compileStandaloneSetup(final CheckCatalog catalog) { + final StringBuilder sb = new StringBuilder(2048); + if (!StringExtensions.isNullOrEmpty(catalog.getPackageName())) { + sb.append("package ").append(catalog.getPackageName()).append(";\n"); + } + sb.append('\n'); + sb.append("import org.apache.logging.log4j.Logger;\n"); + sb.append("import org.apache.logging.log4j.LogManager;\n"); + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.check.runtime.configuration.ModelLocation;\n"); + sb.append("import com.avaloq.tools.ddk.check.runtime.registry.ICheckCatalogRegistry;\n"); + sb.append("import com.avaloq.tools.ddk.check.runtime.registry.ICheckValidatorRegistry;\n"); + sb.append("import com.avaloq.tools.ddk.check.runtime.registry.ICheckValidatorStandaloneSetup;\n"); + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Standalone setup for ").append(catalog.getName()).append(" as required by the standalone builder.\n"); + sb.append(" */\n"); + sb.append("@SuppressWarnings(\"nls\")\n"); + sb.append("public class ").append(checkGeneratorNaming.standaloneSetupClassName(catalog)) + .append(" implements ICheckValidatorStandaloneSetup {\n"); + sb.append('\n'); + sb.append(" private static final Logger LOG = LogManager.getLogger(") + .append(checkGeneratorNaming.standaloneSetupClassName(catalog)).append(".class);\n"); + final Grammar grammar = catalog.getGrammar(); + if (grammar != null) { + sb.append(" private static final String GRAMMAR_NAME = \"") + .append(grammar.getName()).append("\";\n"); + } + sb.append(" private static final String CATALOG_FILE_PATH = \"") + .append(checkGeneratorNaming.checkFilePath(catalog)).append("\";\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public void doSetup() {\n"); + sb.append(" ICheckValidatorRegistry.INSTANCE.registerValidator("); + if (grammar != null) { + sb.append("GRAMMAR_NAME, "); + } + sb.append("new ").append(checkGeneratorNaming.validatorClassName(catalog)).append("());\n"); + sb.append(" ICheckCatalogRegistry.INSTANCE.registerCatalog("); + if (grammar != null) { + sb.append("GRAMMAR_NAME, "); + } + sb.append("new ModelLocation(\n"); + sb.append(" ").append(checkGeneratorNaming.standaloneSetupClassName(catalog)) + .append(".class.getClassLoader().getResource(CATALOG_FILE_PATH), CATALOG_FILE_PATH));\n"); + sb.append(" LOG.info(\"Standalone setup done for ") + .append(checkGeneratorNaming.checkFilePath(catalog)).append("\");\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public String toString() {\n"); + sb.append(" return \"CheckValidatorSetup(") + .append(catalog.eResource().getURI().path()).append(")\";\n"); + sb.append(" }\n"); + sb.append("}\n"); + return sb; + } + // CHECKSTYLE:CONSTANTS-ON + + /* + * Writes contents of the service registry file containing fully qualified class names of all validators. + * See also http://docs.oracle.com/javase/1.4.2/docs/api/javax/imageio/spi/ServiceRegistry.html + */ + public CharSequence generateServiceRegistry(final CheckCatalog catalog, final String serviceRegistryFileName, final IFileSystemAccess fsa) { + final OutputConfiguration config = ((AbstractFileSystemAccess) fsa).getOutputConfigurations().get(CheckGeneratorConstants.CHECK_REGISTRY_OUTPUT); + final String outputDirectory = config.getOutputDirectory(); + final String path = outputDirectory + "/" + serviceRegistryFileName; + final Set contents = generatorExtensions.getContents(catalog, path); + contents.add(checkGeneratorNaming.qualifiedStandaloneSetupClassName(catalog)); + final StringBuilder sb = new StringBuilder(512); + for (String c : contents) { + sb.append(c).append('\n'); + } + return sb; + } + + @Override + public ITreeAppendable _generateMember(final JvmField field, final ITreeAppendable appendable, final GeneratorConfig config) { + // Suppress generation of the "artificial" fields for FormalParameters in check impls, but not elsewhere. + if (field.isFinal() && !field.isStatic()) { // A bit hacky to use this as the distinction... + final FormalParameter parameter = compiler.getFormalParameter(field); + if (parameter != null) { + return appendable; + } + } + return super._generateMember(field, appendable, config); + } +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.xtend deleted file mode 100644 index 1753758cc6..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGenerator.xtend +++ /dev/null @@ -1,215 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.generator - -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.util.CheckUtil -import com.google.inject.Inject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.AbstractFileSystemAccess -import org.eclipse.xtext.generator.IFileSystemAccess -import org.eclipse.xtext.xbase.compiler.JvmModelGenerator - -import static org.eclipse.xtext.xbase.lib.IteratorExtensions.* -import com.avaloq.tools.ddk.check.check.FormalParameter -import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable -import org.eclipse.xtext.common.types.JvmField -import org.eclipse.xtext.xbase.compiler.GeneratorConfig - -import static extension com.avaloq.tools.ddk.check.generator.CheckGeneratorExtensions.* -import static extension com.avaloq.tools.ddk.check.generator.CheckGeneratorNaming.* -import com.avaloq.tools.ddk.check.compiler.ICheckGeneratorConfigProvider - -class CheckGenerator extends JvmModelGenerator { - - @Inject extension CheckGeneratorExtensions generatorExtensions - @Inject extension CheckGeneratorNaming - @Inject CheckCompiler compiler - @Inject ICheckGeneratorConfigProvider generatorConfigProvider; - - override void doGenerate(Resource resource, IFileSystemAccess fsa) { - super.doGenerate(resource, fsa); // Generate validator, catalog, and preference initializer from inferred Jvm models. - val config = generatorConfigProvider.get(resource?.URI); - for (catalog : toIterable(resource.allContents).filter(typeof(CheckCatalog))) { - - fsa.generateFile(catalog.issueCodesFilePath, catalog.compileIssueCodes) - fsa.generateFile(catalog.standaloneSetupPath, catalog.compileStandaloneSetup) - - // change output path for service registry - fsa.generateFile( - CheckUtil::serviceRegistryClassName, - CheckGeneratorConstants::CHECK_REGISTRY_OUTPUT, - catalog.generateServiceRegistry(CheckUtil::serviceRegistryClassName, fsa) - ) - // generate documentation for SCA-checks only - if(config !== null && (config.doGenerateDocumentationForAllChecks || !config.generateLanguageInternalChecks)){ - // change output path for html files to docs/ - fsa.generateFile(catalog.docFileName, CheckGeneratorConstants::CHECK_DOC_OUTPUT, catalog.compileDoc) - } - } - } - - /* Documentation compiler, generates HTML output. */ - def compileDoc (CheckCatalog catalog)''' - «val body = bodyDoc(catalog)» - - - - - - «catalog.name» - - - -

Check Catalog «catalog.name»

- «val formattedDescription = catalog.description.formatDescription» - «IF formattedDescription !== null» -

«formattedDescription»

- «ENDIF» - «body» - - - - ''' - - def bodyDoc(CheckCatalog catalog)''' - «FOR check:catalog.checks» -

«check.label» («check.defaultSeverity.name().toLowerCase»)

- «val formattedCheckDescription = check.description.formatDescription» - «IF formattedCheckDescription !== null» - «formattedCheckDescription» - «ENDIF» -

Message: «check.message.replacePlaceholder»


- «ENDFOR» - «FOR category:catalog.categories» -
-

«category.label»

- «val formattedCateogryDescription = category.description.formatDescription» - «IF formattedCateogryDescription !== null» - «formattedCateogryDescription» - «ENDIF» - «FOR check:category.checks» -
-

«check.label» («check.defaultSeverity.name().toLowerCase»)

- «val formattedCheckDescription = check.description.formatDescription» - «IF formattedCheckDescription !== null» - «formattedCheckDescription» - «ENDIF» -

Message: «check.message.replacePlaceholder»

-
- «ENDFOR» -
- «ENDFOR» - ''' - - /* - * Creates an IssueCodes file for a Check Catalog. Every Check Catalog will have its own file - * of issue codes. - */ - def compileIssueCodes(CheckCatalog catalog) { - val allIssues = catalog.checkAndImplementationIssues // all Issue instances - val allIssueNames = allIssues.toMap([issue|issue.issueCode()], [issue|issue.issueName()]) // *all* issue names, unordered - - ''' - «IF !(catalog.packageName.isNullOrEmpty)» - package «catalog.packageName»; - «ENDIF» - - /** - * Issue codes which may be used to address validation issues (for instance in quickfixes). - */ - @SuppressWarnings("all") - public final class «catalog.issueCodesClassName» { - - «FOR issueCode:allIssueNames.keySet.sort» - public static final String «issueCode» = "«issueCodeValue(catalog, allIssueNames.get(issueCode))»"; - «ENDFOR» - - private «catalog.issueCodesClassName»() { - // Prevent instantiation. - } - } - ''' - } - - /* - * Generates the Java standalone setup class which will be called by the ServiceRegistry. - */ - def compileStandaloneSetup(CheckCatalog catalog) { - ''' - «IF !(catalog.packageName.isNullOrEmpty)» - package «catalog.packageName»; - «ENDIF» - - import org.apache.logging.log4j.Logger; - import org.apache.logging.log4j.LogManager; - - import com.avaloq.tools.ddk.check.runtime.configuration.ModelLocation; - import com.avaloq.tools.ddk.check.runtime.registry.ICheckCatalogRegistry; - import com.avaloq.tools.ddk.check.runtime.registry.ICheckValidatorRegistry; - import com.avaloq.tools.ddk.check.runtime.registry.ICheckValidatorStandaloneSetup; - - /** - * Standalone setup for «catalog.name» as required by the standalone builder. - */ - @SuppressWarnings("nls") - public class «catalog.standaloneSetupClassName» implements ICheckValidatorStandaloneSetup { - - private static final Logger LOG = LogManager.getLogger(«catalog.standaloneSetupClassName».class); - «IF catalog.grammar !== null» - private static final String GRAMMAR_NAME = "«catalog.grammar.name»"; - «ENDIF» - private static final String CATALOG_FILE_PATH = "«catalog.checkFilePath»"; - - @Override - public void doSetup() { - ICheckValidatorRegistry.INSTANCE.registerValidator(«IF catalog.grammar !== null»GRAMMAR_NAME,«ENDIF» new «catalog.validatorClassName»()); - ICheckCatalogRegistry.INSTANCE.registerCatalog(«IF catalog.grammar !== null»GRAMMAR_NAME,«ENDIF» new ModelLocation( - «catalog.standaloneSetupClassName».class.getClassLoader().getResource(CATALOG_FILE_PATH), CATALOG_FILE_PATH)); - LOG.info("Standalone setup done for «catalog.checkFilePath»"); - } - - @Override - public String toString() { - return "CheckValidatorSetup(«catalog.eResource.URI.path»)"; - } - } - ''' - } - - /* - * Writes contents of the service registry file containing fully qualified class names of all validators. - * See also http://docs.oracle.com/javase/1.4.2/docs/api/javax/imageio/spi/ServiceRegistry.html - */ - def generateServiceRegistry(CheckCatalog catalog, String serviceRegistryFileName, IFileSystemAccess fsa) { - val config = (fsa as AbstractFileSystemAccess).outputConfigurations.get(CheckGeneratorConstants::CHECK_REGISTRY_OUTPUT) - val path = config.outputDirectory + "/" + serviceRegistryFileName - val contents = catalog.getContents(path) - contents.add(catalog.qualifiedStandaloneSetupClassName) - ''' - «FOR c:contents» - «c» - «ENDFOR» - ''' - } - - override ITreeAppendable _generateMember(JvmField field, ITreeAppendable appendable, GeneratorConfig config) { - // Suppress generation of the "artificial" fields for FormalParameters in check impls, but not elsewhere. - if (field.final && !field.static) { // A bit hacky to use this as the distinction... - val FormalParameter parameter = compiler.getFormalParameter(field); - if (parameter !== null) { - return appendable; - } - } - return super._generateMember(field, appendable, config); - } -} - diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorExtensions.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorExtensions.java new file mode 100644 index 0000000000..fc56a38310 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorExtensions.java @@ -0,0 +1,512 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.generator; + +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.Context; +import com.avaloq.tools.ddk.check.check.Implementation; +import com.avaloq.tools.ddk.check.check.TriggerKind; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.avaloq.tools.ddk.check.util.CheckUtil; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.common.io.CharStreams; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Path; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.jdt.internal.ui.text.javadoc.JavaDoc2HTMLTextReader; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.validation.CheckType; +import org.eclipse.xtext.xbase.lib.ListExtensions; + +import static com.avaloq.tools.ddk.check.generator.CheckGeneratorNaming.issueCodesClassName; +import static com.avaloq.tools.ddk.check.generator.CheckGeneratorNaming.parent; + +@SuppressWarnings({"checkstyle:MethodName", "nls"}) +public class CheckGeneratorExtensions { + + /** + * Returns the qualified Java name for an issue code. + * + * @param issue + * the issue expression + * @return the qualified issue code name, or {@code null} if the issue code is null + */ + protected String _qualifiedIssueCodeName(final XIssueExpression issue) { + String result = issueCode(issue); + if (result == null) { + return null; + } else { + return issueCodesClassName(parent(issue, CheckCatalog.class)) + "." + result; + } + } + + /** + * Returns the qualified Java name for an issue code. + * + * @param context + * the context + * @return the qualified issue code name + */ + protected String _qualifiedIssueCodeName(final Context context) { + return issueCodesClassName(parent(context, CheckCatalog.class)) + "." + issueCode(context); + } + + /** + * Gets the simple issue code name for a check. + * + * @param check + * the check + * @return the issue code string + */ + protected static String _issueCode(final Check check) { + if (null != check.getName()) { + return splitCamelCase(check.getName()).toUpperCase(); + } else { + return "ERROR_ISSUE_CODE_NAME_CHECK"; // should only happen if the ID is missing, which will fail a validation + } + } + + /** + * Gets the simple issue code name for an issue expression. + * + * @param issue + * the issue expression + * @return the issue code string + */ + protected static String _issueCode(final XIssueExpression issue) { + if (issue.getIssueCode() != null) { + return splitCamelCase(issue.getIssueCode()).toUpperCase(); + } else if (issue.getCheck() != null && !issue.getCheck().eIsProxy()) { + return issueCode(issue.getCheck()); + } else if (parent(issue, Check.class) != null) { + return issueCode(parent(issue, Check.class)); + } else { + return "ERROR_ISSUE_CODE_NAME_XISSUEEXPRESSION"; // should not happen + } + } + + /** + * Gets the simple issue code name for a check. + * + * @param check + * the check + * @return the issue name string + */ + protected static String _issueName(final Check check) { + if (null != check.getName()) { + return check.getName(); + } else { + return "ErrorIssueCodeNameCheck"; // should only happen if the ID is missing, which will fail a validation + } + } + + /** + * Gets the simple issue code name for an issue expression. + * + * @param issue + * the issue expression + * @return the issue name string + */ + protected static String _issueName(final XIssueExpression issue) { + if (issue.getIssueCode() != null) { + return issue.getIssueCode(); + } else if (issue.getCheck() != null && !issue.getCheck().eIsProxy()) { + return issueName(issue.getCheck()); + } else if (parent(issue, Check.class) != null) { + return issueName(parent(issue, Check.class)); + } else { + return "ErrorIssueCodeName_XIssueExpresion"; // should not happen + } + } + + /** + * Returns the issue code prefix for a catalog. + * + * @param catalog + * the check catalog + * @return the issue code prefix + */ + public static String issueCodePrefix(final CheckCatalog catalog) { + return catalog.getPackageName() + "." + issueCodesClassName(catalog) + "."; + } + + /** + * Returns the value of an issue code. + * + * @param object + * the EObject context + * @param issueName + * the issue name + * @return the issue code value + */ + public static String issueCodeValue(final EObject object, final String issueName) { + CheckCatalog catalog = parent(object, CheckCatalog.class); + return issueCodePrefix(catalog) + CheckUtil.toIssueCodeName(splitCamelCase(issueName)); + } + + /** + * Gets the issue label for a Check. + * + * @param check + * the check + * @return the label + */ + protected String _issueLabel(final Check check) { + return check.getLabel(); + } + + /** + * Gets the issue label for an issue expression. + * + * @param issue + * the issue expression + * @return the label + */ + protected String _issueLabel(final XIssueExpression issue) { + if (issue.getCheck() != null && !issue.getCheck().eIsProxy()) { + return issueLabel(issue.getCheck()); + } else if (parent(issue, Check.class) != null) { + return issueLabel(parent(issue, Check.class)); + } else { + return "ERROR_ISSUE_LABEL_XISSUEEXPRESSION"; // should not happen + } + } + + /* Converts a string such as "AbcDef" to "ABC_DEF". */ + public static String splitCamelCase(final String string) { + return string.replaceAll( + String.format( + "%s|%s|%s", + "(?<=[A-Z])(?=[A-Z][a-z])", + "(?<=[^A-Z_])(?=[A-Z])", + "(?<=[A-Za-z])(?=[^A-Za-z_])" + ), + "_" + ); + } + + /** + * Returns the CheckType for a check. + * + * @param check + * the check + * @return the check type + */ + public CheckType checkType(final Check check) { + /* TODO handle the case of independent check implementations + * An Implementation is not a Check and has no kind, + * but it may execute checks of various types. + * As it is we treat them all as FAST regardless of declared kind. + */ + TriggerKind kind = check != null ? check.getKind() : null; + if (kind == null) { + kind = TriggerKind.FAST; + } + + return switch (kind) { + case EXPENSIVE -> CheckType.EXPENSIVE; + case NORMAL -> CheckType.NORMAL; + case FAST -> CheckType.FAST; + }; + } + + /** + * Returns a default CheckType for a non-Check context. + * + * @param context + * the context + * @return the check type + */ + public CheckType checkType(final Context context) { + EObject container = context.eContainer(); + Check check = (container instanceof Check) ? (Check) container : null; + return checkType(check); + } + + /** + * Returns the qualified CheckType name for a context. + * + * @param context + * the context + * @return the qualified check type name + */ + public String checkTypeQName(final Context context) { + return "CheckType." + checkType(context); + } + + /** + * Returns all issue expressions contained in an EObject. + * + * @param object + * the object to search + * @return the issue expressions + */ + public Iterable issues(final EObject object) { + return Iterables.filter(EcoreUtil2.eAllContents(object), XIssueExpression.class); + } + + /** + * Returns all issue expressions for all checks in a catalog. + * + * @param catalog + * the check catalog + * @return the issue expressions + */ + public Iterable issues(final CheckCatalog catalog) { + return Iterables.concat(ListExtensions.map(catalog.getAllChecks(), check -> issues(check))); + } + + /** + * Returns all issue expressions for an implementation. + * + * @param implementation + * the implementation + * @return the issue expressions + */ + public Iterable issues(final Implementation implementation) { + return issues(implementation.getContext()); + } + + /** + * Returns all Check and Implementation Issues for a CheckCatalog. Issues are not necessarily unique. + * + * @param catalog + * the check catalog + * @return all issue expressions + */ + public Iterable checkAndImplementationIssues(final CheckCatalog catalog) { + Iterable checkIssues = issues(catalog); // Issues for all Checks + Iterable implIssues = Iterables.concat(ListExtensions.map(catalog.getImplementations(), impl -> issues(impl))); // Issues for all Implementations + return Iterables.concat(checkIssues, implIssues); // all Issue instances + } + + /** + * Returns the check associated with an issue expression. + * + * @param expression + * the issue expression + * @return the associated check, or {@code null} + */ + public Check issuedCheck(final XIssueExpression expression) { + if (expression.getCheck() != null) { + return expression.getCheck(); + } else { + Check containerCheck = EcoreUtil2.getContainerOfType(expression, Check.class); + if (containerCheck != null) { + return containerCheck; + //TODO we obviously need a validation in the language so that there is always a value here! + } + return null; + } + } + + /** + * Gets the IFile which is associated with given object's eResource, or null if none + * could be determined. + * + * @param object + * the EObject + * @return the associated file, or {@code null} + */ + public IFile fileForObject(final EObject object) { + Resource res = object.eResource(); + if (res.getURI().isPlatform()) { + return (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(res.getURI().toPlatformString(true)); + } + return null; + } + + /** + * Gets the IProject which is associated with a given EObject or null + * if none could be determined. + * + * @param object + * the EObject + * @return the associated project, or {@code null} + */ + public IProject projectForObject(final EObject object) { + IFile file = object != null ? fileForObject(object) : null; + return file != null ? file.getProject() : null; + } + + /** + * Gets the name of the project in which given object is contained. + * + * @param object + * the EObject + * @return the bundle name, or {@code null} + */ + public String bundleName(final EObject object) { + IProject proj = projectForObject(object); + if (proj != null) { + return proj.getName(); + } + return null; + } + + /** + * Replace binding placeholders of a message with "...". + * + * @param message + * the message + * @return the message with placeholders replaced + */ + public String replacePlaceholder(final String message) { + Pattern p = Pattern.compile("\\{[0-9]+\\}"); + Matcher m = p.matcher(message); + return m.replaceAll("..."); + } + + /** + * Format the Check description for Eclipse Help. + * + * @param comment + * the comment to format + * @return the formatted HTML, or {@code null} + */ + // CHECKSTYLE:CHECK-OFF IllegalCatch + public String formatDescription(final String comment) { + if (comment == null) { + return null; + } + try { + JavaDoc2HTMLTextReader reader = new JavaDoc2HTMLTextReader(new StringReader(comment)); + return reader.getString(); + } catch (Exception e) { + return null; + } + } + // CHECKSTYLE:CHECK-ON IllegalCatch + + /** + * Gets the contents of a file in the project. + * + * @param catalog + * the check catalog + * @param path + * the file path + * @return the set of lines + * @throws IllegalStateException + * if the file cannot be read + */ + public Set getContents(final CheckCatalog catalog, final String path) { + IProject project = projectForObject(catalog); + if (project != null) { // In some compiler tests we may not have a project. + IFile file = project.getFile(new Path(path)); + if (file.exists()) { + // CHECKSTYLE:CHECK-OFF IllegalCatch + try (InputStreamReader reader = new InputStreamReader(file.getContents(), StandardCharsets.UTF_8)) { + List content = CharStreams.readLines(reader); + return Sets.newTreeSet(content); + } catch (Exception e) { + throw new IllegalStateException(e); + } + // CHECKSTYLE:CHECK-ON IllegalCatch + } + } + return new LinkedHashSet<>(); + } + + /** + * Returns the qualified issue code name for an EObject. + * + * @param context + * the EObject context + * @return the qualified issue code name + * @throws IllegalArgumentException + * if the parameter type is not handled + */ + public String qualifiedIssueCodeName(final EObject context) { + if (context instanceof Context) { + return _qualifiedIssueCodeName((Context) context); + } else if (context instanceof XIssueExpression) { + return _qualifiedIssueCodeName((XIssueExpression) context); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(context).toString()); + } + } + + /** + * Returns the issue code for an EObject. + * + * @param check + * the EObject (Check or XIssueExpression) + * @return the issue code string + * @throws IllegalArgumentException + * if the parameter type is not handled + */ + public static String issueCode(final EObject check) { + if (check instanceof Check) { + return _issueCode((Check) check); + } else if (check instanceof XIssueExpression) { + return _issueCode((XIssueExpression) check); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(check).toString()); + } + } + + /** + * Returns the issue name for an EObject. + * + * @param check + * the EObject (Check or XIssueExpression) + * @return the issue name string + * @throws IllegalArgumentException + * if the parameter type is not handled + */ + public static String issueName(final EObject check) { + if (check instanceof Check) { + return _issueName((Check) check); + } else if (check instanceof XIssueExpression) { + return _issueName((XIssueExpression) check); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(check).toString()); + } + } + + /** + * Returns the issue label for an EObject. + * + * @param check + * the EObject (Check or XIssueExpression) + * @return the label string + * @throws IllegalArgumentException + * if the parameter type is not handled + */ + public String issueLabel(final EObject check) { + if (check instanceof Check) { + return _issueLabel((Check) check); + } else if (check instanceof XIssueExpression) { + return _issueLabel((XIssueExpression) check); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(check).toString()); + } + } +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorExtensions.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorExtensions.xtend deleted file mode 100644 index 432b588da1..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorExtensions.xtend +++ /dev/null @@ -1,267 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.generator - -import com.avaloq.tools.ddk.check.check.Check -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.Context -import com.avaloq.tools.ddk.check.check.Implementation -import com.avaloq.tools.ddk.check.check.TriggerKind -import com.avaloq.tools.ddk.check.check.XIssueExpression -import com.google.common.collect.Iterables -import com.google.common.collect.Sets -import com.google.common.io.CharStreams -import java.io.InputStreamReader -import java.io.StringReader -import java.util.Set -import java.util.regex.Pattern -import org.eclipse.core.resources.IFile -import org.eclipse.core.resources.IProject -import org.eclipse.core.resources.ResourcesPlugin -import org.eclipse.core.runtime.Path -import org.eclipse.emf.ecore.EObject -import org.eclipse.jdt.internal.ui.text.javadoc.JavaDoc2HTMLTextReader -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.validation.CheckType - -import static extension com.avaloq.tools.ddk.check.generator.CheckGeneratorNaming.* -import static extension com.avaloq.tools.ddk.check.util.CheckUtil.* - -class CheckGeneratorExtensions { - - def dispatch String qualifiedIssueCodeName(XIssueExpression issue) { - val result = issue.issueCode() - if (result === null) { - null - } else { - issue.parent(typeof(CheckCatalog)).issueCodesClassName + '.' + result - } - } - - /* Returns the qualified Java name for an issue code. */ - def dispatch String qualifiedIssueCodeName(Context context) { - context.parent(typeof(CheckCatalog)).issueCodesClassName + '.' + context.issueCode - } - - /* Gets the simple issue code name for a check. */ - def static dispatch String issueCode(Check check) { - if (null !== check.name) { - check.name.splitCamelCase.toUpperCase - } else { - "ERROR_ISSUE_CODE_NAME_CHECK" // should only happen if the ID is missing, which will fail a validation - } - } - - /* Gets the simple issue code name for an issue expression. */ - def static dispatch String issueCode(XIssueExpression issue) { - if (issue.issueCode !== null) { - issue.issueCode.splitCamelCase.toUpperCase - } else if (issue.check !== null && !issue.check.eIsProxy) { - issueCode(issue.check) - } else if (issue.parent(Check) !== null) { - issueCode(issue.parent(Check)) - } else { - "ERROR_ISSUE_CODE_NAME_XISSUEEXPRESSION" // should not happen - } - } - - /* Gets the simple issue code name for a check. */ - def static dispatch String issueName(Check check) { - if (null !== check.name) { - check.name - } else { - "ErrorIssueCodeNameCheck" // should only happen if the ID is missing, which will fail a validation - } - } - - /* Gets the simple issue code name for an issue expression. */ - def static dispatch String issueName(XIssueExpression issue) { - if (issue.issueCode !== null) { - issue.issueCode - } else if (issue.check !== null && !issue.check.eIsProxy) { - issueName(issue.check) - } else if (issue.parent(Check) !== null) { - issueName(issue.parent(Check)) - } else { - "ErrorIssueCodeName_XIssueExpresion" // should not happen - } - } - - def static issueCodePrefix(CheckCatalog catalog) { - catalog.packageName + "." + catalog.issueCodesClassName + "." - } - - /* Returns the value of an issue code. */ - def static issueCodeValue(EObject object, String issueName) { - val catalog = object.parent(typeof(CheckCatalog)) - catalog.issueCodePrefix + issueName.splitCamelCase.toIssueCodeName - } - - /* Gets the issue label for a Check. */ - def dispatch String issueLabel(Check check) { - check.label - } - - /* Gets the issue label for an issue expression. */ - def dispatch String issueLabel(XIssueExpression issue) { - if (issue.check !== null && !issue.check.eIsProxy) { - issueLabel(issue.check) - } else if (issue.parent(Check) !== null) { - issueLabel(issue.parent(Check)) - } else { - "ERROR_ISSUE_LABEL_XISSUEEXPRESSION" // should not happen - } - } - - /* Converts a string such as "AbcDef" to "ABC_DEF". */ - def static String splitCamelCase(String string) { - string.replaceAll( - String::format( - "%s|%s|%s", - "(?<=[A-Z])(?=[A-Z][a-z])", - "(?<=[^A-Z_])(?=[A-Z])", - "(?<=[A-Za-z])(?=[^A-Za-z_])" - ), - "_" - ) - } - - def CheckType checkType(Check check) { - /* TODO handle the case of independent check implementations - * An Implementation is not a Check and has no kind, - * but it may execute checks of various types. - * As it is we treat them all as FAST regardless of declared kind. - */ - val TriggerKind kind = check?.kind ?: TriggerKind::FAST; - - return switch (kind) { - case TriggerKind::EXPENSIVE: CheckType::EXPENSIVE - case TriggerKind::NORMAL: CheckType::NORMAL - case TriggerKind::FAST: CheckType::FAST - }; - } - - /* Returns a default CheckType for a non-Check context. */ - def CheckType checkType(Context context) { - val container = context.eContainer(); - val Check check = if (container instanceof Check) container else null; - return checkType(check); - } - - def String checkTypeQName(Context context) { - return "CheckType." + checkType(context); - } - - def issues(EObject object) { - EcoreUtil2::eAllContents(object).filter(typeof(XIssueExpression)) - } - - def issues(CheckCatalog catalog) { - catalog.allChecks.map(check|check.issues).flatten - } - - def issues(Implementation implementation) { - implementation.context.issues - } - - /* Returns all Check and Implementation Issues for a CheckCatalog. Issues are not necessarily unique. */ - def checkAndImplementationIssues(CheckCatalog catalog) { - val checkIssues = catalog.issues // Issues for all Checks - val implIssues = catalog.implementations.map(impl|impl.issues).flatten // Issues for all Implementations - return Iterables::concat(checkIssues, implIssues) // all Issue instances - } - - def issuedCheck(XIssueExpression expression) { - if (expression.check !== null) { - expression.check - } else { - val containerCheck = EcoreUtil2::getContainerOfType(expression, typeof(Check)) - if (containerCheck !== null) { - containerCheck - //TODO we obviously need a validation in the language so that there is always a value here! - } - } - } - - /** - * Gets the IFile which is associated with given object's eResource, or null if none - * could be determined. - */ - def IFile fileForObject(EObject object) { - val res = object.eResource - if (res.URI.platform) { - return ResourcesPlugin::workspace.root.findMember(res.URI.toPlatformString(true)) as IFile - } - return null - } - - /** - * Gets the IProject which is associated with a given EObject or null - * if none could be determined. - */ - def IProject projectForObject(EObject object) { - return object?.fileForObject?.project - } - - /** - * Gets the name of the project in which given object is contained. - */ - def String bundleName(EObject object) { - val proj = object.projectForObject - if (proj !== null) { - return proj.name - } - return null - } - - /* - * Replace binding placeholders of a message with "...". - */ - def String replacePlaceholder(String message) { - val p = Pattern::compile("\\{[0-9]+\\}") - val m = p.matcher(message) - m.replaceAll("...") - } - - /* - * Format the Check description for Eclipse Help - */ - def String formatDescription(String comment) { - if (comment === null) { - return null - } - try { - val reader = new JavaDoc2HTMLTextReader(new StringReader(comment)) - return reader.string - } catch (Exception e) { - return null - } - } - - def Set getContents(CheckCatalog catalog, String path) { - val project = catalog.projectForObject - if (project !== null) { // In some compiler tests we may not have a project. - val file = project.getFile(new Path(path)) - if (file.exists) { - val reader = new InputStreamReader(file.getContents()) - try { - val content = CharStreams::readLines(reader) - return Sets.newTreeSet(content) - } finally { - reader.close - } - } - } - newLinkedHashSet() - } - -} - diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorNaming.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorNaming.java new file mode 100644 index 0000000000..fa358d02c2 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorNaming.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.generator; + +import com.avaloq.tools.ddk.check.check.Category; +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.FormalParameter; +import com.google.inject.Inject; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.naming.IQualifiedNameProvider; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.xbase.lib.StringExtensions; + +import static com.avaloq.tools.ddk.check.runtime.CheckRuntimeConstants.ISSUE_CODES_CLASS_NAME_SUFFIX; + +@SuppressWarnings("nls") +public class CheckGeneratorNaming { + + @Inject + private IQualifiedNameProvider nameProvider; + + public static T parent(final EObject object, final Class c) { + return EcoreUtil2.getContainerOfType(object, c); + } + + // creates a pathName out of a qualified javaPackagename + public String asPath(final String javaPackageName) { + if (javaPackageName != null) { + return javaPackageName.replace('.', '/') + "/"; + } else { + return ""; + } + } + + /* Gets the class name of the check validator. */ + public String validatorClassName(final CheckCatalog c) { + return c.getName() + "CheckImpl"; + } + + /* Gets the fully qualified class name of the check validator. */ + public String qualifiedValidatorClassName(final CheckCatalog c) { + return c.getPackageName() + "." + validatorClassName(c); + } + + /* Gets the file path of the check validator. */ + public String validatorFilePath(final CheckCatalog c) { + return asPath(c.getPackageName()) + validatorClassName(c) + ".java"; + } + + /* Gets the check catalog class name. */ + public String catalogClassName(final CheckCatalog c) { + return c.getName() + "CheckCatalog"; + } + + /* Gets the qualified check catalog class name. */ + public String qualifiedCatalogClassName(final CheckCatalog c) { + return c.getPackageName() + "." + catalogClassName(c); + } + + /* Gets the preference initializer class name. */ + public String preferenceInitializerClassName(final CheckCatalog c) { + return c.getName() + "PreferenceInitializer"; + } + + /* Gets the qualified standalone setup class name. */ + public String qualifiedStandaloneSetupClassName(final CheckCatalog c) { + return c.getPackageName() + "." + standaloneSetupClassName(c); + } + + /* Gets the standalone setup class name. */ + public String standaloneSetupClassName(final CheckCatalog c) { + return c.getName() + "StandaloneSetup"; + } + + /* Gets the qualified preference initializer class name. */ + public String qualifiedPreferenceInitializerClassName(final CheckCatalog c) { + return c.getPackageName() + "." + preferenceInitializerClassName(c); + } + + /* Gets the standalone setup class file path. */ + public String standaloneSetupPath(final CheckCatalog c) { + return asPath(c.getPackageName()) + standaloneSetupClassName(c) + ".java"; + } + + /* Gets the documentation file name. */ + public String docFileName(final CheckCatalog c) { + return c.getName() + ".html"; + } + + /* Gets the issue codes class name. */ + public static String issueCodesClassName(final CheckCatalog c) { + return c.getName() + ISSUE_CODES_CLASS_NAME_SUFFIX; + } + + /* Gets the issue codes file path. */ + public String issueCodesFilePath(final CheckCatalog c) { + return asPath(c.getPackageName()) + issueCodesClassName(c) + ".java"; + } + + /* Gets the quickfix provider class name. */ + public String quickfixClassName(final CheckCatalog c) { + return c.getName() + "QuickfixProvider"; + } + + /* Gets the qualified quickfix provider class name. */ + public String qualifiedQuickfixClassName(final CheckCatalog c) { + return c.getPackageName() + "." + quickfixClassName(c); + } + + /* Gets the quickfix provider file path. */ + public String quickfixFilePath(final CheckCatalog c) { + return asPath(c.getPackageName()) + quickfixClassName(c) + ".java"; + } + + /* Gets the full path to the check file, e.g. com/avaloq/MyChecks.check. */ + public String checkFilePath(final CheckCatalog c) { + return asPath(c.getPackageName()) + c.getName() + ".check"; + } + + /* Gets the name of the getter method generated for a formal parameter. */ + public String formalParameterGetterName(final FormalParameter p) { + Check check = (Check) p.eContainer(); + return "get" + + StringExtensions.toFirstUpper(check.getName()) + + "_" + + StringExtensions.toFirstUpper(p.getName()); + } + + /* Gets the name of the getter method generated for a field. */ + public String fieldGetterName(final String fieldName) { + return "get" + StringExtensions.toFirstUpper(fieldName); + } + + /* Check catalog instance name in the validator */ + public String catalogInstanceName(final EObject object) { + return StringExtensions.toFirstLower(EcoreUtil2.getContainerOfType(object, CheckCatalog.class).getName()) + "Catalog"; + } + + /* Check issue code to label map field name in the catalog */ + public String issueCodeToLabelMapFieldName() { + return "issueCodeToLabelMap"; + } + + /* Gets the name of the default validator class. */ + public String defaultValidatorClassName() { + return "DispatchingCheckImpl"; + } + + /* Gets the fully qualified name of the default validator class. */ + public String qualifiedDefaultValidatorClassName() { + return "com.avaloq.tools.ddk.check.runtime.issue." + defaultValidatorClassName(); + } + + /* Gets the prefix for the context id (used in contexts.xml) */ + public String getContextIdPrefix(final QualifiedName catalog) { + return catalog.getLastSegment().toLowerCase() + "_"; // TODO make context id use fully qualified catalog names + } + + /* Gets the full context id (used in contexts.xml) */ + public String getContextId(final Check check) { + CheckCatalog catalog = parent(check, CheckCatalog.class); + return getContextIdPrefix(nameProvider.apply(catalog)) + check.getLabel().replaceAll(" ", "").replaceAll("\"", "").replaceAll("'", "").toLowerCase(); + } + + /* Gets the full context id (used in contexts.xml) */ + public String getContextId(final Category category) { + CheckCatalog catalog = parent(category, CheckCatalog.class); + return getContextIdPrefix(nameProvider.apply(catalog)) + category.getLabel().replaceAll(" ", "").replaceAll("\"", "").replaceAll("'", "").toLowerCase(); + } +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorNaming.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorNaming.xtend deleted file mode 100644 index 1a0c493c05..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/generator/CheckGeneratorNaming.xtend +++ /dev/null @@ -1,174 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.generator - -import com.avaloq.tools.ddk.check.check.Category -import com.avaloq.tools.ddk.check.check.Check -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.FormalParameter -import com.google.inject.Inject -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.naming.IQualifiedNameProvider -import org.eclipse.xtext.naming.QualifiedName - -import static com.avaloq.tools.ddk.check.runtime.CheckRuntimeConstants.ISSUE_CODES_CLASS_NAME_SUFFIX - -class CheckGeneratorNaming { - - @Inject IQualifiedNameProvider nameProvider - - def static T parent(EObject object, Class c) { - EcoreUtil2::getContainerOfType(object, c) - } - - // creates a pathName out of a qualified javaPackagename - def String asPath(String javaPackageName) { - if (javaPackageName !== null) javaPackageName.replace('.', '/') + "/" else "" - } - - /* Gets the class name of the check validator. */ - def String validatorClassName(CheckCatalog c) { - c.name + "CheckImpl" - } - - /* Gets the fully qualified class name of the check validator. */ - def String qualifiedValidatorClassName(CheckCatalog c) { - c.packageName + '.' + c.validatorClassName - } - - /* Gets the file path of the check validator. */ - def String validatorFilePath(CheckCatalog c) { - c.packageName.asPath + c.validatorClassName + ".java" - } - - /* Gets the check catalog class name. */ - def String catalogClassName(CheckCatalog c) { - c.name + "CheckCatalog" - } - - /* Gets the qualified check catalog class name. */ - def String qualifiedCatalogClassName(CheckCatalog c) { - c.packageName + '.' + c.catalogClassName - } - - /* Gets the preference initializer class name. */ - def String preferenceInitializerClassName(CheckCatalog c) { - c.name + "PreferenceInitializer" - } - - /* Gets the qualified standalone setup class name. */ - def String qualifiedStandaloneSetupClassName(CheckCatalog c) { - c.packageName + '.' + c.standaloneSetupClassName - } - - /* Gets the standalone setup class name. */ - def String standaloneSetupClassName(CheckCatalog c) { - c.name + "StandaloneSetup" - } - - /* Gets the qualified preference initializer class name. */ - def String qualifiedPreferenceInitializerClassName(CheckCatalog c) { - c.packageName + '.' + c.preferenceInitializerClassName - } - - /* Gets the standalone setup class file path. */ - def String standaloneSetupPath(CheckCatalog c) { - c.packageName.asPath + c.standaloneSetupClassName + ".java" - } - - /* Gets the documentation file name. */ - def String docFileName(CheckCatalog c){ - c.name + ".html" - } - - /* Gets the issue codes class name. */ - def static String issueCodesClassName(CheckCatalog c) { - c.name + ISSUE_CODES_CLASS_NAME_SUFFIX - } - - /* Gets the issue codes file path. */ - def String issueCodesFilePath(CheckCatalog c) { - c.packageName.asPath + c.issueCodesClassName + ".java" - } - - /* Gets the quickfix provider class name. */ - def String quickfixClassName(CheckCatalog c) { - c.name + "QuickfixProvider" - } - - /* Gets the qualified quickfix provider class name. */ - def String qualifiedQuickfixClassName(CheckCatalog c) { - c.packageName + '.' + c.quickfixClassName - } - - /* Gets the quickfix provider file path. */ - def String quickfixFilePath(CheckCatalog c) { - c.packageName.asPath + c.quickfixClassName + ".java" - } - - /* Gets the full path to the check file, e.g. com/avaloq/MyChecks.check. */ - def String checkFilePath(CheckCatalog c) { - c.packageName.asPath + c.name + ".check" - } - - /* Gets the name of the getter method generated for a formal parameter. */ - def String formalParameterGetterName(FormalParameter p) { - val check = p.eContainer as Check - return "get" - + check.name.toFirstUpper - + "_" - + p.name.toFirstUpper - } - - /* Gets the name of the getter method generated for a field. */ - def String fieldGetterName(String fieldName) { - "get" + fieldName.toFirstUpper - } - - /* Check catalog instance name in the validator */ - def String catalogInstanceName(EObject object) { - EcoreUtil2::getContainerOfType(object, typeof(CheckCatalog)).name.toFirstLower + "Catalog" - } - - /* Check issue code to label map field name in the catalog */ - def String issueCodeToLabelMapFieldName() { - "issueCodeToLabelMap" - } - - /* Gets the name of the default validator class. */ - def String defaultValidatorClassName() { - "DispatchingCheckImpl" - } - - /* Gets the fully qualified name of the default validator class. */ - def String qualifiedDefaultValidatorClassName() { - "com.avaloq.tools.ddk.check.runtime.issue." + defaultValidatorClassName - } - - /* Gets the prefix for the context id (used in contexts.xml) */ - def getContextIdPrefix(QualifiedName catalog){ - catalog.lastSegment.toString.toLowerCase + "_" // TODO make context id use fully qualified catalog names - } - - /* Gets the full context id (used in contexts.xml) */ - def String getContextId(Check check) { - val catalog = check.parent(typeof (CheckCatalog)) - nameProvider.apply(catalog).contextIdPrefix + check.label.replaceAll(" ", "").replaceAll("\"", "").replaceAll("'", "").toLowerCase - } - - /* Gets the full context id (used in contexts.xml) */ - def String getContextId(Category category) { - val catalog = category.parent(typeof (CheckCatalog)) - nameProvider.apply(catalog).contextIdPrefix + category.label.replaceAll(" ", "").replaceAll("\"", "").replaceAll("'", "").toLowerCase - } - -} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/jvmmodel/CheckJvmModelInferrer.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/jvmmodel/CheckJvmModelInferrer.java new file mode 100644 index 0000000000..e82545993d --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/jvmmodel/CheckJvmModelInferrer.java @@ -0,0 +1,770 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.jvmmodel; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +import org.apache.commons.text.StringEscapeUtils; +import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.common.types.JvmAnnotationReference; +import org.eclipse.xtext.common.types.JvmAnnotationType; +import org.eclipse.xtext.common.types.JvmDeclaredType; +import org.eclipse.xtext.common.types.JvmField; +import org.eclipse.xtext.common.types.JvmGenericType; +import org.eclipse.xtext.common.types.JvmMember; +import org.eclipse.xtext.common.types.JvmOperation; +import org.eclipse.xtext.common.types.JvmParameterizedTypeReference; +import org.eclipse.xtext.common.types.JvmTypeReference; +import org.eclipse.xtext.common.types.JvmVisibility; +import org.eclipse.xtext.common.types.TypesFactory; +import org.eclipse.xtext.diagnostics.Severity; +import org.eclipse.xtext.util.Strings; +import org.eclipse.xtext.validation.CheckMode; +import org.eclipse.xtext.validation.CheckType; +import org.eclipse.xtext.validation.EObjectDiagnosticImpl; +import org.eclipse.xtext.xbase.XFeatureCall; +import org.eclipse.xtext.xbase.XMemberFeatureCall; +import org.eclipse.xtext.xbase.XbaseFactory; +import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable; +import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer; +import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor; +import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ListExtensions; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; +import org.eclipse.xtext.xbase.lib.StringExtensions; + +import com.avaloq.tools.ddk.check.CheckConstants; +import com.avaloq.tools.ddk.check.check.Category; +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.Context; +import com.avaloq.tools.ddk.check.check.FormalParameter; +import com.avaloq.tools.ddk.check.check.Implementation; +import com.avaloq.tools.ddk.check.check.Member; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.avaloq.tools.ddk.check.generator.CheckGeneratorExtensions; +import com.avaloq.tools.ddk.check.generator.CheckGeneratorNaming; +import com.avaloq.tools.ddk.check.generator.CheckPropertiesGenerator; +import com.avaloq.tools.ddk.check.resource.CheckLocationInFileProvider; +import com.avaloq.tools.ddk.check.runtime.configuration.ICheckConfigurationStoreService; +import com.avaloq.tools.ddk.check.runtime.issue.AbstractIssue; +import com.avaloq.tools.ddk.check.runtime.issue.DispatchingCheckImpl; +import com.avaloq.tools.ddk.check.runtime.issue.DispatchingCheckImpl.DiagnosticCollector; +import com.avaloq.tools.ddk.check.runtime.issue.SeverityKind; +import com.avaloq.tools.ddk.check.validation.IssueCodes; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.inject.Inject; +import com.google.inject.Singleton; + + +/** + *

+ * Infers a JVM model from the source model. + *

+ *

+ * The JVM model should contain all elements that would appear in the Java code + * which is generated from the source model. Other models link against the JVM model rather than the source model. + *

+ */ +@SuppressWarnings({"checkstyle:MethodName", "nls"}) +public class CheckJvmModelInferrer extends AbstractModelInferrer { + + @Inject + private TypesFactory typesFactory; + + @Inject + private CheckLocationInFileProvider locationInFileProvider; + + @Inject + private CheckGeneratorExtensions checkGeneratorExtensions; + + @Inject + private CheckGeneratorNaming checkGeneratorNaming; + + @Inject + private JvmTypesBuilder jvmTypesBuilder; + + // CHECKSTYLE:CONSTANTS-OFF + // CHECKSTYLE:CHECK-OFF LambdaBodyLength + protected void _infer(final CheckCatalog catalog, final IJvmDeclaredTypeAcceptor acceptor, final boolean preIndexingPhase) { + // The xbase automatic scoping mechanism (typeRef()) cannot find secondary classes in the same resource. It can + // only find indexed resources (either in the JDT index or in the xtext index). However, we'll initialize the + // JVM validator class before the resource gets indexed, so the JVM catalog class cannot be found yet when we + // create the injection in the validator. Therefore, remember the class here directly, and set it directly + // in the validator, completely bypassing any scoping. + if (preIndexingPhase) { + return; + } + JvmGenericType catalogClass = jvmTypesBuilder.toClass(catalog, checkGeneratorNaming.qualifiedCatalogClassName(catalog)); + JvmTypeReference issueCodeToLabelMapTypeRef = _typeReferenceBuilder.typeRef(ImmutableMap.class, _typeReferenceBuilder.typeRef(String.class), _typeReferenceBuilder.typeRef(String.class)); + acceptor. accept(catalogClass, (final JvmGenericType it) -> { + JvmTypeReference parentType = checkedTypeRef(catalog, AbstractIssue.class); + if (parentType != null) { + it.getSuperTypes().add(parentType); + } + Iterables.addAll(it.getAnnotations(), createAnnotation(checkedTypeRef(catalog, Singleton.class), (final JvmAnnotationReference it1) -> { + })); + jvmTypesBuilder.setDocumentation(it, "Issues for " + catalog.getName() + "."); + Iterables.addAll(it.getMembers(), createInjectedField(catalog, "checkConfigurationStoreService", checkedTypeRef(catalog, ICheckConfigurationStoreService.class))); + + // Create map of issue code to label and associated getter + it.getMembers().add(jvmTypesBuilder.toField(catalog, checkGeneratorNaming.issueCodeToLabelMapFieldName(), issueCodeToLabelMapTypeRef, (final JvmField it1) -> { + it1.setStatic(true); + it1.setFinal(true); + // Get all issue codes and labels + Iterable issues = checkGeneratorExtensions.checkAndImplementationIssues(catalog); + // Use a TreeMap to eliminate duplicates, + // and also to sort by qualified issue code name so autogenerated files are more readable and less prone to spurious ordering changes. + // Do this when compiling the Check, to avoid discovering duplicates at runtime. + Map sortedUniqueQualifiedIssueCodeNamesAndLabels = new TreeMap(); + for (XIssueExpression issue : issues) { + String qualifiedIssueCodeName = checkGeneratorExtensions.qualifiedIssueCodeName(issue); + String issueLabel = StringEscapeUtils.escapeJava(checkGeneratorExtensions.issueLabel(issue)); + String existingIssueLabel = sortedUniqueQualifiedIssueCodeNamesAndLabels.putIfAbsent(qualifiedIssueCodeName, issueLabel); + if (null != existingIssueLabel && !Objects.equals(issueLabel, existingIssueLabel)) { + // This qualified issue code name is already in the map, with a different label. Fail the build. + throw new IllegalArgumentException("Multiple issues found with qualified issue code name: " + qualifiedIssueCodeName); + } + } + jvmTypesBuilder.setInitializer(it1, (final ITreeAppendable appendable) -> { + StringBuilder sb = new StringBuilder(512); + sb.append(ImmutableMap.class.getSimpleName()).append(".<").append(String.class.getSimpleName()).append(", ").append(String.class.getSimpleName()).append(">builderWithExpectedSize(").append(sortedUniqueQualifiedIssueCodeNamesAndLabels.entrySet().size()).append(")\n"); + for (Map.Entry qualifiedIssueCodeNameAndLabel : sortedUniqueQualifiedIssueCodeNamesAndLabels.entrySet()) { + sb.append(" .put(").append(qualifiedIssueCodeNameAndLabel.getKey()).append(", \"").append(qualifiedIssueCodeNameAndLabel.getValue()).append("\")\n"); + } + sb.append(" .build()\n"); + appendable.append(sb.toString()); + }); + })); + it.getMembers().add(jvmTypesBuilder.toMethod(catalog, checkGeneratorNaming.fieldGetterName(checkGeneratorNaming.issueCodeToLabelMapFieldName()), issueCodeToLabelMapTypeRef, (final JvmOperation it1) -> { + jvmTypesBuilder.setDocumentation(it1, "Get map of issue code to label for " + catalog.getName() + ".\n\n@returns Map of issue code to label for " + + catalog.getName() + ".\n"); + it1.setStatic(true); + it1.setFinal(true); + jvmTypesBuilder.setBody(it1, (final ITreeAppendable appendable) -> { + appendable.append("return " + checkGeneratorNaming.issueCodeToLabelMapFieldName() + ";"); + }); + })); + + Iterables.addAll(it.getMembers(), IterableExtensions. filterNull(Iterables. concat(ListExtensions.> map(catalog.getAllChecks(), (final Check c) -> createIssue(catalog, c))))); + }); + + acceptor. accept(jvmTypesBuilder.toClass(catalog, checkGeneratorNaming.qualifiedValidatorClassName(catalog)), (final JvmGenericType it) -> { + JvmTypeReference parentType = checkedTypeRef(catalog, DispatchingCheckImpl.class); + if (parentType != null) { + it.getSuperTypes().add(parentType); + } + // Constructor will be added automatically. + jvmTypesBuilder.setDocumentation(it, "Validator for " + catalog.getName() + "."); + // Create catalog injections + Iterables.addAll(it.getMembers(), createInjectedField(catalog, checkGeneratorNaming.catalogInstanceName(catalog), _typeReferenceBuilder.typeRef(catalogClass))); + // Create fields + Iterables.addAll(it.getMembers(), ListExtensions. map(catalog.getMembers(), (final Member m) -> jvmTypesBuilder.toField(m, m.getName(), m.getType(), (final JvmField it1) -> { + jvmTypesBuilder.setInitializer(it1, m.getValue()); + jvmTypesBuilder.addAnnotations(it1, m.getAnnotations()); + }))); + // Create catalog name function + it.getMembers().add(jvmTypesBuilder.toMethod(catalog, "getQualifiedCatalogName", _typeReferenceBuilder.typeRef(String.class), (final JvmOperation it1) -> { + jvmTypesBuilder.setBody(it1, (final ITreeAppendable appendable) -> { + appendable.append("return \"" + catalog.getPackageName() + "." + catalog.getName() + "\";"); + }); + })); + + // Create getter for map of issue code to label + it.getMembers().add(jvmTypesBuilder.toMethod(catalog, checkGeneratorNaming.fieldGetterName(checkGeneratorNaming.issueCodeToLabelMapFieldName()), issueCodeToLabelMapTypeRef, (final JvmOperation it1) -> { + it1.setFinal(true); + jvmTypesBuilder.setBody(it1, (final ITreeAppendable appendable) -> { + appendable.append("return " + checkGeneratorNaming.catalogClassName(catalog) + "." + + checkGeneratorNaming.fieldGetterName(checkGeneratorNaming.issueCodeToLabelMapFieldName()) + "();"); + }); + })); + + it.getMembers().add(createDispatcherMethod(catalog)); + + // Create methods for contexts in checks + EList checks = catalog.getChecks(); + Iterable flattenedCatChecks = Iterables. concat(ListExtensions.> map(catalog.getCategories(), (final Category cat) -> cat.getChecks())); + Iterable allChecks = Iterables. concat(checks, flattenedCatChecks); + Iterables.addAll(it.getMembers(), Iterables. concat(IterableExtensions.> map(allChecks, (final Check chk) -> createCheck(chk)))); + // Create methods for stand-alone context implementations + Iterables.addAll(it.getMembers(), IterableExtensions. filterNull(ListExtensions. map(catalog.getImplementations(), (final Implementation impl) -> createCheckMethod(impl.getContext())))); + }); + acceptor. accept(jvmTypesBuilder.toClass(catalog, checkGeneratorNaming.qualifiedPreferenceInitializerClassName(catalog)), (final JvmGenericType it) -> { + JvmTypeReference parentType = checkedTypeRef(catalog, AbstractPreferenceInitializer.class); + if (parentType != null) { + it.getSuperTypes().add(parentType); + } + it.getMembers().add(jvmTypesBuilder.toField(catalog, "RUNTIME_NODE_NAME", _typeReferenceBuilder.typeRef(String.class), (final JvmField it1) -> { + it1.setStatic(true); + it1.setFinal(true); + jvmTypesBuilder.setInitializer(it1, (final ITreeAppendable appendable) -> { + appendable.append("\"" + checkGeneratorExtensions.bundleName(catalog) + "\""); + }); + })); + Iterables.addAll(it.getMembers(), createFormalParameterFields(catalog)); + Iterables.addAll(it.getMembers(), createPreferenceInitializerMethods(catalog)); + }); + } + // CHECKSTYLE:CHECK-ON LambdaBodyLength + + private JvmOperation createDispatcherMethod(final CheckCatalog catalog) { + JvmTypeReference objectBaseJavaTypeRef = checkedTypeRef(catalog, EObject.class); + return jvmTypesBuilder.toMethod(catalog, "validate", _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> { + it.setVisibility(JvmVisibility.PUBLIC); + it.getParameters().add(jvmTypesBuilder.toParameter(catalog, "checkMode", checkedTypeRef(catalog, CheckMode.class))); + it.getParameters().add(jvmTypesBuilder.toParameter(catalog, "object", objectBaseJavaTypeRef)); + it.getParameters().add(jvmTypesBuilder.toParameter(catalog, "diagnosticCollector", checkedTypeRef(catalog, DiagnosticCollector.class))); + Iterables.addAll(it.getAnnotations(), createAnnotation(checkedTypeRef(catalog, Override.class), (final JvmAnnotationReference it1) -> { + })); + jvmTypesBuilder.setBody(it, (final ITreeAppendable out) -> { + emitDispatcherMethodBody(out, catalog, objectBaseJavaTypeRef); + }); + }); + } + + private void emitDispatcherMethodBody(final ITreeAppendable out, final CheckCatalog catalog, final JvmTypeReference objectBaseJavaTypeRef) { + /* + * A catalog may contain both Check and Implementation objects, + * which in turn may contain Context objects. + * Categories may optionally be used for grouping checks, and + * we can include categorized checks by using getAllChecks(). + * We only consider Context objects with a typed contextVariable. + */ + Iterable checkContexts = Iterables. concat(ListExtensions.> map(catalog.getAllChecks(), (final Check chk) -> chk.getContexts())); + Iterable implContexts = IterableExtensions. filterNull(ListExtensions. map(catalog.getImplementations(), (final Implementation impl) -> impl.getContext())); + Iterable allContexts = IterableExtensions. filter(Iterables. concat(checkContexts, implContexts), (final Context ctx) -> { + JvmTypeReference type = null; + if (ctx.getContextVariable() != null) { + type = ctx.getContextVariable().getType(); + } + return type != null; + }); + + /* + * Contexts grouped by CheckType. + * We use an OrderedMap for deterministic ordering of check type checks. + * For Context objects we retain their order of appearance, apart from groupings. + */ + Map> contextsByCheckType = new TreeMap>(); + for (Context context : allContexts) { + contextsByCheckType.compute(checkGeneratorExtensions.checkType(context), (final CheckType k, final List lst) -> lst != null ? lst + : new java.util.ArrayList()).add(context); + } + + String baseTypeName = objectBaseJavaTypeRef.getQualifiedName(); + + for (Iterator>> iterator = contextsByCheckType.entrySet().iterator(); iterator.hasNext();) { + Map.Entry> entry = iterator.next(); + String checkType = "CheckType." + entry.getKey(); + + out.append("if (checkMode.shouldCheck(" + checkType + ")) {"); + out.increaseIndentation(); + out.newLine(); + out.append("diagnosticCollector.setCurrentCheckType(" + checkType + ");"); + emitInstanceOfConditionals(out, entry.getValue(), catalog, baseTypeName); // with preceding newline for each + out.decreaseIndentation(); + out.newLine(); + out.append("}"); + if (iterator.hasNext()) { // not at method body end + out.newLine(); // separator between mode checks + } + } + } + + private void emitInstanceOfConditionals(final ITreeAppendable out, final List contexts, final CheckCatalog catalog, final String baseTypeName) { + /* + * Contexts grouped by fully qualified variable type name, + * otherwise in order of appearance. + */ + Map> contextsByVarType = new TreeMap>(); + for (Context context : contexts) { + contextsByVarType.compute(context.getContextVariable().getType().getQualifiedName(), (final String k, final List lst) -> lst != null ? lst + : new java.util.ArrayList()).add(context); + } + + /* Ordering for context variable type checks. */ + List contextVarTypes = ListExtensions. map(contexts, (final Context x) -> x.getContextVariable().getType()); + InstanceOfCheckOrderer.Forest forest = InstanceOfCheckOrderer.orderTypes(contextVarTypes); + + emitInstanceOfTree(out, forest, null, contextsByVarType, catalog, baseTypeName, 0); + } + + private void emitInstanceOfTree(final ITreeAppendable out, final InstanceOfCheckOrderer.Forest forest, final String node, final Map> contextsByVarType, final CheckCatalog catalog, final String baseTypeName, final int level) { + if (node != null) { + String typeName = node; + if (Objects.equals(typeName, baseTypeName)) { + typeName = null; + } + String varName; + if (typeName == null) { + varName = "object"; + } else { + varName = "castObject" + (level > 1 ? Integer.toString(level) : ""); + } + + out.newLine(); + StringBuilder sb = new StringBuilder(512); + if (typeName != null) { + sb.append("if (object instanceof final ").append(typeName).append(' ').append(varName).append(") "); + } + sb.append('{'); + out.append(sb.toString()); + out.increaseIndentation(); + + List ctxList = contextsByVarType.get(node); + for (Context context : ctxList) { + emitCheckMethodCall(out, varName, context, catalog); // with preceding newline + } + } + + Collection subTypes = forest.getSubTypes(node); + for (String child : subTypes) { + emitInstanceOfTree(out, forest, child, contextsByVarType, catalog, baseTypeName, level + 1); + } + + if (node != null) { + out.decreaseIndentation(); + out.newLine(); + out.append("}"); + } + } + + private void emitCheckMethodCall(final ITreeAppendable out, final String varName, final Context context, final CheckCatalog catalog) { + String methodName = generateContextMethodName(context); + String jMethodName = toJavaLiteral(methodName); + String qMethodName = toJavaLiteral(catalog.getName(), methodName); + + out.newLine(); + out.append("validate(" + jMethodName + ", " + qMethodName + ", object,\n () -> " + methodName + "(" + varName + + ", diagnosticCollector), diagnosticCollector);"); + } + + private String toJavaLiteral(final String... strings) { + return "\"" + Strings.convertToJavaString(String.join(".", strings)) + "\""; + } + + private Iterable createInjectedField(final CheckCatalog context, final String fieldName, final JvmTypeReference type) { + // Generate @Inject private typeName fieldName; + if (type == null) { + return Collections.emptyList(); + } + JvmField field = typesFactory.createJvmField(); + field.setSimpleName(fieldName); + field.setVisibility(JvmVisibility.PRIVATE); + field.setType(jvmTypesBuilder.cloneWithProxies(type)); + Iterables.addAll(field.getAnnotations(), createAnnotation(checkedTypeRef(context, Inject.class), (final JvmAnnotationReference it) -> { + })); + return Collections.singleton(field); + } + + private Iterable createCheck(final Check chk) { + // If we don't have FormalParameters, there's no need to do all this song and dance with inner classes. + if (chk.getFormalParameters().isEmpty()) { + return ListExtensions. map(chk.getContexts(), (final Context ctx) -> createCheckMethod(ctx)); + } else { + return createCheckWithParameters(chk); + } + } + + private Iterable createCheckWithParameters(final Check chk) { + // Generate an inner class, plus a field holding an instance of that class. + // Put the formal parameters into that class as fields. + // For each check context, generate a run method. + // For each check context, generate an annotated check method outside to call the appropriate run method. + // This is the only way I found to make those formal parameters visible in the check constraints... + // The generated Java looks a bit strange, because we suppress actually generating these fields, as we + // don't use them; we only need them for scoping based on this inferred model. + List newMembers = Lists.newArrayList(); + // First the class + JvmGenericType checkClass = jvmTypesBuilder.toClass(chk, StringExtensions.toFirstUpper(chk.getName()) + "Class", (final JvmGenericType it) -> { + it.getSuperTypes().add(_typeReferenceBuilder.typeRef(Object.class)); + it.setVisibility(JvmVisibility.PRIVATE); + // Add a fields for the parameters, so that they can be linked. We suppress generation of these fields in the generator, + // and replace all references by calls to the getter function in the catalog. + Iterables.addAll(it.getMembers(), IterableExtensions. map(IterableExtensions. filter(chk.getFormalParameters(), (final FormalParameter f) -> f.getType() != null + && f.getName() != null), (final FormalParameter f) -> jvmTypesBuilder.toField(f, f.getName(), f.getType(), (final JvmField it1) -> { + it1.setFinal(true); + }))); + }); + newMembers.add(checkClass); + newMembers.add(jvmTypesBuilder.toField(chk, StringExtensions.toFirstLower(chk.getName()) + + "Impl", _typeReferenceBuilder.typeRef(checkClass), (final JvmField it) -> { + jvmTypesBuilder.setInitializer(it, (final ITreeAppendable appendable) -> { + appendable.append("new " + checkClass.getSimpleName() + "()"); + }); + })); + Iterables.addAll(newMembers, IterableExtensions. filterNull(ListExtensions. map(chk.getContexts(), (final Context ctx) -> createCheckCaller(ctx, chk)))); + // If we create these above in the class initializer, the types of the context variables somehow are not resolved yet. + Iterables.addAll(checkClass.getMembers(), IterableExtensions. filterNull(ListExtensions. map(chk.getContexts(), (final Context ctx) -> createCheckExecution(ctx)))); + return newMembers; + } + + private JvmOperation createCheckExecution(final Context ctx) { + if (ctx == null || ctx.getContextVariable() == null) { + return null; + } + JvmTypeReference ctxVarType = ctx.getContextVariable().getType(); + String simpleName = null; + if (ctxVarType != null) { + simpleName = ctxVarType.getSimpleName(); + } + String functionName = "run" + StringExtensions.toFirstUpper(simpleName); + return jvmTypesBuilder.toMethod(ctx, functionName, _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> { + String paramName = ctx.getContextVariable().getName() == null ? CheckConstants.IT : ctx.getContextVariable().getName(); + it.getParameters().add(jvmTypesBuilder.toParameter(ctx, paramName, ctx.getContextVariable().getType())); + it.getParameters().add(jvmTypesBuilder.toParameter(ctx, "diagnosticCollector", checkedTypeRef(ctx, DiagnosticCollector.class))); + jvmTypesBuilder.setBody(it, ctx.getConstraint()); + }); + } + + private Iterable createCheckAnnotation(final Context ctx) { + JvmTypeReference checkTypeTypeRef = checkedTypeRef(ctx, CheckType.class); + if (checkTypeTypeRef == null) { + return Collections.emptyList(); + } + XFeatureCall featureCall = XbaseFactory.eINSTANCE.createXFeatureCall(); + featureCall.setFeature(checkTypeTypeRef.getType()); + featureCall.setTypeLiteral(true); + XMemberFeatureCall memberCall = XbaseFactory.eINSTANCE.createXMemberFeatureCall(); + memberCall.setMemberCallTarget(featureCall); + // The grammar doesn't use the CheckType constants directly... + String name = checkGeneratorExtensions.checkTypeQName(ctx); + int i = name.lastIndexOf('.'); + if (i >= 0) { + name = name.substring(i + 1); + } + memberCall.setFeature(IterableExtensions.head(((JvmDeclaredType) checkTypeTypeRef.getType()).findAllFeaturesByName(name))); + + // memberCall needs to belong to a resource. + // We add it as a separate model to the context's resource. + ctx.eResource().getContents().add(memberCall); + + return createAnnotation(checkedTypeRef(ctx, org.eclipse.xtext.validation.Check.class), (final JvmAnnotationReference it) -> { + it.getExplicitValues().add(jvmTypesBuilder.toJvmAnnotationValue(memberCall)); + }); + } + + private JvmOperation createCheckCaller(final Context ctx, final Check chk) { + if (ctx == null || ctx.getContextVariable() == null) { + return null; + } + JvmTypeReference ctxVarType = ctx.getContextVariable().getType(); + String simpleName = null; + if (ctxVarType != null) { + simpleName = ctxVarType.getSimpleName(); + } + String functionName = StringExtensions.toFirstLower(chk.getName()) + simpleName; + // To make the formal parameter visible, we have to generate quite a bit... I see no way to get the XVariableDeclaration for them + // into the XBlockExpression of ctx.constraint. Just copying them doesn't work; modifies the source model! + // Therefore, we generate something new: each check becomes a local class + + return jvmTypesBuilder.toMethod(ctx, functionName, _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> { + it.getParameters().add(jvmTypesBuilder.toParameter(ctx, "context", ctx.getContextVariable().getType())); + it.getParameters().add(jvmTypesBuilder.toParameter(ctx, "diagnosticCollector", checkedTypeRef(ctx, DiagnosticCollector.class))); + Iterables.addAll(it.getAnnotations(), createCheckAnnotation(ctx)); + jvmTypesBuilder.setDocumentation(it, functionName + "."); // Well, that's not very helpful, but it is what the old compiler did... + jvmTypesBuilder.setBody(it, (final ITreeAppendable appendable) -> { + JvmTypeReference ctxVarType1 = ctx.getContextVariable().getType(); + String simpleName1 = null; + if (ctxVarType1 != null) { + simpleName1 = ctxVarType1.getSimpleName(); + } + appendable.append(StringExtensions.toFirstLower(chk.getName()) + "Impl" + ".run" + StringExtensions.toFirstUpper(simpleName1) + + "(context, diagnosticCollector);"); + }); + }); + } + + private JvmOperation createCheckMethod(final Context ctx) { + // Simple case for contexts of checks that do not have formal parameters. No need to generate nested classes for these. + if (ctx == null || ctx.getContextVariable() == null) { + return null; + } + String functionName = generateContextMethodName(ctx); + + return jvmTypesBuilder.toMethod(ctx, functionName, _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> { + String paramName = ctx.getContextVariable().getName() == null ? CheckConstants.IT : ctx.getContextVariable().getName(); + it.getParameters().add(jvmTypesBuilder.toParameter(ctx, paramName, ctx.getContextVariable().getType())); + it.getParameters().add(jvmTypesBuilder.toParameter(ctx, "diagnosticCollector", checkedTypeRef(ctx, DiagnosticCollector.class))); + Iterables.addAll(it.getAnnotations(), createCheckAnnotation(ctx)); + jvmTypesBuilder.setDocumentation(it, functionName + "."); // Well, that's not very helpful, but it is what the old compiler did... + jvmTypesBuilder.setBody(it, ctx.getConstraint()); + }); + } + + private String generateContextMethodName(final Context ctx) { + EObject container = ctx.eContainer(); + String baseName; + if (container instanceof Check check) { + baseName = check.getName(); + } else if (container instanceof Implementation impl) { + baseName = impl.getName(); + } else { + baseName = null; + } + JvmTypeReference ctxVarType = ctx.getContextVariable().getType(); + String simpleName = null; + if (ctxVarType != null) { + simpleName = ctxVarType.getSimpleName(); + } + return StringExtensions.toFirstLower(baseName) + simpleName; + } + + // CheckCatalog + + // CHECKSTYLE:CHECK-OFF LambdaBodyLength + private Iterable createIssue(final CheckCatalog catalog, final Check check) { + List members = Lists.newArrayList(); + for (FormalParameter parameter : check.getFormalParameters()) { + JvmTypeReference returnType = parameter.getType(); + if (returnType != null && !returnType.eIsProxy()) { + String returnName = returnType.getQualifiedName(); + String operation; + if (returnName != null) { + operation = switch (returnName) { + case "java.lang.Boolean" -> "getBoolean"; + case "boolean" -> "getBoolean"; + case "java.lang.Integer" -> "getInt"; + case "int" -> "getInt"; + case "java.util.List" -> "getStrings"; + case "java.util.List" -> "getBooleans"; + case "java.util.List" -> "getIntegers"; + default -> "getString"; + }; + } else { + operation = "getString"; + } + String parameterKey = CheckPropertiesGenerator.parameterKey(parameter, check); + String defaultName = "null"; + if (parameter.getRight() != null) { + defaultName = CheckGeneratorExtensions.splitCamelCase(checkGeneratorNaming.formalParameterGetterName(parameter)).toUpperCase() + "_DEFAULT"; + // Is generated into the PreferenceInitializer. Actually, since we do have it in the initializer, passing it here again + // as default value is just a safety measure if something went wrong and the property shouldn't be set. + } + String javaDefaultValue = checkGeneratorNaming.preferenceInitializerClassName(catalog) + "." + defaultName; + members.add(jvmTypesBuilder.toMethod(parameter, checkGeneratorNaming.formalParameterGetterName(parameter), returnType, (final JvmOperation it) -> { + jvmTypesBuilder.setDocumentation(it, "Gets the run-time value of formal parameter " + parameter.getName() + + ". The value\nreturned is either the default as defined in the check definition, or the\nconfigured value, if existing.\n\n@param context\n the context object used to determine the current project in\n order to check if a configured value exists in a project scope\n@return the run-time value of " + + parameter.getName() + ""); + JvmTypeReference eObjectTypeRef = checkedTypeRef(parameter, EObject.class); + if (eObjectTypeRef != null) { + it.getParameters().add(jvmTypesBuilder.toParameter(parameter, "context", eObjectTypeRef)); + } + jvmTypesBuilder.setBody(it, (final ITreeAppendable appendable) -> { + appendable.append("return checkConfigurationStoreService.getCheckConfigurationStore(context)." + operation + "(\"" + parameterKey + "\", " + + javaDefaultValue + ");"); + }); + })); + } // end if + } // end for + members.add(jvmTypesBuilder.toMethod(check, "get" + StringExtensions.toFirstUpper(check.getName()) + + "Message", _typeReferenceBuilder.typeRef(String.class), (final JvmOperation it) -> { + jvmTypesBuilder.setDocumentation(it, CheckJvmModelInferrerUtil.GET_MESSAGE_DOCUMENTATION); + // Generate one parameter "Object... bindings" + it.setVarArgs(true); + it.getParameters().add(jvmTypesBuilder.toParameter(check, "bindings", jvmTypesBuilder.addArrayTypeDimension(_typeReferenceBuilder.typeRef(Object.class)))); + jvmTypesBuilder.setBody(it, (final ITreeAppendable appendable) -> { + appendable.append("return org.eclipse.osgi.util.NLS.bind(\"" + Strings.convertToJavaString(check.getMessage()) + "\", bindings);"); + }); + // TODO (minor): how to get NLS into the imports? + })); + JvmTypeReference severityType = checkedTypeRef(check, SeverityKind.class); + if (severityType != null) { + members.add(jvmTypesBuilder.toMethod(check, "get" + StringExtensions.toFirstUpper(check.getName()) + "SeverityKind", severityType, (final JvmOperation it) -> { + jvmTypesBuilder.setDocumentation(it, "Gets the {@link SeverityKind severity kind} of check\n" + check.getLabel() + + ". The severity kind returned is either the\ndefault ({@code " + check.getDefaultSeverity().name() + + "}), as is set in the check definition, or the\nconfigured value, if existing.\n\n@param context\n the context object used to determine the current project in\n order to check if a configured value exists in a project scope\n@return the severity kind of this check: returns the default (" + + check.getDefaultSeverity().name() + + ") if\n no configuration for this check was found, else the configured\n value looked up in the configuration store"); + JvmTypeReference eObjectTypeRef = checkedTypeRef(check, EObject.class); + if (eObjectTypeRef != null) { + it.getParameters().add(jvmTypesBuilder.toParameter(check, "context", eObjectTypeRef)); + } + jvmTypesBuilder.setBody(it, (final ITreeAppendable appendable) -> { + appendable.append("final int result = checkConfigurationStoreService.getCheckConfigurationStore(context).getInt(\"" + + CheckPropertiesGenerator.checkSeverityKey(check) + "\", " + check.getDefaultSeverity().getValue() + + ");\nreturn SeverityKind.values()[result];"); + }); + })); + } + return members; + } + // CHECKSTYLE:CHECK-ON LambdaBodyLength + + // PreferenceInitializer. + + private Iterable createFormalParameterFields(final CheckCatalog catalog) { + // For each formal parameter, create a public static final field with a unique name derived from the formal parameter and + // set it to its right-hand side expression. We let Java evaluate this! + EList checks = catalog.getChecks(); + Iterable flattenedCatChecks = Iterables. concat(ListExtensions.> map(catalog.getCategories(), (final Category cat) -> cat.getChecks())); + Iterable allChecks = Iterables. concat(checks, flattenedCatChecks); + List result = Lists.newArrayList(); + for (Check c : allChecks) { + for (FormalParameter parameter : c.getFormalParameters()) { + if (parameter.getType() != null && parameter.getRight() != null) { + String defaultName = CheckGeneratorExtensions.splitCamelCase(checkGeneratorNaming.formalParameterGetterName(parameter)).toUpperCase() + "_DEFAULT"; + result.add(jvmTypesBuilder.toField(parameter, defaultName, parameter.getType(), (final JvmField it) -> { + it.setVisibility(JvmVisibility.PUBLIC); + it.setFinal(true); + it.setStatic(true); + jvmTypesBuilder.setInitializer(it, parameter.getRight()); + })); + } + } + } + return result; + } + + // CHECKSTYLE:CHECK-OFF LambdaBodyLength + private Iterable createPreferenceInitializerMethods(final CheckCatalog catalog) { + JvmTypeReference prefStore = checkedTypeRef(catalog, IEclipsePreferences.class); + List result = Lists.newArrayList(); + + if (prefStore != null) { + result.add(jvmTypesBuilder.toMethod(catalog, "initializeDefaultPreferences", _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> { + Iterables.addAll(it.getAnnotations(), createAnnotation(checkedTypeRef(catalog, Override.class), (final JvmAnnotationReference it1) -> { + })); + it.setVisibility(JvmVisibility.PUBLIC); + jvmTypesBuilder.setBody(it, (final ITreeAppendable appendable) -> { + appendable.append("IEclipsePreferences preferences = org.eclipse.core.runtime.preferences.InstanceScope.INSTANCE.getNode(RUNTIME_NODE_NAME);\n\ninitializeSeverities(preferences);\ninitializeFormalParameters(preferences);"); + }); + })); + EList checks = catalog.getChecks(); + Iterable flattenedCatChecks = Iterables. concat(ListExtensions.> map(catalog.getCategories(), (final Category cat) -> cat.getChecks())); + Iterable allChecks = Iterables. concat(checks, flattenedCatChecks); + result.add(jvmTypesBuilder.toMethod(catalog, "initializeSeverities", _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> { + it.setVisibility(JvmVisibility.PRIVATE); + it.getParameters().add(jvmTypesBuilder.toParameter(catalog, "preferences", prefStore)); + jvmTypesBuilder.setBody(it, (final ITreeAppendable appendable) -> { + StringBuilder sb = new StringBuilder(); + for (Check c : allChecks) { + sb.append("preferences.putInt(\"").append(CheckPropertiesGenerator.checkSeverityKey(c)).append("\", ").append(c.getDefaultSeverity().getValue()).append(");\n"); + } + appendable.append(sb.toString()); + }); + })); + result.add(jvmTypesBuilder.toMethod(catalog, "initializeFormalParameters", _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> { + it.setVisibility(JvmVisibility.PRIVATE); + it.getParameters().add(jvmTypesBuilder.toParameter(catalog, "preferences", jvmTypesBuilder.cloneWithProxies(prefStore))); + jvmTypesBuilder.setBody(it, (final ITreeAppendable appendable) -> { + for (Check c : allChecks) { + for (FormalParameter parameter : c.getFormalParameters()) { + if (parameter.getRight() != null) { + String key = CheckPropertiesGenerator.parameterKey(parameter, c); + String defaultFieldName = CheckGeneratorExtensions.splitCamelCase(checkGeneratorNaming.formalParameterGetterName(parameter)).toUpperCase() + + "_DEFAULT"; + JvmTypeReference jvmType = parameter.getType(); + String typeName = jvmType.getQualifiedName(); + if (typeName != null && typeName.startsWith("java.util.List<")) { + // Marshal lists. + EList args = ((JvmParameterizedTypeReference) jvmType).getArguments(); + if (args != null && args.size() == 1) { + String baseTypeName = IterableExtensions. head(args).getSimpleName(); + appendable.append("preferences.put(\"" + key + "\", com.avaloq.tools.ddk.check.runtime.configuration.CheckPreferencesHelper.marshal" + + baseTypeName + "s(" + defaultFieldName + "));\n"); + } else { + appendable.append("// Found " + key + " with " + typeName + "\n"); + } + } else { + String prefOperation; + if (typeName != null) { + prefOperation = switch (typeName) { + case "java.lang.Boolean" -> "putBoolean"; + case "boolean" -> "putBoolean"; + case "java.lang.Integer" -> "putInt"; + case "int" -> "putInt"; + default -> "put"; + }; + } else { + prefOperation = "put"; + } + appendable.append("preferences." + prefOperation + "(\"" + key + "\", " + defaultFieldName + ");\n"); + } + } + } + } + }); + })); + } + return result; + } + // CHECKSTYLE:CHECK-ON LambdaBodyLength + // CHECKSTYLE:CONSTANTS-ON + + private Iterable createAnnotation(final JvmTypeReference typeRef, final Procedure1 initializer) { + if (typeRef == null) { + return Collections.emptyList(); + } + + JvmAnnotationReference annotation = typesFactory.createJvmAnnotationReference(); + annotation.setAnnotation((JvmAnnotationType) typeRef.getType()); + Objects.requireNonNull(initializer, "Initializer is null").apply(annotation); + + return Collections.singletonList(annotation); + } + + // Error handling etc. + + private void createError(final String message, final EObject context, final EStructuralFeature feature) { + Resource rsc = context.eResource(); + if (rsc != null) { + EStructuralFeature f = feature; + if (f == null) { + f = locationInFileProvider.getIdentifierFeature(context); + } + rsc.getErrors().add(new EObjectDiagnosticImpl(Severity.ERROR, IssueCodes.INFERRER_ERROR, "Check compiler: " + message, context, f, -1, null)); + } + } + + private void createTypeNotFoundError(final String name, final EObject context) { + createError("Type " + name + " not found; check project setup (missing required bundle?)", context, null); + } + + private JvmTypeReference checkedTypeRef(final EObject context, final Class clazz) { + if (clazz == null) { + createTypeNotFoundError("", context); + return null; + } + JvmTypeReference result = _typeReferenceBuilder.typeRef(clazz); + if (result == null || result.getType() == null) { + createTypeNotFoundError(clazz.getName(), context); + return null; + } + return result; + } + + @Override + public void infer(final EObject catalog, final IJvmDeclaredTypeAcceptor acceptor, final boolean preIndexingPhase) { + if (catalog instanceof CheckCatalog checkCatalog) { + _infer(checkCatalog, acceptor, preIndexingPhase); + } else if (catalog != null) { + _infer(catalog, acceptor, preIndexingPhase); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(catalog, acceptor, preIndexingPhase).toString()); + } + } +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/jvmmodel/CheckJvmModelInferrer.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/jvmmodel/CheckJvmModelInferrer.xtend deleted file mode 100644 index f182219f03..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/jvmmodel/CheckJvmModelInferrer.xtend +++ /dev/null @@ -1,646 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.jvmmodel - -import com.avaloq.tools.ddk.check.CheckConstants -import com.avaloq.tools.ddk.check.check.Check -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.Context -import com.avaloq.tools.ddk.check.check.FormalParameter -import com.avaloq.tools.ddk.check.check.Implementation -import com.avaloq.tools.ddk.check.generator.CheckGeneratorExtensions -import com.avaloq.tools.ddk.check.generator.CheckGeneratorNaming -import com.avaloq.tools.ddk.check.generator.CheckPropertiesGenerator -import com.avaloq.tools.ddk.check.resource.CheckLocationInFileProvider -import com.avaloq.tools.ddk.check.runtime.configuration.ICheckConfigurationStoreService -import com.avaloq.tools.ddk.check.runtime.issue.AbstractIssue -import com.avaloq.tools.ddk.check.runtime.issue.DispatchingCheckImpl -import com.avaloq.tools.ddk.check.runtime.issue.DispatchingCheckImpl.DiagnosticCollector -import com.avaloq.tools.ddk.check.runtime.issue.SeverityKind -import com.avaloq.tools.ddk.check.validation.IssueCodes -import com.google.common.collect.ImmutableMap -import com.google.common.collect.Lists -import com.google.inject.Inject -import com.google.inject.Singleton -import java.util.ArrayList -import java.util.Collections -import java.util.List -import java.util.Map -import java.util.Objects -import java.util.TreeMap -import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer -import org.eclipse.core.runtime.preferences.IEclipsePreferences -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EStructuralFeature -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.common.types.JvmAnnotationReference -import org.eclipse.xtext.common.types.JvmAnnotationType -import org.eclipse.xtext.common.types.JvmDeclaredType -import org.eclipse.xtext.common.types.JvmField -import org.eclipse.xtext.common.types.JvmMember -import org.eclipse.xtext.common.types.JvmOperation -import org.eclipse.xtext.common.types.JvmParameterizedTypeReference -import org.eclipse.xtext.common.types.JvmTypeReference -import org.eclipse.xtext.common.types.JvmVisibility -import org.eclipse.xtext.common.types.TypesFactory -import org.eclipse.xtext.diagnostics.Severity -import org.eclipse.xtext.util.Strings -import org.eclipse.xtext.validation.CheckMode -import org.eclipse.xtext.validation.CheckType -import org.eclipse.xtext.validation.EObjectDiagnosticImpl -import org.eclipse.xtext.xbase.XFeatureCall -import org.eclipse.xtext.xbase.XMemberFeatureCall -import org.eclipse.xtext.xbase.XbaseFactory -import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable -import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer -import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor -import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder -import org.eclipse.xtext.xbase.lib.Procedures.Procedure1 - -import static extension com.avaloq.tools.ddk.check.generator.CheckGeneratorExtensions.* -import static extension org.apache.commons.text.StringEscapeUtils.escapeJava - -/** - *

Infers a JVM model from the source model.

- * - *

The JVM model should contain all elements that would appear in the Java code - * which is generated from the source model. Other models link against the JVM model rather than the source model.

- */ -class CheckJvmModelInferrer extends AbstractModelInferrer { - - @Inject TypesFactory typesFactory - @Inject CheckLocationInFileProvider locationInFileProvider - - @Inject extension CheckGeneratorExtensions - @Inject extension CheckGeneratorNaming - @Inject extension JvmTypesBuilder - - def dispatch infer(CheckCatalog catalog, IJvmDeclaredTypeAcceptor acceptor, boolean preIndexingPhase) { - // The xbase automatic scoping mechanism (typeRef()) cannot find secondary classes in the same resource. It can - // only find indexed resources (either in the JDT index or in the xtext index). However, we'll initialize the - // JVM validator class before the resource gets indexed, so the JVM catalog class cannot be found yet when we - // create the injection in the validator. Therefore, remember the class here directly, and set it directly - // in the validator, completely bypassing any scoping. - if (preIndexingPhase) return; - val catalogClass = catalog.toClass(catalog.qualifiedCatalogClassName); - val issueCodeToLabelMapTypeRef = typeRef(ImmutableMap, typeRef(String), typeRef(String)) - acceptor.accept(catalogClass, [ - val parentType = checkedTypeRef(catalog, typeof(AbstractIssue)); - if (parentType !== null) { - superTypes += parentType; - } - annotations += createAnnotation(checkedTypeRef(catalog, typeof(Singleton)), []) - documentation = '''Issues for «catalog.name».'''; - members += createInjectedField(catalog, 'checkConfigurationStoreService', checkedTypeRef(catalog, typeof(ICheckConfigurationStoreService))); - - // Create map of issue code to label and associated getter - members += catalog.toField(issueCodeToLabelMapFieldName, issueCodeToLabelMapTypeRef, [ - static = true - final = true - // Get all issue codes and labels - val issues = catalog.checkAndImplementationIssues - // Use a TreeMap to eliminate duplicates, - // and also to sort by qualified issue code name so autogenerated files are more readable and less prone to spurious ordering changes. - // Do this when compiling the Check, to avoid discovering duplicates at runtime. - val sortedUniqueQualifiedIssueCodeNamesAndLabels = new TreeMap(); - for (issue : issues) { - val qualifiedIssueCodeName = issue.qualifiedIssueCodeName(); - val issueLabel = issue.issueLabel().escapeJava; - val existingIssueLabel = sortedUniqueQualifiedIssueCodeNamesAndLabels.putIfAbsent(qualifiedIssueCodeName, issueLabel); - if (null !== existingIssueLabel && issueLabel != existingIssueLabel) { - // This qualified issue code name is already in the map, with a different label. Fail the build. - throw new IllegalArgumentException('''Multiple issues found with qualified issue code name: «qualifiedIssueCodeName»''') - } - } - initializer = [append(''' - «ImmutableMap.simpleName».<«String.simpleName», «String.simpleName»>builderWithExpectedSize(«sortedUniqueQualifiedIssueCodeNamesAndLabels.entrySet.size») - «FOR qualifiedIssueCodeNameAndLabel : sortedUniqueQualifiedIssueCodeNamesAndLabels.entrySet» - .put(«qualifiedIssueCodeNameAndLabel.key», "«qualifiedIssueCodeNameAndLabel.value»") - «ENDFOR» - .build() - ''')] - ]) - members += catalog.toMethod(issueCodeToLabelMapFieldName.fieldGetterName, issueCodeToLabelMapTypeRef, [ - documentation = ''' - Get map of issue code to label for «catalog.name». - - @returns Map of issue code to label for «catalog.name». - '''; - static = true - final = true - body = '''return «issueCodeToLabelMapFieldName»;''' - ]) - - members += catalog.allChecks.map(c|createIssue(catalog, c)).flatten.filterNull; - ]); - - acceptor.accept(catalog.toClass(catalog.qualifiedValidatorClassName), [ - val parentType = checkedTypeRef(catalog, typeof(DispatchingCheckImpl)); - if (parentType !== null) { - superTypes += parentType; - } - // Constructor will be added automatically. - documentation = ''' - Validator for «catalog.name».'''; - // Create catalog injections - members += createInjectedField(catalog, catalog.catalogInstanceName, typeRef(catalogClass)); - // Create fields - members += catalog.members.map(m|m.toField(m.name, m.type) [initializer = m.value; it.addAnnotations(m.annotations);]); - // Create catalog name function - members += catalog.toMethod('getQualifiedCatalogName', typeRef(typeof(String))) [ - body = [append('''return "«catalog.packageName».«catalog.name»";''')]; - ]; - - // Create getter for map of issue code to label - members += catalog.toMethod(issueCodeToLabelMapFieldName.fieldGetterName, issueCodeToLabelMapTypeRef, [ - final = true - body = '''return «catalog.catalogClassName».«issueCodeToLabelMapFieldName.fieldGetterName»();''' - ]) - - members += createDispatcherMethod(catalog); - - // Create methods for contexts in checks - val allChecks = catalog.checks + catalog.categories.map(cat|cat.checks).flatten; - members += allChecks.map(chk|createCheck(chk)).flatten; - // Create methods for stand-alone context implementations - members += catalog.implementations.map(impl|createCheckMethod(impl.context)).filterNull; - ]); - acceptor.accept(catalog.toClass(catalog.qualifiedPreferenceInitializerClassName), [ - val parentType = checkedTypeRef(catalog, typeof(AbstractPreferenceInitializer)); - if (parentType !== null) { - superTypes += parentType; - } - members += catalog.toField('RUNTIME_NODE_NAME', typeRef(typeof(String))) [ - static = true; - final = true; - initializer = [append('"' + catalog.bundleName + '"')]; - ]; - members += createFormalParameterFields(catalog); - members += createPreferenceInitializerMethods(catalog); - ]); - } - - private def JvmOperation createDispatcherMethod(CheckCatalog catalog) { - val objectBaseJavaTypeRef = checkedTypeRef(catalog, EObject); - return catalog.toMethod("validate", typeRef("void"), [ - visibility = JvmVisibility::PUBLIC; - parameters += catalog.toParameter("checkMode", checkedTypeRef(catalog, CheckMode)); - parameters += catalog.toParameter("object", objectBaseJavaTypeRef); - parameters += catalog.toParameter("diagnosticCollector", checkedTypeRef(catalog, DiagnosticCollector)); - annotations += createAnnotation(checkedTypeRef(catalog, typeof(Override)), []); - body = [out | emitDispatcherMethodBody(out, catalog, objectBaseJavaTypeRef)]; - ]); - } - - private def void emitDispatcherMethodBody(ITreeAppendable out, CheckCatalog catalog, JvmTypeReference objectBaseJavaTypeRef) { - /* A catalog may contain both Check and Implementation objects, - * which in turn may contain Context objects. - * Categories may optionally be used for grouping checks, and - * we can include categorized checks by using getAllChecks(). - * We only consider Context objects with a typed contextVariable. - */ - val allContexts = (catalog.allChecks.map(chk | chk.contexts).flatten + - catalog.implementations.map(impl | impl.context).filterNull) - .filter(ctx | ctx.contextVariable?.type !== null); - - /* Contexts grouped by CheckType. - * We use an OrderedMap for deterministic ordering of check type checks. - * For Context objects we retain their order of appearance, apart from groupings. - */ - val contextsByCheckType = new TreeMap>(); - for (Context context : allContexts) { - contextsByCheckType.compute(checkType(context), [k, lst | lst ?: new ArrayList()]).add(context); - } - - val baseTypeName = objectBaseJavaTypeRef.qualifiedName; - - for (val iterator = contextsByCheckType.entrySet.iterator(); iterator.hasNext(); ) { - val entry = iterator.next(); - val checkType = '''CheckType.«entry.key»'''; - - out.append('''if (checkMode.shouldCheck(«checkType»)) {'''); - out.increaseIndentation; - out.newLine; - out.append('''diagnosticCollector.setCurrentCheckType(«checkType»);'''); - emitInstanceOfConditionals(out, entry.value, catalog, baseTypeName); // with preceding newline for each - out.decreaseIndentation; - out.newLine; - out.append("}"); - if (iterator.hasNext()) // not at method body end - out.newLine; // separator between mode checks - } - } - - private def void emitInstanceOfConditionals(ITreeAppendable out, List contexts, CheckCatalog catalog, String baseTypeName) { - /* Contexts grouped by fully qualified variable type name, - * otherwise in order of appearance. - */ - val contextsByVarType = new TreeMap>(); - for (Context context : contexts) { - contextsByVarType.compute(context.contextVariable.type.qualifiedName, - [k, lst | lst ?: new ArrayList()] - ).add(context); - } - - /* Ordering for context variable type checks. */ - val List contextVarTypes = contexts.map([x | x.contextVariable.type]); - val forest = InstanceOfCheckOrderer.orderTypes(contextVarTypes); - - emitInstanceOfTree(out, forest, null, contextsByVarType, catalog, baseTypeName, 0); - } - - private def void emitInstanceOfTree(ITreeAppendable out, InstanceOfCheckOrderer.Forest forest, String node, Map> contextsByVarType, CheckCatalog catalog, String baseTypeName, int level) { - if (node !== null) { - var String typeName = node; - if (typeName == baseTypeName) - typeName = null; - val varName = if (typeName === null) "object" else "castObject" + (if (level > 1) Integer.toString(level) else ""); - - out.newLine; - out.append('''«IF typeName !== null»if (object instanceof final «typeName» «varName») «ENDIF»{'''); - out.increaseIndentation; - - val contexts = contextsByVarType.get(node); - for (context : contexts) { - emitCheckMethodCall(out, varName, context, catalog); // with preceding newline - } - } - - for (child : forest.getSubTypes(node)) { - emitInstanceOfTree(out, forest, child, contextsByVarType, catalog, baseTypeName, level + 1); - } - - if (node !== null) { - out.decreaseIndentation; - out.newLine; - out.append('}'); - } - } - - private def void emitCheckMethodCall(ITreeAppendable out, String varName, Context context, CheckCatalog catalog) { - val methodName = generateContextMethodName(context); - val jMethodName = toJavaLiteral(methodName); - val qMethodName = toJavaLiteral(catalog.name, methodName); - - out.newLine; - out.append(''' - validate(«jMethodName», «qMethodName», object, - () -> «methodName»(«varName», diagnosticCollector), diagnosticCollector);'''); - } - - private def String toJavaLiteral(String... strings) { - return '''"«Strings::convertToJavaString(String.join(".", strings))»"'''; - } - - private def Iterable createInjectedField(CheckCatalog context, String fieldName, JvmTypeReference type) { - // Generate @Inject private typeName fieldName; - if (type === null) { - return Collections::emptyList; - } - val field = typesFactory.createJvmField(); - field.simpleName = fieldName; - field.visibility = JvmVisibility::PRIVATE; - field.type = cloneWithProxies(type); - field.annotations += createAnnotation(checkedTypeRef(context, typeof(Inject)), []); - return Collections::singleton(field); - } - - private def Iterable createCheck(Check chk) { - // If we don't have FormalParameters, there's no need to do all this song and dance with inner classes. - if (chk.formalParameters.empty) { - return chk.contexts.map(ctx|createCheckMethod(ctx) as JvmMember); - } else { - return createCheckWithParameters(chk); - } - } - - private def Iterable createCheckWithParameters(Check chk) { - // Generate an inner class, plus a field holding an instance of that class. - // Put the formal parameters into that class as fields. - // For each check context, generate a run method. - // For each check context, generate an annotated check method outside to call the appropriate run method. - // This is the only way I found to make those formal parameters visible in the check constraints... - // The generated Java looks a bit strange, because we suppress actually generating these fields, as we - // don't use them; we only need them for scoping based on this inferred model. - val List newMembers = Lists::newArrayList; - // First the class - val checkClass = chk.toClass(chk.name.toFirstUpper + 'Class') [ - superTypes += typeRef(typeof(Object)); - visibility = JvmVisibility::PRIVATE; - // Add a fields for the parameters, so that they can be linked. We suppress generation of these fields in the generator, - // and replace all references by calls to the getter function in the catalog. - members += chk.formalParameters.filter(f|f.type !== null && f.name !== null).map(f|f.toField(f.name, f.type) [final = true]); - ]; - newMembers += checkClass; - newMembers += chk.toField(chk.name.toFirstLower + 'Impl', typeRef(checkClass)) [initializer = [append('''new «checkClass.simpleName»()''')]]; - newMembers += chk.contexts.map(ctx|createCheckCaller(ctx, chk)).filterNull; - // If we create these above in the class initializer, the types of the context variables somehow are not resolved yet. - checkClass.members += chk.contexts.map(ctx|createCheckExecution(ctx)).filterNull; - return newMembers; - } - - private def JvmOperation createCheckExecution(Context ctx) { - if (ctx === null || ctx.contextVariable === null) { - return null; - } - val String functionName = 'run' + ctx.contextVariable.type?.simpleName.toFirstUpper; - ctx.toMethod(functionName, typeRef('void')) [ - parameters += ctx.toParameter(if (ctx.contextVariable.name === null) CheckConstants::IT else ctx.contextVariable.name, ctx.contextVariable.type); - parameters += ctx.toParameter("diagnosticCollector", checkedTypeRef(ctx, DiagnosticCollector)); - body = ctx.constraint; - ] - } - - private def Iterable createCheckAnnotation (Context ctx) { - val checkTypeTypeRef = checkedTypeRef(ctx, typeof(CheckType)); - if (checkTypeTypeRef === null) { - return Collections::emptyList; - } - val XFeatureCall featureCall = XbaseFactory::eINSTANCE.createXFeatureCall(); - featureCall.feature = checkTypeTypeRef.type; - featureCall.typeLiteral = true; - val XMemberFeatureCall memberCall = XbaseFactory::eINSTANCE.createXMemberFeatureCall(); - memberCall.memberCallTarget = featureCall; - // The grammar doesn't use the CheckType constants directly... - var String name = checkTypeQName(ctx); - val int i = name.lastIndexOf('.'); - if (i >= 0) { - name = name.substring(i+1); - } - memberCall.feature = (checkTypeTypeRef.type as JvmDeclaredType).findAllFeaturesByName(name).head; - - // memberCall needs to belong to a resource. - // We add it as a separate model to the context's resource. - ctx.eResource.contents.add(memberCall) - - return createAnnotation(checkedTypeRef(ctx, typeof(org.eclipse.xtext.validation.Check)), [ - explicitValues += memberCall.toJvmAnnotationValue(); - ]); - } - - private def JvmOperation createCheckCaller(Context ctx, Check chk) { - if (ctx === null || ctx.contextVariable === null) { - return null; - } - val String functionName = chk.name.toFirstLower + ctx.contextVariable.type?.simpleName; - // To make the formal parameter visible, we have to generate quite a bit... I see no way to get the XVariableDeclaration for them - // into the XBlockExpression of ctx.constraint. Just copying them doesn't work; modifies the source model! - // Therefore, we generate something new: each check becomes a local class - - ctx.toMethod(functionName, typeRef('void')) [ - parameters += ctx.toParameter("context", ctx.contextVariable.type); - parameters += ctx.toParameter("diagnosticCollector", checkedTypeRef(ctx, DiagnosticCollector)); - annotations += createCheckAnnotation(ctx); - documentation = functionName + '.'; // Well, that's not very helpful, but it is what the old compiler did... - body = [append(''' - «chk.name.toFirstLower + 'Impl'».run«ctx.contextVariable.type?.simpleName.toFirstUpper»(context, diagnosticCollector);''' - )] - ] - } - - private def JvmOperation createCheckMethod(Context ctx) { - // Simple case for contexts of checks that do not have formal parameters. No need to generate nested classes for these. - if (ctx === null || ctx.contextVariable === null) { - return null; - } - val String functionName = generateContextMethodName(ctx); - - ctx.toMethod(functionName, typeRef('void')) [ - parameters += ctx.toParameter(if (ctx.contextVariable.name === null) CheckConstants::IT else ctx.contextVariable.name, ctx.contextVariable.type); - parameters += ctx.toParameter("diagnosticCollector", checkedTypeRef(ctx, DiagnosticCollector)); - annotations += createCheckAnnotation(ctx); - documentation = functionName + '.'; // Well, that's not very helpful, but it is what the old compiler did... - body = ctx.constraint; - ] - } - - private def String generateContextMethodName(Context ctx) { - return switch container : ctx.eContainer { - Check : container.name - Implementation: container.name - }.toFirstLower + ctx.contextVariable.type?.simpleName; - } - - // CheckCatalog - - private def Iterable createIssue(CheckCatalog catalog, Check check) { - val List members = Lists::newArrayList(); - for (FormalParameter parameter : check.formalParameters) { - val JvmTypeReference returnType = parameter.type; - if (returnType !== null && !returnType.eIsProxy) { - val String returnName = returnType.qualifiedName; - val String operation = switch returnName { - case 'java.lang.Boolean' : 'getBoolean' - case 'boolean' : 'getBoolean' - case 'java.lang.Integer' : 'getInt' - case 'int' : 'getInt' - case 'java.util.List' : 'getStrings' - case 'java.util.List' : 'getBooleans' - case 'java.util.List' : 'getIntegers' - default : 'getString' - } - val String parameterKey = CheckPropertiesGenerator::parameterKey(parameter, check); - var String defaultName = 'null'; - if (parameter.right !== null) { - defaultName = parameter.formalParameterGetterName.splitCamelCase.toUpperCase + '_DEFAULT'; - // Is generated into the PreferenceInitializer. Actually, since we do have it in the initializer, passing it here again - // as default value is just a safety measure if something went wrong and the property shouldn't be set. - } - val javaDefaultValue = catalog.preferenceInitializerClassName + '.' + defaultName; - members += parameter.toMethod(parameter.formalParameterGetterName, returnType) [ - documentation = ''' - Gets the run-time value of formal parameter «parameter.name». The value - returned is either the default as defined in the check definition, or the - configured value, if existing. - - @param context - the context object used to determine the current project in - order to check if a configured value exists in a project scope - @return the run-time value of «parameter.name»'''; - val eObjectTypeRef = checkedTypeRef(parameter, typeof(EObject)); - if (eObjectTypeRef !== null) { - parameters += parameter.toParameter('context', eObjectTypeRef); - } - body = [append(''' - return checkConfigurationStoreService.getCheckConfigurationStore(context).«operation»("«parameterKey»", «javaDefaultValue»);''' - )]; - ]; - } // end if - } // end for - members += check.toMethod('get' + check.name.toFirstUpper + 'Message', typeRef(typeof(String))) [ - documentation = CheckJvmModelInferrerUtil.GET_MESSAGE_DOCUMENTATION; - // Generate one parameter "Object... bindings" - varArgs = true; - parameters += check.toParameter('bindings', addArrayTypeDimension(typeRef(typeof(Object)))); - body = [append(''' - return org.eclipse.osgi.util.NLS.bind("«Strings::convertToJavaString(check.message)»", bindings);''' - )]; - // TODO (minor): how to get NLS into the imports? - ]; - val severityType = checkedTypeRef(check, typeof(SeverityKind)); - if (severityType !== null) { - members += check.toMethod('get' + check.name.toFirstUpper + 'SeverityKind', severityType) [ - documentation = ''' - Gets the {@link SeverityKind severity kind} of check - «check.label». The severity kind returned is either the - default ({@code «check.defaultSeverity.name()»}), as is set in the check definition, or the - configured value, if existing. - - @param context - the context object used to determine the current project in - order to check if a configured value exists in a project scope - @return the severity kind of this check: returns the default («check.defaultSeverity.name()») if - no configuration for this check was found, else the configured - value looked up in the configuration store'''; - val eObjectTypeRef = checkedTypeRef(check, typeof(EObject)); - if (eObjectTypeRef !== null) { - parameters += check.toParameter('context', eObjectTypeRef); - } - body = [append(''' - final int result = checkConfigurationStoreService.getCheckConfigurationStore(context).getInt("«CheckPropertiesGenerator::checkSeverityKey(check)»", «check.defaultSeverity.value»); - return SeverityKind.values()[result];''' - )]; - ]; - } - return members; - } - - // PreferenceInitializer. - - private def Iterable createFormalParameterFields (CheckCatalog catalog) { - // For each formal parameter, create a public static final field with a unique name derived from the formal parameter and - // set it to its right-hand side expression. We let Java evaluate this! - val allChecks = catalog.checks + catalog.categories.map(cat|cat.checks).flatten; - val List result = Lists::newArrayList(); - for (Check c : allChecks) { - for (FormalParameter parameter : c.formalParameters) { - if (parameter.type !== null && parameter.right !== null) { - val String defaultName = parameter.formalParameterGetterName.splitCamelCase.toUpperCase + '_DEFAULT'; - result += parameter.toField(defaultName, parameter.type) [ - visibility = JvmVisibility::PUBLIC; - final = true; - static = true; - initializer = parameter.right; - ]; - } - - } - } - return result; - } - - private def Iterable createPreferenceInitializerMethods(CheckCatalog catalog) { - val JvmTypeReference prefStore = checkedTypeRef(catalog, typeof (IEclipsePreferences)); - val List result = Lists::newArrayList(); - - if (prefStore !== null) { - result += catalog.toMethod('initializeDefaultPreferences', typeRef('void')) [ - annotations += createAnnotation(checkedTypeRef(catalog, typeof(Override)), []); - visibility = JvmVisibility::PUBLIC; - body = [append(''' - IEclipsePreferences preferences = org.eclipse.core.runtime.preferences.InstanceScope.INSTANCE.getNode(RUNTIME_NODE_NAME); - - initializeSeverities(preferences); - initializeFormalParameters(preferences);''')]; - ]; - val allChecks = catalog.checks + catalog.categories.map(cat|cat.checks).flatten; - result += catalog.toMethod('initializeSeverities', typeRef('void')) [ - visibility = JvmVisibility::PRIVATE; - parameters += catalog.toParameter('preferences', prefStore); - body = [append('''«FOR c:allChecks» - preferences.putInt("«CheckPropertiesGenerator::checkSeverityKey(c)»", «c.defaultSeverity.value»); - «ENDFOR»''')]; - ]; - result += catalog.toMethod('initializeFormalParameters', typeRef('void')) [ - visibility = JvmVisibility::PRIVATE; - parameters += catalog.toParameter('preferences', prefStore.cloneWithProxies); - body = [ - for (Check c : allChecks) { - for (FormalParameter parameter : c.formalParameters) { - if (parameter.right !== null) { - val String key = CheckPropertiesGenerator::parameterKey(parameter, c); - val String defaultFieldName = parameter.formalParameterGetterName.splitCamelCase.toUpperCase + '_DEFAULT'; - val JvmTypeReference jvmType = parameter.type; - val String typeName = jvmType.qualifiedName; - if (typeName !== null && typeName.startsWith("java.util.List<")) { - // Marshal lists. - val args = (jvmType as JvmParameterizedTypeReference).arguments; - if (args !== null && args.size == 1) { - val baseTypeName = args.head.simpleName; - append('''preferences.put("«key»", com.avaloq.tools.ddk.check.runtime.configuration.CheckPreferencesHelper.marshal«baseTypeName»s(«defaultFieldName»)); - '''); - } else { - append('''// Found «key» with «typeName» - '''); - } - } else { - val String operation = switch typeName { - case 'java.lang.Boolean' : 'putBoolean' - case 'boolean' : 'putBoolean' - case 'java.lang.Integer' : 'putInt' - case 'int' : 'putInt' - default : 'put' - }; - append('''preferences.«operation»("«key»", «defaultFieldName»); - '''); - } - } - } - } - ]; - ]; - } - return result; - - } - - private def Iterable createAnnotation (JvmTypeReference typeRef, Procedure1 initializer) { - if (typeRef === null) { - return Collections::emptyList; - } - - val annotation = typesFactory.createJvmAnnotationReference() - annotation.annotation = typeRef.type as JvmAnnotationType - Objects.requireNonNull(initializer, "Initializer is null").apply(annotation) - - return Collections::singletonList(annotation) - } - - // Error handling etc. - - private def createError (String message, EObject context, EStructuralFeature feature) { - val Resource rsc = context.eResource; - if (rsc !== null) { - var f = feature; - if (f === null) { - f = locationInFileProvider.getIdentifierFeature(context); - } - rsc.errors += new EObjectDiagnosticImpl(Severity::ERROR, IssueCodes::INFERRER_ERROR, "Check compiler: " + message, context, f, -1, null) - } - } - - private def createTypeNotFoundError(String name, EObject context) { - createError("Type " + name + " not found; check project setup (missing required bundle?)", context, null); - } - - private def JvmTypeReference checkedTypeRef(EObject context, Class clazz) { - if (clazz === null) { - createTypeNotFoundError ("", context); - return null; - } - val result = typeRef(clazz); - if (result === null || result.type === null) { - createTypeNotFoundError(clazz.name, context); - return null; - } - return result; - } -} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/scoping/CheckScopeProvider.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/scoping/CheckScopeProvider.java new file mode 100644 index 0000000000..91618050d3 --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/scoping/CheckScopeProvider.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.scoping; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.IGrammarAccess; +import org.eclipse.xtext.common.types.JvmType; +import org.eclipse.xtext.common.types.JvmTypeReference; +import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.EObjectDescription; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.IResourceServiceProvider; +import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider; +import org.eclipse.xtext.scoping.IGlobalScopeProvider; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.impl.MapBasedScope; +import org.eclipse.xtext.scoping.impl.SimpleScope; +import org.eclipse.xtext.xbase.annotations.typesystem.XbaseWithAnnotationsBatchScopeProvider; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ListExtensions; +import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver; + +import com.avaloq.tools.ddk.check.check.Check; +import com.avaloq.tools.ddk.check.check.CheckCatalog; +import com.avaloq.tools.ddk.check.check.CheckPackage; +import com.avaloq.tools.ddk.check.check.Context; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import com.avaloq.tools.ddk.check.naming.CheckDeclarativeQualifiedNameProvider; +import com.google.common.base.Predicates; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.google.inject.Inject; + + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class CheckScopeProvider extends XbaseWithAnnotationsBatchScopeProvider { + + @Inject + private CheckDeclarativeQualifiedNameProvider checkQualifiedNameProvider; + + @Inject + private IQualifiedNameConverter qualifiedNameConverter; + + @Inject + private IBatchTypeResolver typeResolver; + + @Inject + private IGlobalScopeProvider globalScopeProvider; + + @Inject + private ResourceDescriptionsProvider descriptionsProvider; + + // Use dispatch definitions instead of a switch statement since + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=368263 + // will otherwise cause the builder to fail during linking. + @Override + public IScope getScope(final EObject context, final EReference reference) { + IScope res = scope(context, reference); + if (res != null) { + return res; + } else { + return super.getScope(context, reference); + } + } + + protected IScope _scope(final XIssueExpression context, final EReference reference) { + if (reference == CheckPackage.Literals.XISSUE_EXPRESSION__MARKER_FEATURE) { + JvmTypeReference jvmTypeRef; + if (context.getMarkerObject() != null) { + jvmTypeRef = typeResolver.resolveTypes(context.getMarkerObject()).getActualType(context.getMarkerObject()).toTypeReference(); + } else { + jvmTypeRef = EcoreUtil2. getContainerOfType(context, Context.class).getContextVariable().getType(); + } + + if (jvmTypeRef != null) { + EClass eClass = classForJvmType(context, jvmTypeRef.getType()); + if (eClass != null) { + EList features = eClass.getEAllStructuralFeatures(); + Collection descriptions = Collections2.transform(features, (final EStructuralFeature f) -> EObjectDescription.create(QualifiedName.create(f.getName()), f)); + return MapBasedScope.createScope(IScope.NULLSCOPE, descriptions); + } else { + return IScope.NULLSCOPE; + } + } else { + return IScope.NULLSCOPE; + } + } else if (reference == CheckPackage.Literals.XISSUE_EXPRESSION__CHECK) { + // Make sure that only Checks of the current model can be referenced, and if the CheckCatalog includes + // another CheckCatalog, then use that parent as parent scope + + CheckCatalog catalog = EcoreUtil2. getContainerOfType(context, CheckCatalog.class); + List checks = IterableExtensions. toList(IterableExtensions. filter(catalog.getAllChecks(), c -> c.getName() != null)); + + Collection descriptions = Collections2.transform(checks, (final Check c) -> EObjectDescription.create(QualifiedName.create(c.getName()), c)); + // Determine the parent scope; use NULLSCOPE if no included CheckCatalog is defined (or if it cannot be resolved) + IScope parentScope = IScope.NULLSCOPE; + + return MapBasedScope.createScope(parentScope, Iterables.filter(descriptions, Predicates.notNull())); + } + return null; + } + + protected IScope _scope(final CheckCatalog context, final EReference reference) { + if (reference == CheckPackage.Literals.CHECK_CATALOG__GRAMMAR) { + IResourceServiceProvider.Registry reg = IResourceServiceProvider.Registry.INSTANCE; + // CHECKSTYLE:CHECK-OFF IllegalCatch + Collection descriptions = Collections2.transform(reg.getExtensionToFactoryMap().keySet(), (final String e) -> { + URI dummyUri = URI.createURI("foo:/foo." + e); + try { + Grammar g = reg.getResourceServiceProvider(dummyUri).get(IGrammarAccess.class).getGrammar(); + return EObjectDescription.create(qualifiedNameConverter.toQualifiedName(g.getName()), g); + } catch (Exception ex) { + return null; + } + }); + // CHECKSTYLE:CHECK-ON IllegalCatch + // We look first in the workspace for a grammar and then in the registry for a registered grammar + return MapBasedScope.createScope(IScope.NULLSCOPE, Iterables.filter(descriptions, Predicates.notNull())); + } else if (reference == CheckPackage.Literals.XISSUE_EXPRESSION__CHECK) { + List descriptions = ListExtensions.map(context.getAllChecks(), (final Check c) -> EObjectDescription.create(checkQualifiedNameProvider.getFullyQualifiedName(c), c)); + return new SimpleScope(super.getScope(context, reference), descriptions); + } + return null; + } + + // default implementation will throw an illegal argument exception + protected IScope _scope(final EObject context, final EReference reference) { + return null; + } + + public IScope scope(final EObject context, final EReference reference) { + if (context instanceof CheckCatalog) { + return _scope((CheckCatalog) context, reference); + } else if (context instanceof XIssueExpression) { + return _scope((XIssueExpression) context, reference); + } else if (context != null) { + return _scope(context, reference); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(context, reference).toString()); + } + } + + public EClass classForJvmType(final EObject context, final JvmType jvmType) { + if (jvmType != null && !jvmType.eIsProxy()) { + String qualifiedName = jvmType.getQualifiedName(); + String qualifiedPackageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.')); + String packageName = qualifiedPackageName.substring(qualifiedPackageName.lastIndexOf('.') + 1); + EPackage ePackage = getEPackage(context.eResource(), packageName); + if (ePackage != null) { + EClassifier eClassifier = ((EPackage) EcoreUtil.resolve(ePackage, context)).getEClassifier(jvmType.getSimpleName()); + if (eClassifier instanceof EClass) { + return (EClass) eClassifier; + } + } + } + return null; + } + + public EPackage getEPackage(final Resource context, final String name) { + // not using for-each loop, as it could result in a ConcurrentModificationException when a resource is demand-loaded + EList resources = context.getResourceSet().getResources(); + for (int i = 0; i < resources.size(); i++) { + Resource resource = resources.get(i); + for (EObject obj : resource.getContents()) { + if (obj instanceof EPackage && Objects.equals(((EPackage) obj).getName(), name)) { + return (EPackage) obj; + } + } + } + IEObjectDescription desc = globalScopeProvider.getScope(context, EcorePackage.Literals.EPACKAGE__ESUPER_PACKAGE, null).getSingleElement(QualifiedName.create(name)); + if (desc != null) { + return (EPackage) desc.getEObjectOrProxy(); + } + Iterator descs = descriptionsProvider.getResourceDescriptions(context).getExportedObjects(EcorePackage.Literals.EPACKAGE, QualifiedName.create(name), false).iterator(); + if (descs.hasNext()) { + EObject pkg = EcoreUtil.resolve(descs.next().getEObjectOrProxy(), context); + // this filtering only appears to be necessary when executing the unit test BugAig830 in Jenkins (see https://jira.sys.net/browse/ACF-8758) + if (!pkg.eIsProxy()) { + return (EPackage) pkg; + } + } + for (String nsUri : Sets.newHashSet(EPackage.Registry.INSTANCE.keySet())) { + EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(nsUri); + if (Objects.equals(ePackage.getName(), name)) { + return ePackage; + } + } + return null; + } + + // todo: scoping for the check implementation (e.g. the parameters are not visible) + + // todo: scope the allowed imports! +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/scoping/CheckScopeProvider.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/scoping/CheckScopeProvider.xtend deleted file mode 100644 index 01e43d9725..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/scoping/CheckScopeProvider.xtend +++ /dev/null @@ -1,178 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.scoping - -import com.avaloq.tools.ddk.check.check.CheckCatalog -import com.avaloq.tools.ddk.check.check.CheckPackage -import com.avaloq.tools.ddk.check.check.Context -import com.avaloq.tools.ddk.check.check.XIssueExpression -import com.avaloq.tools.ddk.check.naming.CheckDeclarativeQualifiedNameProvider -import com.google.common.base.Predicates -import com.google.common.collect.Collections2 -import com.google.common.collect.Iterables -import com.google.common.collect.Sets -import com.google.inject.Inject -import org.eclipse.emf.common.util.URI -import org.eclipse.emf.ecore.EClass -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EPackage -import org.eclipse.emf.ecore.EReference -import org.eclipse.emf.ecore.EcorePackage -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.IGrammarAccess -import org.eclipse.xtext.common.types.JvmType -import org.eclipse.xtext.naming.IQualifiedNameConverter -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.resource.EObjectDescription -import org.eclipse.xtext.resource.IResourceServiceProvider -import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider -import org.eclipse.xtext.scoping.IGlobalScopeProvider -import org.eclipse.xtext.scoping.IScope -import org.eclipse.xtext.scoping.impl.MapBasedScope -import org.eclipse.xtext.scoping.impl.SimpleScope -import org.eclipse.xtext.xbase.annotations.typesystem.XbaseWithAnnotationsBatchScopeProvider -import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver - -class CheckScopeProvider extends XbaseWithAnnotationsBatchScopeProvider { - - @Inject CheckDeclarativeQualifiedNameProvider checkQualifiedNameProvider - @Inject IQualifiedNameConverter qualifiedNameConverter - @Inject IBatchTypeResolver typeResolver; - @Inject IGlobalScopeProvider globalScopeProvider - @Inject ResourceDescriptionsProvider descriptionsProvider - - // Use dispatch definitions instead of a switch statement since - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=368263 - // will otherwise cause the builder to fail during linking. - override IScope getScope(EObject context, EReference reference) { - val res = scope(context, reference) - if (res !== null) res else super.getScope(context, reference) - } - - def dispatch IScope scope(XIssueExpression context, EReference reference) { - if (reference == CheckPackage.Literals::XISSUE_EXPRESSION__MARKER_FEATURE) { - var jvmTypeRef = - if (context.markerObject !== null) - typeResolver.resolveTypes(context.markerObject).getActualType(context.markerObject).toTypeReference - else - EcoreUtil2::getContainerOfType(context, typeof(Context)).contextVariable.type; - - if (jvmTypeRef !== null) { - val eClass = context.classForJvmType(jvmTypeRef.type); - if (eClass !== null) { - var features = eClass.EAllStructuralFeatures - val descriptions = Collections2::transform(features, [f | EObjectDescription::create(QualifiedName::create(f.name), f)]) - return MapBasedScope::createScope(IScope::NULLSCOPE, descriptions); - } else { - return IScope::NULLSCOPE; - } - } else { - return IScope::NULLSCOPE; - } - } else if (reference == CheckPackage.Literals::XISSUE_EXPRESSION__CHECK) { - // Make sure that only Checks of the current model can be referenced, and if the CheckCatalog includes - // another CheckCatalog, then use that parent as parent scope - - val catalog = EcoreUtil2::getContainerOfType(context, typeof(CheckCatalog)) - val checks = catalog.allChecks.filter(c|c.name !== null).toList - - val descriptions = Collections2::transform(checks, [c|EObjectDescription::create(QualifiedName::create(c.name), c)]) - // Determine the parent scope; use NULLSCOPE if no included CheckCatalog is defined (or if it cannot be resolved) - val parentScope = IScope::NULLSCOPE - - return MapBasedScope::createScope(parentScope, Iterables::filter(descriptions, Predicates::notNull)); - } - } - - def dispatch IScope scope(CheckCatalog context, EReference reference) { - if (reference == CheckPackage.Literals::CHECK_CATALOG__GRAMMAR) { - val reg = IResourceServiceProvider.Registry::INSTANCE - val descriptions = Collections2::transform(reg.extensionToFactoryMap.keySet, - [e | { - val dummyUri = URI::createURI("foo:/foo." + e) - try { - val g = reg.getResourceServiceProvider(dummyUri).get(typeof(IGrammarAccess)).grammar - return EObjectDescription::create(qualifiedNameConverter.toQualifiedName(g.name), g) - } catch (Exception ex) {} - }] - ) - // We look first in the workspace for a grammar and then in the registry for a registered grammar - val parentScope = MapBasedScope::createScope(IScope::NULLSCOPE, Iterables::filter(descriptions, Predicates::notNull)); - return parentScope; - //val grammarScope = new DelegatingScope(parentScope); - //grammarScope.setDelegate(super.getScope(context, reference)); - //return grammarScope; - } else if (reference == CheckPackage.Literals::XISSUE_EXPRESSION__CHECK) { - val descriptions = context.allChecks.map(c|EObjectDescription::create(checkQualifiedNameProvider.getFullyQualifiedName(c), c)) - return new SimpleScope(super.getScope(context, reference), descriptions) - } - } - - // default implementation will throw an illegal argument exception - def dispatch IScope scope(EObject context, EReference reference) { - return null - } - - def EClass classForJvmType(EObject context, JvmType jvmType) { - if (jvmType !== null && !jvmType.eIsProxy) { - val qualifiedName = jvmType.getQualifiedName(); - val qualifiedPackageName = qualifiedName.substring(0, qualifiedName.lastIndexOf(".")); - val packageName = qualifiedPackageName.substring(qualifiedPackageName.lastIndexOf(".") + 1); - val ePackage = getEPackage(context.eResource, packageName); - if (ePackage !== null) { - val eClassifier = (EcoreUtil::resolve(ePackage, context) as EPackage).getEClassifier(jvmType.simpleName) - if (eClassifier instanceof EClass) { - return eClassifier - } - } - } - return null - } - - def EPackage getEPackage(Resource context, String name) { - // not using for-each loop, as it could result in a ConcurrentModificationException when a resource is demand-loaded - val resources = context.resourceSet.resources - for (var i = 0; i < resources.size; i++) { - val resource = resources.get(i) - for (obj : resource.contents) { - if (obj instanceof EPackage && (obj as EPackage).name == name) - return obj as EPackage - } - } - val desc = globalScopeProvider.getScope(context, EcorePackage.Literals.EPACKAGE__ESUPER_PACKAGE, null).getSingleElement(QualifiedName.create(name)) - if (desc !== null) { - return desc.EObjectOrProxy as EPackage - } - val descs = descriptionsProvider.getResourceDescriptions(context).getExportedObjects(EcorePackage.Literals.EPACKAGE, QualifiedName.create(name), false).iterator - if (descs.hasNext) { - val pkg = EcoreUtil.resolve(descs.next.EObjectOrProxy, context) - // this filtering only appears to be necessary when executing the unit test BugAig830 in Jenkins (see https://jira.sys.net/browse/ACF-8758) - if (!pkg.eIsProxy) - return pkg as EPackage - } - for (nsUri : Sets.newHashSet(EPackage.Registry.INSTANCE.keySet)) { - val ePackage = EPackage.Registry.INSTANCE.getEPackage(nsUri) - if (ePackage.name == name) { - return ePackage - } - } - return null - } - - //todo: scoping for the check implementation (e.g. the parameters are not visible) - - //todo: scope the allowed imports! - - -} - diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/typing/CheckTypeComputer.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/typing/CheckTypeComputer.java new file mode 100644 index 0000000000..b23635d95c --- /dev/null +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/typing/CheckTypeComputer.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.typing; + +import com.avaloq.tools.ddk.check.check.FormalParameter; +import com.avaloq.tools.ddk.check.check.XGuardExpression; +import com.avaloq.tools.ddk.check.check.XIssueExpression; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.xbase.XExpression; +import org.eclipse.xtext.xbase.XListLiteral; +import org.eclipse.xtext.xbase.annotations.typesystem.XbaseWithAnnotationsTypeComputer; +import org.eclipse.xtext.xbase.typesystem.computation.ITypeComputationState; + +@SuppressWarnings({"checkstyle:MethodName"}) +public class CheckTypeComputer extends XbaseWithAnnotationsTypeComputer { + + @Override + public void computeTypes(final XExpression expression, final ITypeComputationState state) { + if (expression instanceof XIssueExpression) { + _computeTypes((XIssueExpression) expression, state); + } else if (expression instanceof XGuardExpression) { + _computeTypes((XGuardExpression) expression, state); + } else if (expression.eContainer() instanceof FormalParameter && expression instanceof XListLiteral && ((XListLiteral) expression).getElements().isEmpty()) { + super.computeTypes(expression, state.withExpectation(state.getReferenceOwner().toLightweightTypeReference(((FormalParameter) expression.eContainer()).getType()))); + } else { + super.computeTypes(expression, state); + } + } + + protected void _computeTypes(final XIssueExpression expression, final ITypeComputationState state) { + if (expression.getMarkerObject() != null) { + state.withExpectation(getTypeForName(EObject.class, state)).computeTypes(expression.getMarkerObject()); + } + if (expression.getMarkerIndex() != null) { + state.withExpectation(getTypeForName(Integer.class, state)).computeTypes(expression.getMarkerIndex()); + } + if (expression.getMessage() != null) { + state.withExpectation(getTypeForName(String.class, state)).computeTypes(expression.getMessage()); + } + for (XExpression p : expression.getMessageParameters()) { + state.withExpectation(getTypeForName(Object.class, state)).computeTypes(p); + } + for (XExpression d : expression.getIssueData()) { + state.withExpectation(getTypeForName(String.class, state)).computeTypes(d); + } + state.acceptActualType(getPrimitiveVoid(state)); + } + + protected void _computeTypes(final XGuardExpression expression, final ITypeComputationState state) { + state.withExpectation(getTypeForName(Boolean.class, state)).computeTypes(expression.getGuard()); + state.acceptActualType(getPrimitiveVoid(state)); + } +} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/typing/CheckTypeComputer.xtend b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/typing/CheckTypeComputer.xtend deleted file mode 100644 index 1b80a361f0..0000000000 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/typing/CheckTypeComputer.xtend +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.typing - -import org.eclipse.xtext.xbase.annotations.typesystem.XbaseWithAnnotationsTypeComputer -import org.eclipse.xtext.xbase.typesystem.computation.ITypeComputationState -import org.eclipse.xtext.xbase.XExpression -import com.avaloq.tools.ddk.check.check.XIssueExpression -import com.avaloq.tools.ddk.check.check.XGuardExpression -import org.eclipse.emf.ecore.EObject -import com.avaloq.tools.ddk.check.check.FormalParameter -import org.eclipse.xtext.xbase.XListLiteral - -class CheckTypeComputer extends XbaseWithAnnotationsTypeComputer { - - override computeTypes(XExpression expression, ITypeComputationState state) { - if (expression instanceof XIssueExpression) { - _computeTypes(expression, state); - } else if (expression instanceof XGuardExpression) { - _computeTypes(expression, state); - } else if (expression.eContainer instanceof FormalParameter && expression instanceof XListLiteral && (expression as XListLiteral).elements.empty) { - super.computeTypes(expression, state.withExpectation(state.referenceOwner.toLightweightTypeReference((expression.eContainer as FormalParameter).type))); - } else { - super.computeTypes(expression, state); - } - } - - protected def _computeTypes(XIssueExpression expression, ITypeComputationState state) { - if (expression.markerObject !== null) { - state.withExpectation(getTypeForName(typeof(EObject), state)).computeTypes(expression.markerObject); - } - if (expression.markerIndex !== null) { - state.withExpectation(getTypeForName(typeof(Integer), state)).computeTypes(expression.markerIndex); - } - if (expression.message !== null) { - state.withExpectation(getTypeForName(typeof(String), state)).computeTypes(expression.message); - } - for (p : expression.messageParameters) { - state.withExpectation(getTypeForName(typeof(Object), state)).computeTypes(p); - } - for (d : expression.issueData) { - state.withExpectation(getTypeForName(typeof(String), state)).computeTypes(d); - } - state.acceptActualType(getPrimitiveVoid (state)); - } - - protected def _computeTypes(XGuardExpression expression, ITypeComputationState state) { - state.withExpectation(getTypeForName(typeof(Boolean), state)).computeTypes(expression.guard); - state.acceptActualType(getPrimitiveVoid (state)); - } - -} diff --git a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/validation/ClasspathBasedChecks.java b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/validation/ClasspathBasedChecks.java index 38d069fd2e..ad7dc84e3a 100644 --- a/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/validation/ClasspathBasedChecks.java +++ b/com.avaloq.tools.ddk.check.core/src/com/avaloq/tools/ddk/check/validation/ClasspathBasedChecks.java @@ -54,13 +54,8 @@ public void checkFileNamingConventions(final CheckCatalog catalog) { Resource resource = catalog.eResource(); URI resourceURI = resource.getURI(); String packageName = catalog.getPackageName(); - StringBuilder classpathURIBuilder = new StringBuilder(ClasspathUriUtil.CLASSPATH_SCHEME); - classpathURIBuilder.append(":/"); - if (packageName != null) { - classpathURIBuilder.append(packageName.replace(DOT, SLASH)).append(SLASH); - } - classpathURIBuilder.append(resourceURI.lastSegment()); - URI classpathURI = URI.createURI(classpathURIBuilder.toString()); + String packagePath = packageName != null ? packageName.replace(DOT, SLASH) + SLASH : ""; + URI classpathURI = URI.createURI(ClasspathUriUtil.CLASSPATH_SCHEME + ":/" + packagePath + resourceURI.lastSegment()); URIConverter uriConverter = resource.getResourceSet().getURIConverter(); try { URI normalizedClasspathURI = uriConverter.normalize(classpathURI); diff --git a/com.avaloq.tools.ddk.check.core/xtend-gen/.gitignore b/com.avaloq.tools.ddk.check.core/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.check.ide/.classpath b/com.avaloq.tools.ddk.check.ide/.classpath index 5a4d66ff35..f7054419e4 100644 --- a/com.avaloq.tools.ddk.check.ide/.classpath +++ b/com.avaloq.tools.ddk.check.ide/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.check.ide/build.properties b/com.avaloq.tools.ddk.check.ide/build.properties index 3f5513de7b..2d6ac57da2 100644 --- a/com.avaloq.tools.ddk.check.ide/build.properties +++ b/com.avaloq.tools.ddk.check.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.check.ide/xtend-gen/.gitignore b/com.avaloq.tools.ddk.check.ide/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/.classpath b/com.avaloq.tools.ddk.check.test.runtime.tests/.classpath index 1b454394ec..405761e5aa 100644 --- a/com.avaloq.tools.ddk.check.test.runtime.tests/.classpath +++ b/com.avaloq.tools.ddk.check.test.runtime.tests/.classpath @@ -1,11 +1,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.check.test.runtime.tests/META-INF/MANIFEST.MF index fcde268cab..a436247fef 100644 --- a/com.avaloq.tools.ddk.check.test.runtime.tests/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.check.test.runtime.tests/META-INF/MANIFEST.MF @@ -15,7 +15,6 @@ Require-Bundle: com.avaloq.tools.ddk.check.runtime.core, org.eclipse.xtext.testing, org.eclipse.xtext.ui.testing, org.eclipse.ui.workbench;resolution:=optional, - org.eclipse.xtend.lib, org.eclipse.xtext.xbase.lib, junit-jupiter-api, junit-jupiter-engine, diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/build.properties b/com.avaloq.tools.ddk.check.test.runtime.tests/build.properties index 57a52eb790..2ae3772a83 100644 --- a/com.avaloq.tools.ddk.check.test.runtime.tests/build.properties +++ b/com.avaloq.tools.ddk.check.test.runtime.tests/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/,\ resource/ output.. = bin/ bin.includes = META-INF/,\ diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckConfigurationIsAppliedTest.xtend b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckConfigurationIsAppliedTest.java similarity index 52% rename from com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckConfigurationIsAppliedTest.xtend rename to com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckConfigurationIsAppliedTest.java index c27822ac24..e5d4289367 100644 --- a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckConfigurationIsAppliedTest.xtend +++ b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckConfigurationIsAppliedTest.java @@ -8,33 +8,37 @@ * Contributors: * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.check.test.runtime +package com.avaloq.tools.ddk.check.test.runtime; -import com.avaloq.tools.ddk.check.TestLanguageUiInjectorProvider -import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase -import com.avaloq.tools.ddk.check.testLanguage.Model -import com.avaloq.tools.ddk.check.testLanguage.TestLanguagePackage -import com.avaloq.tools.ddk.check.ui.internal.TestLanguageActivator -import com.avaloq.tools.ddk.check.validation.ExecutionEnvironmentIssueCodes -import com.google.common.collect.Lists -import com.google.inject.Inject -import java.util.List -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil -import org.junit.jupiter.api.Test -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.^extension.ExtendWith +import java.util.List; -@InjectWith(typeof(TestLanguageUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class CheckConfigurationIsAppliedTest extends AbstractCheckTestCase { +import com.avaloq.tools.ddk.check.TestLanguageUiInjectorProvider; +import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase; +import com.avaloq.tools.ddk.check.testLanguage.Model; +import com.avaloq.tools.ddk.check.testLanguage.TestLanguagePackage; +import com.avaloq.tools.ddk.check.ui.internal.TestLanguageActivator; +import com.avaloq.tools.ddk.check.validation.ExecutionEnvironmentIssueCodes; +import com.google.common.collect.Lists; +import com.google.inject.Inject; +import com.google.inject.Injector; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@InjectWith(TestLanguageUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckConfigurationIsAppliedTest extends AbstractCheckTestCase { @Inject - ValidationTestHelper helper + private ValidationTestHelper helper; - override getInjector() { - TestLanguageActivator::instance.getInjector(TestLanguageActivator::COM_AVALOQ_TOOLS_DDK_CHECK_TESTLANGUAGE) + @Override + public Injector getInjector() { + return TestLanguageActivator.getInstance().getInjector(TestLanguageActivator.COM_AVALOQ_TOOLS_DDK_CHECK_TESTLANGUAGE); } /* @@ -43,8 +47,8 @@ override getInjector() { * Eclipse has the Check runtime plugins installed, code will automatically be generated * for those resources. In order to avoid that the file extensions have been ommitted. */ - def List getRequiredSourceFileNames() { - Lists::newArrayList('.settings/com.avaloq.tools.ddk.checkcfg.core.prefs', 'Greetings') + public List getRequiredSourceFileNames() { + return Lists.newArrayList(".settings/com.avaloq.tools.ddk.checkcfg.core.prefs", "Greetings"); } /* @@ -52,13 +56,13 @@ def List getRequiredSourceFileNames() { * and applied: the severity is changed from ERROR to WARNING. */ @Test - def void testCheckConfigurationIsApplied() { + public void testCheckConfigurationIsApplied() throws Exception { // sources are copied into the project and then built by the Xtext builder - addSourcesToWorkspace(typeof(CheckConfigurationIsAppliedTest), requiredSourceFileNames) + addSourcesToWorkspace(CheckConfigurationIsAppliedTest.class, getRequiredSourceFileNames()); // wait for build to finish, otherwise included catalog may not be resolvable - IResourcesSetupUtil::waitForBuild - val model = getModel("Greetings") as Model - helper.assertWarning(model.greetings.get(0), TestLanguagePackage$Literals::GREETING, ExecutionEnvironmentIssueCodes::FRANZNAME) + IResourcesSetupUtil.waitForBuild(); + final Model model = (Model) getModel("Greetings"); + helper.assertWarning(model.getGreetings().get(0), TestLanguagePackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.FRANZNAME); } } diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckExecutionEnvironmentProjectTest.java b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckExecutionEnvironmentProjectTest.java new file mode 100644 index 0000000000..63a132aad2 --- /dev/null +++ b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckExecutionEnvironmentProjectTest.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.test.runtime; + +import com.avaloq.tools.ddk.check.TestLanguageUiInjectorProvider; +import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase; +import com.avaloq.tools.ddk.check.testLanguage.Model; +import com.avaloq.tools.ddk.check.testLanguage.TestLanguagePackage; +import com.avaloq.tools.ddk.check.ui.internal.TestLanguageActivator; +import com.avaloq.tools.ddk.check.validation.ExecutionEnvironmentIssueCodes; +import com.avaloq.tools.ddk.check.validation.LibraryChecksIssueCodes; +import com.google.inject.Inject; +import com.google.inject.Injector; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@InjectWith(TestLanguageUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckExecutionEnvironmentProjectTest extends AbstractCheckTestCase { + + @Inject + private ValidationTestHelper helper; + + @Inject + private ParseHelper parser; + + @Override + protected Injector getInjector() { + return TestLanguageActivator.getInstance().getInjector(TestLanguageActivator.COM_AVALOQ_TOOLS_DDK_CHECK_TESTLANGUAGE); + } + + @Test + public void testFranz() throws Exception { + final Model model = parser.parse("Hello Franz!"); + helper.assertError(model, TestLanguagePackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.FRANZNAME); + } + + @Test + public void testFrans() throws Exception { + final Model model = parser.parse("Hello Frans!"); + helper.assertNoError(model, ExecutionEnvironmentIssueCodes.FRANZNAME); + helper.assertNoError(model, ExecutionEnvironmentIssueCodes.NAMELENGTH); + } + + @Test + public void testGreetingNameIssue() throws Exception { + final Model model = parser.parse("Hello GreetingNameTooLong!"); + helper.assertError(model, TestLanguagePackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.NAMELENGTH); + } + + /* + * Tests that both validations emerging from the Java validator as well as such emerging from the check based + * validator appear. (Fixed by CheckCompositeEValidator). + */ + @Test + //@BugTest("AIG-709") // this plugin should be ACF independent + public void testBugAig709() throws Exception { + final Model model = parser.parse("Hello GreetingNameTooLong!"); + helper.assertError(model, TestLanguagePackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.NAMELENGTH); + helper.assertWarning(model, TestLanguagePackage.Literals.GREETING, IssueCodesConstants.GREETING_NAME_PREFIX); + } + + @Test + public void testLibraryInjection() throws Exception { + final Model model = parser.parse("Hello Peter!"); + helper.assertWarning(model, TestLanguagePackage.Literals.GREETING, LibraryChecksIssueCodes.CHECK_CATALOG_IS_ACTIVE); + helper.assertNoError(model, LibraryChecksIssueCodes.CACHE_DOESNT_WORK); + helper.assertNoError(model, LibraryChecksIssueCodes.CACHE_INJECTION_FAILED); + helper.assertNoError(model, LibraryChecksIssueCodes.FORMAL_PARAMETERS); + } +} diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckExecutionEnvironmentProjectTest.xtend b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckExecutionEnvironmentProjectTest.xtend deleted file mode 100644 index bd0bff2858..0000000000 --- a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/CheckExecutionEnvironmentProjectTest.xtend +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.test.runtime - -import com.avaloq.tools.ddk.check.TestLanguageUiInjectorProvider -import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase -import com.avaloq.tools.ddk.check.testLanguage.Model -import com.avaloq.tools.ddk.check.testLanguage.TestLanguagePackage -import com.avaloq.tools.ddk.check.ui.internal.TestLanguageActivator -import com.avaloq.tools.ddk.check.validation.ExecutionEnvironmentIssueCodes -import com.avaloq.tools.ddk.check.validation.LibraryChecksIssueCodes -import com.google.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.util.ParseHelper -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test - - -@InjectWith(typeof(TestLanguageUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class CheckExecutionEnvironmentProjectTest extends AbstractCheckTestCase { - - @Inject - ValidationTestHelper helper - - @Inject - ParseHelper parser - - override getInjector() { - TestLanguageActivator::instance.getInjector(TestLanguageActivator::COM_AVALOQ_TOOLS_DDK_CHECK_TESTLANGUAGE) - } - - @Test - def void testFranz() { - val model = parser.parse("Hello Franz!") - helper.assertError(model, TestLanguagePackage.Literals::GREETING, ExecutionEnvironmentIssueCodes::FRANZNAME) - } - - @Test - def void testFrans() { - val model = parser.parse("Hello Frans!") - helper.assertNoError(model, ExecutionEnvironmentIssueCodes::FRANZNAME) - helper.assertNoError(model, ExecutionEnvironmentIssueCodes::NAMELENGTH) - } - - @Test - def void testGreetingNameIssue() { - val model = parser.parse("Hello GreetingNameTooLong!") - helper.assertError(model, TestLanguagePackage.Literals::GREETING, ExecutionEnvironmentIssueCodes::NAMELENGTH) - } - - /* - * Tests that both validations emerging from the Java validator as well as such emerging from the check based - * validator appear. (Fixed by CheckCompositeEValidator). - */ - @Test - //@BugTest("AIG-709") // this plugin should be ACF independent - def void testBugAig709() { - val model = parser.parse("Hello GreetingNameTooLong!") - helper.assertError(model, TestLanguagePackage.Literals::GREETING, ExecutionEnvironmentIssueCodes::NAMELENGTH) - helper.assertWarning(model, TestLanguagePackage.Literals::GREETING, IssueCodesConstants::GREETING_NAME_PREFIX) - } - - @Test - def void testLibraryInjection() { - val model = parser.parse("Hello Peter!"); - helper.assertWarning(model, TestLanguagePackage.Literals::GREETING, LibraryChecksIssueCodes::CHECK_CATALOG_IS_ACTIVE); - helper.assertNoError(model, LibraryChecksIssueCodes::CACHE_DOESNT_WORK); - helper.assertNoError(model, LibraryChecksIssueCodes::CACHE_INJECTION_FAILED); - helper.assertNoError(model, LibraryChecksIssueCodes::FORMAL_PARAMETERS); - } -} diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/label/IssueLabelTest.java b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/label/IssueLabelTest.java new file mode 100644 index 0000000000..aabb37c347 --- /dev/null +++ b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/label/IssueLabelTest.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.check.test.runtime.label; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Map; + +import com.avaloq.tools.ddk.check.runtime.label.ICheckRuleLabelProvider; +import com.avaloq.tools.ddk.check.validation.LibraryChecksIssueCodes; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import org.junit.jupiter.api.Test; + +/** + * End-to-end test for getting Check labels. + */ +@SuppressWarnings("nls") +public class IssueLabelTest { + + /** + * End-to-end test for getting Check labels. + */ + @Test + public void testGetLabel() { + + // ARRANGE + final ICheckRuleLabelProvider checkRuleLabelProvider = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + } + }).getInstance(ICheckRuleLabelProvider.class); + + final Map expectedMap = Map.of( + // @Format-Off + LibraryChecksIssueCodes.CACHE_DOESNT_WORK, "Cache doesn't work", + LibraryChecksIssueCodes.CACHE_INJECTION_FAILED, "Cache injection failed", + LibraryChecksIssueCodes.CHECK_CATALOG_IS_ACTIVE, "Check catalog is active", + LibraryChecksIssueCodes.FORMAL_PARAMETERS, "Formal Parameters" + // @Format-On + ); + + for (final Map.Entry entry : expectedMap.entrySet()) { + // ACT + final String label = checkRuleLabelProvider.getLabel(entry.getKey()); + + // ASSERT + assertNotNull(label, "Label should be returned for key " + entry.getKey()); + assertEquals(entry.getValue(), label, "Correct label should be returned for key " + entry.getKey()); + } + } +} diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/label/IssueLabelTest.xtend b/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/label/IssueLabelTest.xtend deleted file mode 100644 index 5471340a03..0000000000 --- a/com.avaloq.tools.ddk.check.test.runtime.tests/src/com/avaloq/tools/ddk/check/test/runtime/label/IssueLabelTest.xtend +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.check.test.runtime.label - -import com.avaloq.tools.ddk.check.runtime.label.ICheckRuleLabelProvider -import com.avaloq.tools.ddk.check.validation.LibraryChecksIssueCodes -import com.google.inject.AbstractModule -import com.google.inject.Guice -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * End-to-end test for getting Check labels. - */ -class IssueLabelTest { - - /** - * End-to-end test for getting Check labels. - */ - @Test - def testGetLabel() { - - // ARRANGE - val checkRuleLabelProvider = Guice.createInjector(new AbstractModule() { - protected override configure() {} - }).getInstance(ICheckRuleLabelProvider); - - val expectedMap = #{ - // @Format-Off - LibraryChecksIssueCodes.CACHE_DOESNT_WORK -> "Cache doesn't work", - LibraryChecksIssueCodes.CACHE_INJECTION_FAILED -> "Cache injection failed", - LibraryChecksIssueCodes.CHECK_CATALOG_IS_ACTIVE -> "Check catalog is active", - LibraryChecksIssueCodes.FORMAL_PARAMETERS -> "Formal Parameters" - // @Format-On - } - - for (entry : expectedMap.entrySet) { - // ACT - val label = checkRuleLabelProvider.getLabel(entry.key); - - // ASSERT - assertNotNull(label, "Label should be returned for key " + entry.key); - assertEquals( entry.value, label, "Correct label should be returned for key " + entry.key); - } - } -} diff --git a/com.avaloq.tools.ddk.check.test.runtime.tests/xtend-gen/.gitignore b/com.avaloq.tools.ddk.check.test.runtime.tests/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.check.test.runtime/.classpath b/com.avaloq.tools.ddk.check.test.runtime/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.check.test.runtime/.classpath +++ b/com.avaloq.tools.ddk.check.test.runtime/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.check.test.runtime/build.properties b/com.avaloq.tools.ddk.check.test.runtime/build.properties index 31255ed05b..e10dcceb6a 100644 --- a/com.avaloq.tools.ddk.check.test.runtime/build.properties +++ b/com.avaloq.tools.ddk.check.test.runtime/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ .,\ plugin.xml \ No newline at end of file diff --git a/com.avaloq.tools.ddk.check.test.runtime/src/com/avaloq/tools/ddk/check/generator/TestLanguageGenerator.xtend b/com.avaloq.tools.ddk.check.test.runtime/src/com/avaloq/tools/ddk/check/generator/TestLanguageGenerator.java similarity index 57% rename from com.avaloq.tools.ddk.check.test.runtime/src/com/avaloq/tools/ddk/check/generator/TestLanguageGenerator.xtend rename to com.avaloq.tools.ddk.check.test.runtime/src/com/avaloq/tools/ddk/check/generator/TestLanguageGenerator.java index 99d7b4e6a6..c022af5d6f 100644 --- a/com.avaloq.tools.ddk.check.test.runtime/src/com/avaloq/tools/ddk/check/generator/TestLanguageGenerator.xtend +++ b/com.avaloq.tools.ddk.check.test.runtime/src/com/avaloq/tools/ddk/check/generator/TestLanguageGenerator.java @@ -8,15 +8,16 @@ * Contributors: * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.check.generator +package com.avaloq.tools.ddk.check.generator; -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.IGenerator -import org.eclipse.xtext.generator.IFileSystemAccess +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.generator.IFileSystemAccess; +import org.eclipse.xtext.generator.IGenerator; -class TestLanguageGenerator implements IGenerator { +public class TestLanguageGenerator implements IGenerator { - override void doGenerate(Resource resource, IFileSystemAccess fsa) { - //TODO implment me - } + @Override + public void doGenerate(final Resource resource, final IFileSystemAccess fsa) { + // TODO implement me + } } diff --git a/com.avaloq.tools.ddk.check.test.runtime/xtend-gen/.gitignore b/com.avaloq.tools.ddk.check.test.runtime/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.check.ui.test/.classpath b/com.avaloq.tools.ddk.check.ui.test/.classpath index aa502e196b..1ad2437b6e 100644 --- a/com.avaloq.tools.ddk.check.ui.test/.classpath +++ b/com.avaloq.tools.ddk.check.ui.test/.classpath @@ -1,11 +1,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.check.ui.test/build.properties b/com.avaloq.tools.ddk.check.ui.test/build.properties index d6c49e5cf1..6b7927735f 100644 --- a/com.avaloq.tools.ddk.check.ui.test/build.properties +++ b/com.avaloq.tools.ddk.check.ui.test/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.check.ui.test/src/com/avaloq/tools/ddk/check/ui/test/quickfix/CheckQuickfixTest.java b/com.avaloq.tools.ddk.check.ui.test/src/com/avaloq/tools/ddk/check/ui/test/quickfix/CheckQuickfixTest.java new file mode 100644 index 0000000000..f0940dd04a --- /dev/null +++ b/com.avaloq.tools.ddk.check.ui.test/src/com/avaloq/tools/ddk/check/ui/test/quickfix/CheckQuickfixTest.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.check.ui.test.quickfix; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; +import org.eclipse.swtbot.swt.finder.widgets.TimeoutException; +import org.eclipse.xtext.diagnostics.Diagnostic; +import org.eclipse.xtext.validation.Issue; +import org.junit.jupiter.api.Test; + +import com.avaloq.tools.ddk.check.ui.quickfix.Messages; +import com.avaloq.tools.ddk.check.validation.IssueCodes; +import com.avaloq.tools.ddk.test.core.Retry; +import com.avaloq.tools.ddk.test.core.jupiter.BugTest; +import com.avaloq.tools.ddk.test.ui.swtbot.SwtWorkbenchBot; +import com.avaloq.tools.ddk.test.ui.swtbot.condition.WaitForEquals; +import com.avaloq.tools.ddk.test.ui.swtbot.util.ProblemsViewTestUtil; +import com.avaloq.tools.ddk.xtext.test.TestSource; +import com.avaloq.tools.ddk.xtext.test.XtextTestSource; + + +/** + * Test quickfixes for Check files. + */ +@SuppressWarnings("nls") +// CHECKSTYLE:CONSTANTS-OFF +public class CheckQuickfixTest extends AbstractCheckQuickfixTest { + + private static final String PACKAGE_NAME = "com.avaloq.test"; + + private final SwtWorkbenchBot bot = new SwtWorkbenchBot(); + private boolean oldAutoBuildState; + + public String getTestSourceFileName(final String catalogName) { + return PACKAGE_NAME.replace(".", "/") + '/' + catalogName + '.' + getXtextTestUtil().getFileExtension(); + } + + @Override + protected String getTestSourceFileName() { + return getTestSourceFileName(getTestSourceModelName()); + } + + @Override + protected void registerRequiredSources() { + } + + @Override + protected XtextTestSource getTestSource() { + return null; + } + + @Override + protected void beforeEachTest() { + super.beforeEachTest(); + oldAutoBuildState = getTestProjectManager().setAutobuild(true); + cleanUp(); + } + + @Override + protected void afterEachTest() { + getTestProjectManager().setAutobuild(oldAutoBuildState); + cleanUp(); + super.afterEachTest(); + } + + /** + * Close all shells and editors and remove all sources. + */ + private void cleanUp() { + bot.closeAllShells(); + bot.closeAllEditors(); + Collection testSources = getTestProjectManager().getTestSources(); + for (final TestSource testSource : testSources) { + getTestProjectManager().removeTestSource(testSource); + } + } + + @Test + @BugTest(value = "DSL-244") + public void testImportFix() { + String source = String.format(""" + package %s + + catalog %s for grammar org.eclipse.xtext.Xtext + { + /** Missing import test */ + warning TestWarning "Test Warning" + message "This is a Test Warning" { + for AbstractRule c { + issue + } + } + } + """, PACKAGE_NAME, getTestSourceModelName()); + createTestSource(getTestSourceFileName(), source); + openEditor(getTestSourceFileName()); + final String quickfixLabel = "Import 'AbstractRule' (org.eclipse.xtext)"; + final List beforeIssues = getXtextTestUtil().getIssues(getDocument()); + assertHasQuickFix(Diagnostic.LINKING_DIAGNOSTIC, quickfixLabel); + assertQuickFixSuccessful(Diagnostic.LINKING_DIAGNOSTIC, quickfixLabel); + final List afterIssues = getXtextTestUtil().getIssues(getDocument()); + assertTrue(afterIssues.size() < beforeIssues.size()); + } + + /** + * Test the Add ID quickfix. + */ + @Test + public void testAddID() { + // ARRANGE + final String sourceContent = String.format(""" + package %s + + catalog %s + for grammar org.eclipse.xtext.Xtext { + + warning "Test Warning" + message "This is a Test Warning" { + } + } + """, PACKAGE_NAME, getTestSourceModelName()); + + final String expectedContent = String.format(""" + package %s + + catalog %s + for grammar org.eclipse.xtext.Xtext { + + warning TestWarning "Test Warning" + message "This is a Test Warning" { + } + } + """, PACKAGE_NAME, getTestSourceModelName()); + + // ACT and ASSERT + assertQuickFixExistsAndSuccessfulInCustomerSource(IssueCodes.MISSING_ID_ON_CHECK, + Messages.CheckQuickfixProvider_ADD_ID_LABEL, getTestSourceFileName(), sourceContent, expectedContent); + } + + /** + * Test bulk-applying a quickfix. + * Tolerate up to ten timeouts, because occasionally new markers don't appear or not all markers are fixed; + * the problems appears to be in Eclipse. + */ + @Test + @Retry(10) + public void testBulkApplyingQuickfix() { + // ARRANGE + final List catalogNames = List.of(getTestSourceModelName(), getTestSourceModelName() + "2"); + final List checkLabels = List.of("Check with no explicit ID", "Another check with no explicit ID"); + final int expectedMarkers = catalogNames.size() * checkLabels.size(); + + // Show all error markers + ProblemsViewTestUtil.showProblemsView(bot); + ProblemsViewTestUtil.showAllErrors(bot); + ProblemsViewTestUtil.groupByNone(bot); + + // Add catalogs containing multiple instances of the same quickfixable marker + for (final String catalogName : catalogNames) { + StringBuilder builder = new StringBuilder(512); + builder.append("package "); + builder.append(PACKAGE_NAME); + builder.append('\n'); + builder.append('\n'); + builder.append("catalog "); + builder.append(catalogName); + builder.append('\n'); + builder.append("for grammar org.eclipse.xtext.Xtext {\n"); + for (final String checkLabel : checkLabels) { + builder.append('\n'); + builder.append(" live error \""); + builder.append(checkLabel); + builder.append("\"\n"); + builder.append(" message \""); + builder.append(checkLabel); + builder.append("\" {\n"); + builder.append(" }\n"); + } + builder.append("}\n"); + createTestSource(getTestSourceFileName(catalogName), builder.toString()); + } + + // Build the catalogs, and wait for the expected markers to appear + getTestProjectManager().build(); + final SWTBotTree markersTreeBot = ProblemsViewTestUtil.getMarkersTree(bot); + bot.waitUntil(new WaitForEquals<>("Not all expected markers appeared.", () -> expectedMarkers, () -> markersTreeBot.getAllItems().length)); + + // ACT + // Disable autobuilding, to avoid losing focus while selecting markers + getTestProjectManager().setAutobuild(false); + getTestProjectManager().build(); + + // Bulk-apply quickfixes on all markers, ensuring that all markers remain selected + ProblemsViewTestUtil.bulkApplyQuickfix(bot, Messages.CheckQuickfixProvider_ADD_ID_LABEL, markersTreeBot.getAllItems()); + bot.waitUntil(new WaitForEquals<>("Not all markers are still selected.", () -> expectedMarkers, () -> markersTreeBot.selectionCount())); + + // Save all modified files and build the catalogs + bot.editors().forEach(editor -> editor.save()); + getTestProjectManager().setAutobuild(true); + getTestProjectManager().build(); + + // ASSERT + // Check that all markers are fixed + try { + bot.waitUntil(new WaitForEquals<>("Some markers were not quickfixed.", () -> 0, () -> markersTreeBot.getAllItems().length)); + } catch (TimeoutException exception) { + fail(exception.getMessage()); + } + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.check.ui.test/src/com/avaloq/tools/ddk/check/ui/test/quickfix/CheckQuickfixTest.xtend b/com.avaloq.tools.ddk.check.ui.test/src/com/avaloq/tools/ddk/check/ui/test/quickfix/CheckQuickfixTest.xtend deleted file mode 100644 index af5a67e390..0000000000 --- a/com.avaloq.tools.ddk.check.ui.test/src/com/avaloq/tools/ddk/check/ui/test/quickfix/CheckQuickfixTest.xtend +++ /dev/null @@ -1,200 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.check.ui.test.quickfix - -import com.avaloq.tools.ddk.check.ui.quickfix.Messages -import com.avaloq.tools.ddk.check.validation.IssueCodes -import com.avaloq.tools.ddk.test.core.jupiter.BugTest -import com.avaloq.tools.ddk.test.core.Retry -import com.avaloq.tools.ddk.test.ui.swtbot.SwtWorkbenchBot -import com.avaloq.tools.ddk.test.ui.swtbot.condition.WaitForEquals -import com.avaloq.tools.ddk.test.ui.swtbot.util.ProblemsViewTestUtil -import org.eclipse.swtbot.swt.finder.widgets.TimeoutException -import org.eclipse.xtext.diagnostics.Diagnostic -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertTrue -import static org.junit.jupiter.api.Assertions.fail - -/** - * Test quickfixes for Check files. - */ -class CheckQuickfixTest extends AbstractCheckQuickfixTest { - - static val PACKAGE_NAME = "com.avaloq.test" - - val SwtWorkbenchBot bot = new SwtWorkbenchBot - var boolean oldAutoBuildState - - def String getTestSourceFileName(String catalogName) { - return '''«PACKAGE_NAME.replace(".", "/")»/«catalogName».«xtextTestUtil.getFileExtension»''' - } - - override protected getTestSourceFileName() { - return getTestSourceFileName(testSourceModelName) - } - - override protected registerRequiredSources() { - } - - override protected getTestSource() { - return null - } - - override protected beforeEachTest() { - super.beforeEachTest - oldAutoBuildState = testProjectManager.setAutobuild(true) - cleanUp - } - - override protected afterEachTest() { - testProjectManager.autobuild = oldAutoBuildState - cleanUp - super.afterEachTest - } - - /** - * Close all shells and editors and remove all sources. - */ - def private void cleanUp() { - bot.closeAllShells - bot.closeAllEditors - for (testSource : testProjectManager.testSources) { - testProjectManager.removeTestSource(testSource) - } - } - - @Test - @BugTest(value="DSL-244") - def testImportFix() { - createTestSource(testSourceFileName, ''' - package «PACKAGE_NAME» - - catalog «testSourceModelName» for grammar org.eclipse.xtext.Xtext - { - /** Missing import test */ - warning TestWarning "Test Warning" - message "This is a Test Warning" { - for AbstractRule c { - issue - } - } - } - ''') - openEditor(testSourceFileName) - val quickfixLabel = "Import 'AbstractRule' (org.eclipse.xtext)" - val beforeIssues = getXtextTestUtil().getIssues(getDocument()); - assertHasQuickFix(Diagnostic::LINKING_DIAGNOSTIC, quickfixLabel); - assertQuickFixSuccessful(Diagnostic::LINKING_DIAGNOSTIC, quickfixLabel); - val afterIssues = getXtextTestUtil().getIssues(getDocument()); - assertTrue(afterIssues.size < beforeIssues.size); - } - - /** - * Test the Add ID quickfix. - */ - @Test - def testAddID() { - - // ARRANGE - val sourceContent = ''' - package «PACKAGE_NAME» - - catalog «testSourceModelName» - for grammar org.eclipse.xtext.Xtext { - - warning "Test Warning" - message "This is a Test Warning" { - } - } - ''' - val expectedContent = ''' - package «PACKAGE_NAME» - - catalog «testSourceModelName» - for grammar org.eclipse.xtext.Xtext { - - warning TestWarning "Test Warning" - message "This is a Test Warning" { - } - } - ''' - - // ACT and ASSERT - assertQuickFixExistsAndSuccessfulInCustomerSource(IssueCodes::MISSING_ID_ON_CHECK, - Messages.CheckQuickfixProvider_ADD_ID_LABEL, testSourceFileName, sourceContent, expectedContent) - } - - /** - * Test bulk-applying a quickfix. - * Tolerate up to ten timeouts, because occasionally new markers don't appear or not all markers are fixed; - * the problems appears to be in Eclipse. - */ - @Test - @Retry(10) - def void testBulkApplyingQuickfix() { - - // ARRANGE - val catalogNames = #[testSourceModelName, testSourceModelName + "2"] - val checkLabels = #["Check with no explicit ID", "Another check with no explicit ID"] - val expectedMarkers = catalogNames.length * checkLabels.length - - // Show all error markers - ProblemsViewTestUtil.showProblemsView(bot) - ProblemsViewTestUtil.showAllErrors(bot) - ProblemsViewTestUtil.groupByNone(bot) - - // Add catalogs containing multiple instances of the same quickfixable marker - for (catalogName : catalogNames) { - createTestSource(getTestSourceFileName(catalogName), ''' - package «PACKAGE_NAME» - - catalog «catalogName» - for grammar org.eclipse.xtext.Xtext { - «FOR checkLabel : checkLabels» - - live error "«checkLabel»" - message "«checkLabel»" { - } - «ENDFOR» - } - ''') - } - - // Build the catalogs, and wait for the expected markers to appear - testProjectManager.build - val markersTreeBot = ProblemsViewTestUtil.getMarkersTree(bot) - bot.waitUntil(new WaitForEquals("Not all expected markers appeared.", [expectedMarkers], [markersTreeBot.allItems.length])) - - // ACT - // Disable autobuilding, to avoid losing focus while selecting markers - testProjectManager.autobuild = false - testProjectManager.build - - // Bulk-apply quickfixes on all markers, ensuring that all markers remain selected - ProblemsViewTestUtil.bulkApplyQuickfix(bot, Messages.CheckQuickfixProvider_ADD_ID_LABEL, markersTreeBot.allItems) - bot.waitUntil(new WaitForEquals("Not all markers are still selected.", [expectedMarkers], [markersTreeBot.selectionCount])) - - // Save all modified files and build the catalogs - bot.editors.forEach[save] - testProjectManager.autobuild = true - testProjectManager.build - - // ASSERT - // Check that all markers are fixed - try { - bot.waitUntil(new WaitForEquals("Some markers were not quickfixed.", [0], [markersTreeBot.allItems.length])) - } catch (TimeoutException exception) { - fail(exception.message) - } - } - -} - diff --git a/com.avaloq.tools.ddk.check.ui.test/xtend-gen/.gitignore b/com.avaloq.tools.ddk.check.ui.test/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.check.ui/.classpath b/com.avaloq.tools.ddk.check.ui/.classpath index 5a4d66ff35..f7054419e4 100644 --- a/com.avaloq.tools.ddk.check.ui/.classpath +++ b/com.avaloq.tools.ddk.check.ui/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.check.ui/build.properties b/com.avaloq.tools.ddk.check.ui/build.properties index 45000ac928..08ea14410f 100644 --- a/com.avaloq.tools.ddk.check.ui/build.properties +++ b/com.avaloq.tools.ddk.check.ui/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ icons/,\ .,\ diff --git a/com.avaloq.tools.ddk.check.ui/xtend-gen/.gitignore b/com.avaloq.tools.ddk.check.ui/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/.classpath b/com.avaloq.tools.ddk.checkcfg.core.test/.classpath index fa768afab7..5540ec29d4 100644 --- a/com.avaloq.tools.ddk.checkcfg.core.test/.classpath +++ b/com.avaloq.tools.ddk.checkcfg.core.test/.classpath @@ -1,11 +1,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.checkcfg.core.test/META-INF/MANIFEST.MF index 9ef1ac0ec0..c9b412d8e0 100644 --- a/com.avaloq.tools.ddk.checkcfg.core.test/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.checkcfg.core.test/META-INF/MANIFEST.MF @@ -16,7 +16,6 @@ Require-Bundle: com.avaloq.tools.ddk.test.core, org.eclipse.ui, org.eclipse.ui.ide, org.eclipse.core.runtime, - org.eclipse.xtend.lib, org.eclipse.xtext.ui.testing, org.eclipse.xtext.xbase.lib, org.eclipse.ui.workbench;resolution:=optional, diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/build.properties b/com.avaloq.tools.ddk.checkcfg.core.test/build.properties index d8e2f0e92e..34d2e4d2da 100644 --- a/com.avaloq.tools.ddk.checkcfg.core.test/build.properties +++ b/com.avaloq.tools.ddk.checkcfg.core.test/build.properties @@ -1,5 +1,4 @@ -source.. = src/,\ - xtend-gen/ +source.. = src/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/contentassist/CheckCfgContentAssistTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/contentassist/CheckCfgContentAssistTest.java new file mode 100644 index 0000000000..0954fc269f --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/contentassist/CheckCfgContentAssistTest.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.checkcfg.contentassist; + +import com.avaloq.tools.ddk.checkcfg.CheckCfgUiInjectorProvider; +import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil; +import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithExpectedValues; +import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithOutExpectedValues; +import com.avaloq.tools.ddk.test.core.jupiter.BugTest; +import com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractAcfContentAssistTest; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTestUtil; +import com.google.common.collect.Lists; +import java.util.List; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE; +import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXTENSION_POINT; + +@ExtendWith(InjectionExtension.class) +@InjectWith(CheckCfgUiInjectorProvider.class) +// CHECKSTYLE:CONSTANTS-OFF +@SuppressWarnings("nls") +public class CheckCfgContentAssistTest extends AbstractAcfContentAssistTest { + + @Override + protected AbstractXtextTestUtil getXtextTestUtil() { + return CheckCfgTestUtil.getInstance(); + } + + @Override + protected List getRequiredSourceFileNames() { + return Lists.newArrayListWithCapacity(0); + } + + @Override + protected void beforeAllTests() { + ExtensionRegistryMock.mockExecutableExtension(ExtensionRegistryMock.mockConfigurationElement(PROPERTY_EXTENSION_POINT), PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithExpectedValues.INSTANCE); + ExtensionRegistryMock.mockExecutableExtension(ExtensionRegistryMock.mockConfigurationElement(PROPERTY_EXTENSION_POINT), PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithOutExpectedValues.INSTANCE); + super.beforeAllTests(); + } + + @Override + protected void afterAllTests() { + super.afterAllTests(); + ExtensionRegistryMock.unMock(PROPERTY_EXTENSION_POINT); + } + + @Test + public void testConfiguredParameterProposals() { + final String source = String.format(""" + check configuration Test { + catalog TestChecks { + default Test ( + %s = %s"banana" + ) + } + } + """, TestPropertySpecificationWithExpectedValues.INSTANCE.getName(), expected(TestPropertySpecificationWithExpectedValues.INSTANCE.getExpectedValues())); + assertKernelSourceProposals("ConfiguredParameterProposals.checkcfg", source); + } + + @BugTest(value = "DSL-1811", unresolved = true) + public void testNoTypeMismatchedParameterValueProposals() { + final String source = String.format(""" + check configuration Test { + catalog TestChecks { + default Test ( + %s = %s"banana" + ) + } + } + """, TestPropertySpecificationWithExpectedValues.INSTANCE.getName(), expectedExactly(TestPropertySpecificationWithExpectedValues.INSTANCE.getExpectedValues())); + assertKernelSourceProposals("NoTypeMismatchedParameterValueProposals.checkcfg", source); + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/contentassist/CheckCfgContentAssistTest.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/contentassist/CheckCfgContentAssistTest.xtend deleted file mode 100644 index c94d565e21..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/contentassist/CheckCfgContentAssistTest.xtend +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.checkcfg.contentassist - -import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithExpectedValues -import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithOutExpectedValues -import com.google.common.collect.Lists - -import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE -import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXTENSION_POINT - -import static extension com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock.mockConfigurationElement -import static extension com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock.mockExecutableExtension -import static extension com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock.unMock -import com.avaloq.tools.ddk.test.core.jupiter.BugTest -import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractAcfContentAssistTest -import org.junit.jupiter.api.Test -import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import com.avaloq.tools.ddk.checkcfg.CheckCfgUiInjectorProvider -import org.eclipse.xtext.testing.InjectWith - -@ExtendWith(InjectionExtension) -@InjectWith(CheckCfgUiInjectorProvider) -class CheckCfgContentAssistTest extends AbstractAcfContentAssistTest { - - override protected getXtextTestUtil() { - return CheckCfgTestUtil.instance - } - - override protected getRequiredSourceFileNames() { - Lists.newArrayListWithCapacity(0) - } - - override protected beforeAllTests() { - PROPERTY_EXTENSION_POINT.mockConfigurationElement.mockExecutableExtension(PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithExpectedValues.INSTANCE) - PROPERTY_EXTENSION_POINT.mockConfigurationElement.mockExecutableExtension(PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithOutExpectedValues.INSTANCE) - super.beforeAllTests() - } - - override protected afterAllTests() { - super.afterAllTests() - PROPERTY_EXTENSION_POINT.unMock - } - - @Test - def testConfiguredParameterProposals() { - assertKernelSourceProposals("ConfiguredParameterProposals.checkcfg", ''' - check configuration Test { - catalog TestChecks { - default Test ( - «TestPropertySpecificationWithExpectedValues.INSTANCE.name» = «expected(TestPropertySpecificationWithExpectedValues.INSTANCE.expectedValues)»"banana" - ) - } - } - ''') - } - - @BugTest(value="DSL-1811", unresolved=true) - def testNoTypeMismatchedParameterValueProposals() { - assertKernelSourceProposals("NoTypeMismatchedParameterValueProposals.checkcfg", ''' - check configuration Test { - catalog TestChecks { - default Test ( - «TestPropertySpecificationWithExpectedValues.INSTANCE.name» = «expectedExactly(TestPropertySpecificationWithExpectedValues.INSTANCE.expectedValues)»"banana" - ) - } - } - ''') - } - - - -} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/scoping/CheckCfgScopeProviderTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/scoping/CheckCfgScopeProviderTest.java new file mode 100644 index 0000000000..7abe3a6756 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/scoping/CheckCfgScopeProviderTest.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.checkcfg.scoping; + +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckcfgPackage; +import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil; +import com.avaloq.tools.ddk.test.core.jupiter.BugTest; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractScopingTest; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTestUtil; +import java.util.List; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.scoping.IScopeProvider; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +@SuppressWarnings("nls") +public final class CheckCfgScopeProviderTest extends AbstractScopingTest { + + private final IScopeProvider scopeProvider = getScopeProvider(); + + /** {@inheritDoc} */ + @Override + protected AbstractXtextTestUtil getXtextTestUtil() { + return CheckCfgTestUtil.getInstance(); + } + + /** {@inheritDoc} */ + @Override + protected void registerRequiredSources() { + } + + /** + * Regression test for DSL-1498 Incorrect Catalog Name inserted by Content Assist + *

+ * All catalogs supplied to Context Assist should be in the correct fully-qualified package. + *

+ */ + @BugTest(value = "DSL-1498") + @SuppressWarnings("PMD.SignatureDeclareThrowsException") // test method + public void testCatalogsAreInCorrectPackage() throws Exception { + + // ARRANGE + + final List expPackageNamePrefix = List.of("com", "avaloq", "tools", "ddk"); + + // Define test data + final int cursorPos = getTag(); + final String sourceContent = "check configuration testCheckCfg {\n " + mark(cursorPos) + "\n}\n"; + + // Register a check configuration source, and get a context model + registerModel(getTestSourceFileName(), sourceContent); + final EObject context = getMarkerTagsInfo().getModel(cursorPos); + if (null == context) { + throw new IllegalStateException("Got null context model"); + } + + // ACT + + // Get catalogs + final Iterable elements = scopeProvider.getScope(context, CheckcfgPackage.Literals.CONFIGURED_CATALOG__CATALOG).getAllElements(); + if (!elements.iterator().hasNext()) { + throw new IllegalStateException("Scope has no elements"); + } + + // ASSERT + + elements.forEach((IEObjectDescription element) -> { + // Check catalog has the correct fully-qualified package name + final List actualName = element.getName().getSegments(); + final List actualPackageName = actualName.subList(0, expPackageNamePrefix.size()); + assertArrayEquals(expPackageNamePrefix.toArray(), actualPackageName.toArray(), "Catalog must have the correct fully-qualified package name"); + }); + } +} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/scoping/CheckCfgScopeProviderTest.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/scoping/CheckCfgScopeProviderTest.xtend deleted file mode 100644 index c4a5c076b3..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/scoping/CheckCfgScopeProviderTest.xtend +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.checkcfg.scoping - -import com.avaloq.tools.ddk.test.core.jupiter.BugTest -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckcfgPackage -import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil -import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractScopingTest -import static org.junit.jupiter.api.Assertions.assertArrayEquals - -final class CheckCfgScopeProviderTest extends AbstractScopingTest { - - val scopeProvider = getScopeProvider(); - - /** {@inheritDoc} */ - override protected getXtextTestUtil() { - return CheckCfgTestUtil.getInstance; - } - - /** {@inheritDoc} */ - override protected registerRequiredSources() {} - - /** - * Regression test for DSL-1498 Incorrect Catalog Name inserted by Content Assist - *

- * All catalogs supplied to Context Assist should be in the correct fully-qualified package. - *

- */ - @BugTest(value="DSL-1498") - def testCatalogsAreInCorrectPackage() { - - // ARRANGE - - val EXP_PACKAGE_NAME_PREFIX = #["com", "avaloq", "tools", "ddk"]; - - // Define test data - val CURSOR_POS = getTag; - val SOURCE_CONTENT = ''' - check configuration testCheckCfg { - «mark(CURSOR_POS)» - } - '''; - - // Register a check configuration source, and get a context model - registerModel(getTestSourceFileName, SOURCE_CONTENT); - val context = getMarkerTagsInfo().getModel(CURSOR_POS); - if (null === context) { - throw new NullPointerException("Got null context model"); - } - - // ACT - - // Get catalogs - val elements = scopeProvider.getScope(context, CheckcfgPackage.Literals.CONFIGURED_CATALOG__CATALOG).getAllElements; - if (elements.empty) { - throw new Exception("Scope has no elements"); - } - - // ASSERT - - elements.forEach[element | - // Check catalog has the correct fully-qualified package name - val actualName = element.name.segments; - val actualPackageName = actualName.take(EXP_PACKAGE_NAME_PREFIX.size); - assertArrayEquals(EXP_PACKAGE_NAME_PREFIX, actualPackageName, "Catalog must have the correct fully-qualified package name"); - ] - } -} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/syntax/CheckCfgSyntaxTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/syntax/CheckCfgSyntaxTest.java new file mode 100644 index 0000000000..60c00c4edd --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/syntax/CheckCfgSyntaxTest.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.checkcfg.syntax; + +import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractValidationTest; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTestUtil; +import java.util.LinkedList; +import java.util.List; +import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +// CHECKSTYLE:CONSTANTS-OFF +@SuppressWarnings("nls") +public class CheckCfgSyntaxTest extends AbstractValidationTest { + + @Override + protected AbstractXtextTestUtil getXtextTestUtil() { + return CheckCfgTestUtil.getInstance(); + } + + @Override + protected List getRequiredSourceFileNames() { + return new LinkedList(); + } + + @BeforeAll + public void setup() { + final String checkSource = """ + package checkcfgtest + + import com.avaloq.tools.ddk.check.check.Check + + catalog CheckCfgTestChecks + for grammar com.avaloq.tools.ddk.check.Check { + /** + * Test Error Documentation + */ + live error TestError "Test Error" + message "Test Error message." { + for Check c { + issue on c#name; + } + } + } + """; + addCustomerSourceToWorkspace("customer$sca_testchecks.check", checkSource); + IResourcesSetupUtil.waitForBuild(); + } + + @Test + public void testSyntax() { + final String checkcfgSource = """ + check configuration checkconfiguration { + catalog checkcfgtest.CheckCfgTestChecks { + default TestError + } + } + """; + validateCustomerSourceStrictly("checkconfiguration.checkcfg", checkcfgSource); + } + + @Test + public void testSyntaxConfiguredLanguage() { + final String checkcfgSource = """ + check configuration checkconfiguration + for com.avaloq.tools.ddk.^check.TestLanguage { + catalog checkcfgtest.CheckCfgTestChecks { + default TestError + } + } + """; + validateCustomerSourceStrictly("checkconfiguration.checkcfg", checkcfgSource); + } + + @Test + public void testPropertiesOnAllLevels() { + final String checkcfgSource = """ + check configuration checkconfiguration + integrationRelevant = true + testBooleanList = #[true, false, false] + + for com.avaloq.tools.ddk.^check.TestLanguage { + nameOverrides = #['altName1', 'altName2'] + + catalog checkcfgtest.CheckCfgTestChecks { + default TestError(testNumber = 3, testNumberList = #[1, 2, 3]) + } + } + """; + validateCustomerSourceStrictly("checkconfiguration.checkcfg", checkcfgSource); + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/syntax/CheckCfgSyntaxTest.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/syntax/CheckCfgSyntaxTest.xtend deleted file mode 100644 index 7349296e2c..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/syntax/CheckCfgSyntaxTest.xtend +++ /dev/null @@ -1,99 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.checkcfg.syntax - -import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil -import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractValidationTest -import java.util.LinkedList -import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.BeforeAll - -class CheckCfgSyntaxTest extends AbstractValidationTest { - - override protected getXtextTestUtil() { - CheckCfgTestUtil.instance - } - - override protected getRequiredSourceFileNames() { - new LinkedList - } - - @BeforeAll - def void setup() { - val checkSource = ''' - package checkcfgtest - - import com.avaloq.tools.ddk.check.check.Check - - catalog CheckCfgTestChecks - for grammar com.avaloq.tools.ddk.check.Check { - /** - * Test Error Documentation - */ - live error TestError "Test Error" - message "Test Error message." { - for Check c { - issue on c#name; - } - } - } - ''' - addCustomerSourceToWorkspace("customer$sca_testchecks.check", checkSource) - IResourcesSetupUtil.waitForBuild - } - - - @Test - def void testSyntax() { - val checkcfgSource = ''' - check configuration checkconfiguration { - catalog checkcfgtest.CheckCfgTestChecks { - default TestError - } - } - ''' - validateCustomerSourceStrictly("checkconfiguration.checkcfg", checkcfgSource) - - } - - @Test - def void testSyntaxConfiguredLanguage() { - val checkcfgSource = ''' - check configuration checkconfiguration - for com.avaloq.tools.ddk.^check.TestLanguage { - catalog checkcfgtest.CheckCfgTestChecks { - default TestError - } - } - ''' - validateCustomerSourceStrictly("checkconfiguration.checkcfg", checkcfgSource) -} - - @Test - def void testPropertiesOnAllLevels() { - val checkcfgSource = ''' - check configuration checkconfiguration - integrationRelevant = true - testBooleanList = #[true, false, false] - - for com.avaloq.tools.ddk.^check.TestLanguage { - nameOverrides = #['altName1', 'altName2'] - - catalog checkcfgtest.CheckCfgTestChecks { - default TestError(testNumber = 3, testNumberList = #[1, 2, 3]) - } - } - ''' - validateCustomerSourceStrictly("checkconfiguration.checkcfg", checkcfgSource) - } -} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgModelUtil.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgModelUtil.java similarity index 54% rename from com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgModelUtil.xtend rename to com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgModelUtil.java index c68e0791af..e1aced590e 100644 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgModelUtil.xtend +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgModelUtil.java @@ -8,35 +8,32 @@ * Contributors: * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.checkcfg.util +package com.avaloq.tools.ddk.checkcfg.util; /* * Provides utility operations for Check Configuration model stubs. Only partial models * are returned as strings. */ -class CheckCfgModelUtil { +@SuppressWarnings("nls") +public class CheckCfgModelUtil { - def String basicModel(String name) {''' - check configuration «name» {'''.toString + public String basicModel(final String name) { + return "check configuration " + name + " {"; } - def String basicModel() { - basicModel("testing") + public String basicModel() { + return basicModel("testing"); } - def String basicModelWithCatalog() { - basicModel + ''' - catalog Sample {'''.toString + public String basicModelWithCatalog() { + return basicModel() + "catalog Sample {"; } - def String basicModelWithTest() { - basicModelWithCatalog + ''' - Test'''.toString + public String basicModelWithTest() { + return basicModelWithCatalog() + "Test"; } - def String basicModelWithDisabledTest() { - basicModelWithCatalog + ''' - ignore Test'''.toString + public String basicModelWithDisabledTest() { + return basicModelWithCatalog() + "ignore Test"; } - } diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgTestUtil.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgTestUtil.java new file mode 100644 index 0000000000..57e1203786 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgTestUtil.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.checkcfg.util; + +import com.avaloq.tools.ddk.checkcfg.ui.internal.CheckcfgActivator; +import com.avaloq.tools.ddk.xtext.test.ITestProjectManager; +import com.avaloq.tools.ddk.xtext.test.PluginTestProjectManager; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTestUtil; +import com.google.inject.Injector; + +public class CheckCfgTestUtil extends AbstractXtextTestUtil { + + private static final AbstractXtextTestUtil UTIL_INSTANCE = new CheckCfgTestUtil(); + + public static AbstractXtextTestUtil getInstance() { + return UTIL_INSTANCE; + } + + @Override + protected Injector getInjector() { + return CheckcfgActivator.getInstance().getInjector(CheckcfgActivator.COM_AVALOQ_TOOLS_DDK_CHECKCFG_CHECKCFG); + } + + @Override + protected ITestProjectManager createTestProjectManager() { + return new PluginTestProjectManager(getInjector()); + } +} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgTestUtil.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgTestUtil.xtend deleted file mode 100644 index 9e82cc1f0d..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/util/CheckCfgTestUtil.xtend +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.checkcfg.util - -import com.avaloq.tools.ddk.xtext.test.PluginTestProjectManager -import com.avaloq.tools.ddk.xtext.test.ITestProjectManager -import com.avaloq.tools.ddk.checkcfg.ui.internal.CheckcfgActivator -import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTestUtil - -class CheckCfgTestUtil extends AbstractXtextTestUtil{ - - - static AbstractXtextTestUtil UTIL_INSTANCE = new CheckCfgTestUtil() - def static AbstractXtextTestUtil getInstance(){UTIL_INSTANCE} - - override protected getInjector() { - CheckcfgActivator.getInstance().getInjector(CheckcfgActivator.COM_AVALOQ_TOOLS_DDK_CHECKCFG_CHECKCFG) - } - - override protected ITestProjectManager createTestProjectManager() { - new PluginTestProjectManager(getInjector()); - } -} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgConfiguredParameterValidationsTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgConfiguredParameterValidationsTest.java new file mode 100644 index 0000000000..cbe2bd997a --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgConfiguredParameterValidationsTest.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.checkcfg.validation; + +import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE; +import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXTENSION_POINT; + +import java.util.List; + +import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil; +import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithExpectedValues; +import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithOutExpectedValues; +import com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractValidationTest; +import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTestUtil; +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("nls") +public class CheckCfgConfiguredParameterValidationsTest extends AbstractValidationTest { + + @Override + protected AbstractXtextTestUtil getXtextTestUtil() { + return CheckCfgTestUtil.getInstance(); + } + + @Override + protected List getRequiredSourceFileNames() { + return Lists.newArrayListWithCapacity(0); + } + + @Override + protected void beforeAllTests() { + ExtensionRegistryMock.mockExecutableExtension(ExtensionRegistryMock.mockConfigurationElement(PROPERTY_EXTENSION_POINT), PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithExpectedValues.INSTANCE); + ExtensionRegistryMock.mockExecutableExtension(ExtensionRegistryMock.mockConfigurationElement(PROPERTY_EXTENSION_POINT), PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithOutExpectedValues.INSTANCE); + super.beforeAllTests(); + } + + @Override + protected void afterAllTests() { + super.afterAllTests(); + ExtensionRegistryMock.unMock(PROPERTY_EXTENSION_POINT); + } + + @Test + public void testConfiguredParameterValues() { + final TestPropertySpecificationWithExpectedValues allowedOnly = TestPropertySpecificationWithExpectedValues.INSTANCE; + final TestPropertySpecificationWithOutExpectedValues acceptsAny = TestPropertySpecificationWithOutExpectedValues.INSTANCE; + final StringBuilder builder = new StringBuilder(512); + builder.append("check configuration Test\n"); + builder.append(" ").append(allowedOnly.getName()).append(" = ").append(error(IssueCodes.PARAMETER_VALUE_NOT_ALLOWED)).append("\"notAllowed\"\n"); + builder.append(" for com.avaloq.tools.ddk.^check.TestLanguage {\n"); + builder.append(" ").append(allowedOnly.getName()).append(" = ").append(noDiagnostic(IssueCodes.PARAMETER_VALUE_NOT_ALLOWED)).append('"').append(allowedOnly.getExpectedValues()[0]).append("\"\n"); + builder.append(" ").append(acceptsAny.getName()).append(" = ").append(noDiagnostic(IssueCodes.PARAMETER_VALUE_NOT_ALLOWED)).append("\"whatever\"\n"); + builder.append(" }\n"); + validateKernelSourceStrictly("ConfiguredParameterValues.checkcfg", builder); + } + +} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgConfiguredParameterValidationsTest.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgConfiguredParameterValidationsTest.xtend deleted file mode 100644 index 32b1f4b56c..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgConfiguredParameterValidationsTest.xtend +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.checkcfg.validation - -import com.avaloq.tools.ddk.checkcfg.util.CheckCfgTestUtil -import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithExpectedValues -import com.avaloq.tools.ddk.test.checkcfg.TestPropertySpecificationWithOutExpectedValues -import com.google.common.collect.Lists - -import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE -import static com.avaloq.tools.ddk.checkcfg.CheckCfgConstants.PROPERTY_EXTENSION_POINT - -import static extension com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock.mockConfigurationElement -import static extension com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock.mockExecutableExtension -import static extension com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock.unMock -import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractValidationTest -import org.junit.jupiter.api.Test - -class CheckCfgConfiguredParameterValidationsTest extends AbstractValidationTest { - - override protected getXtextTestUtil() { - return CheckCfgTestUtil.instance - } - - override protected getRequiredSourceFileNames() { - Lists.newArrayListWithCapacity(0) - } - - override protected beforeAllTests() { - PROPERTY_EXTENSION_POINT.mockConfigurationElement.mockExecutableExtension(PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithExpectedValues.INSTANCE) - PROPERTY_EXTENSION_POINT.mockConfigurationElement.mockExecutableExtension(PROPERTY_EXECUTABLE_EXTENSION_ATTRIBUTE, TestPropertySpecificationWithOutExpectedValues.INSTANCE) - super.beforeAllTests() - } - - override protected afterAllTests() { - super.afterAllTests() - PROPERTY_EXTENSION_POINT.unMock - } - - @Test - def testConfiguredParameterValues() { - val allowedOnly = TestPropertySpecificationWithExpectedValues.INSTANCE - val acceptsAny = TestPropertySpecificationWithOutExpectedValues.INSTANCE - validateKernelSourceStrictly("ConfiguredParameterValues.checkcfg", ''' - check configuration Test - «allowedOnly.name» = «error(IssueCodes.PARAMETER_VALUE_NOT_ALLOWED)»"notAllowed" - for com.avaloq.tools.ddk.^check.TestLanguage { - «allowedOnly.name» = «noDiagnostic(IssueCodes.PARAMETER_VALUE_NOT_ALLOWED)»"«allowedOnly.expectedValues.head»" - «acceptsAny.name» = «noDiagnostic(IssueCodes.PARAMETER_VALUE_NOT_ALLOWED)»"whatever" - } - ''') - } - -} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java new file mode 100644 index 0000000000..46b7bc8d68 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.checkcfg.validation; + +import com.avaloq.tools.ddk.checkcfg.CheckCfgUiInjectorProvider; +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckcfgPackage; +import com.google.inject.Inject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@InjectWith(CheckCfgUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class CheckCfgTest { + + @Inject + private ValidationTestHelper helper; + + @Inject + private ParseHelper parser; + + @Test + public void testValidLanguageOk() throws Exception { + final CheckConfiguration model = parser.parse(""" + check configuration Test + + for com.avaloq.tools.ddk.^check.TestLanguage { + + } + """); + helper.assertNoIssues(model); + } + + @Test + public void testUnknownLanguageNotOk() throws Exception { + final CheckConfiguration model = parser.parse(""" + check configuration Test + + for com.avaloq.tools.ddk.^check.Unknown { + + } + """); + helper.assertError(model, CheckcfgPackage.Literals.CONFIGURED_LANGUAGE_VALIDATOR, IssueCodes.UNKNOWN_LANGUAGE); + } + +} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.xtend b/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.xtend deleted file mode 100644 index 58b57e1764..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core.test/src/com/avaloq/tools/ddk/checkcfg/validation/CheckCfgTest.xtend +++ /dev/null @@ -1,63 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.checkcfg.validation - -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckcfgPackage -import com.google.inject.Inject -import org.eclipse.xtext.testing.util.ParseHelper -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.eclipse.xtext.testing.InjectWith -import com.avaloq.tools.ddk.checkcfg.CheckCfgUiInjectorProvider -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension - -@InjectWith(typeof(CheckCfgUiInjectorProvider)) -@ExtendWith(InjectionExtension) -class CheckCfgTest { - - - @Inject - ValidationTestHelper helper; - - @Inject - ParseHelper parser; - - @Test - def testValidLanguageOk() { - - val model = parser.parse(''' - check configuration Test - - for com.avaloq.tools.ddk.^check.TestLanguage { - - } - - '''); - helper.assertNoIssues(model); - } - - @Test - def testUnknownLanguageNotOk() { - - val model = parser.parse(''' - check configuration Test - - for com.avaloq.tools.ddk.^check.Unknown { - - } - - '''); - helper.assertError(model, CheckcfgPackage.Literals::CONFIGURED_LANGUAGE_VALIDATOR, IssueCodes.UNKNOWN_LANGUAGE); - } - -} diff --git a/com.avaloq.tools.ddk.checkcfg.core.test/xtend-gen/.gitignore b/com.avaloq.tools.ddk.checkcfg.core.test/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.checkcfg.core/.classpath b/com.avaloq.tools.ddk.checkcfg.core/.classpath index 5a4d66ff35..f7054419e4 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/.classpath +++ b/com.avaloq.tools.ddk.checkcfg.core/.classpath @@ -6,11 +6,6 @@
- - - - - diff --git a/com.avaloq.tools.ddk.checkcfg.core/build.properties b/com.avaloq.tools.ddk.checkcfg.core/build.properties index 7838757824..d69df83aa0 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/build.properties +++ b/com.avaloq.tools.ddk.checkcfg.core/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ .,\ plugin.xml,\ diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.java new file mode 100644 index 0000000000..ab8b5f6650 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.checkcfg.generator; + +import com.avaloq.tools.ddk.check.runtime.configuration.ICheckConfigurationStoreService; +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import java.util.Properties; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.generator.AbstractFileSystemAccess; +import org.eclipse.xtext.generator.IFileSystemAccess; +import org.eclipse.xtext.generator.IGenerator; +import org.eclipse.xtext.xbase.lib.IteratorExtensions; + +@SuppressWarnings("nls") +public class CheckCfgGenerator implements IGenerator { + + @Inject + private CheckConfigurationPropertiesGenerator propertiesGenerator; + + public String outputPath() { + return ".settings"; + } + + @SuppressWarnings("PMD.UnusedFormalParameter") // parameter kept for API consistency + public String fileName(final CheckConfiguration configuration) { + return ICheckConfigurationStoreService.DEFAULT_CHECK_CONFIGURATION_NODE + ".prefs"; + } + + @Override + public void doGenerate(final Resource resource, final IFileSystemAccess fsa) { + if (fsa instanceof AbstractFileSystemAccess abstractFsa) { + abstractFsa.setOutputPath(outputPath()); + } + for (final CheckConfiguration configuration : Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), CheckConfiguration.class)) { + fsa.generateFile(fileName(configuration), compile(configuration)); + } + } + + public CharSequence compile(final CheckConfiguration config) { + final Properties properties = propertiesGenerator.convertToProperties(config); + final StringBuilder builder = new StringBuilder(); + for (final Object k : properties.keySet()) { + builder.append(k).append('=').append(properties.get(k)).append('\n'); + } + return builder; + } +} diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.xtend b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.xtend deleted file mode 100644 index 1b5537e5a8..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/generator/CheckCfgGenerator.xtend +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.checkcfg.generator - -import com.avaloq.tools.ddk.check.runtime.configuration.ICheckConfigurationStoreService -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration -import com.google.inject.Inject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.AbstractFileSystemAccess -import org.eclipse.xtext.generator.IFileSystemAccess -import org.eclipse.xtext.generator.IGenerator - -import static org.eclipse.xtext.xbase.lib.IteratorExtensions.* - -class CheckCfgGenerator implements IGenerator { - - @Inject - CheckConfigurationPropertiesGenerator propertiesGenerator; - - def outputPath() { - '.settings' - } - - def fileName(CheckConfiguration configuration) { - ICheckConfigurationStoreService.DEFAULT_CHECK_CONFIGURATION_NODE + '.prefs' - } - - override void doGenerate(Resource resource, IFileSystemAccess fsa) { - if (fsa instanceof AbstractFileSystemAccess) { - fsa.setOutputPath(outputPath) - } - for (configuration:toIterable(resource.allContents).filter(typeof(CheckConfiguration))) { - fsa.generateFile(configuration.fileName, configuration.compile) - } - } - - def compile(CheckConfiguration config) { - val properties = propertiesGenerator.convertToProperties(config); - ''' - «FOR k:properties.keySet» - «k»=«properties.get(k)» - «ENDFOR» - ''' - } -} diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/jvmmodel/CheckCfgJvmModelInferrer.xtend b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/jvmmodel/CheckCfgJvmModelInferrer.java similarity index 51% rename from com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/jvmmodel/CheckCfgJvmModelInferrer.xtend rename to com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/jvmmodel/CheckCfgJvmModelInferrer.java index 65c97bf1a3..bff78a5b5a 100644 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/jvmmodel/CheckCfgJvmModelInferrer.xtend +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/jvmmodel/CheckCfgJvmModelInferrer.java @@ -8,13 +8,13 @@ * Contributors: * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.checkcfg.jvmmodel +package com.avaloq.tools.ddk.checkcfg.jvmmodel; -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer -import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor -import com.google.inject.Inject -import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder +import com.google.inject.Inject; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer; +import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor; +import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder; /** *

Infers a JVM model from the source model.

@@ -22,24 +22,27 @@ *

The JVM model should contain all elements that would appear in the Java code * which is generated from the source model. Other models link against the JVM model rather than the source model.

*/ -class CheckCfgJvmModelInferrer extends AbstractModelInferrer { +@SuppressWarnings("nls") +public class CheckCfgJvmModelInferrer extends AbstractModelInferrer { - @Inject extension JvmTypesBuilder + @Inject + private JvmTypesBuilder jvmTypesBuilder; - override void _infer(EObject element, IJvmDeclaredTypeAcceptor acceptor, boolean preIndexingPhase) { + @Override + public void _infer(final EObject element, final IJvmDeclaredTypeAcceptor acceptor, final boolean preIndexingPhase) { // Infer dummy class as type resolver expects at least one root Java type - acceptor.accept(element.toClass("xxxyyyzzz.dummy.class.name")[]) + acceptor.accept(jvmTypesBuilder.toClass(element, "xxxyyyzzz.dummy.class.name", (it) -> {})); } // Here you explain how your model is mapped to Java elements, by writing the actual translation code. // An example based on the initial hellow world example could look like this: -// acceptor.accept(element.toClass("my.company.greeting.MyGreetings") [ -// for (greeting : element.greetings) { -// members += greeting.toMethod(greeting.name, greeting.newTypeRef(typeof(String))) [ -// it.body [''' -// return "Hello «greeting.name»"; -// '''] -// ] -// } -// ]) +// acceptor.accept(element.toClass("my.company.greeting.MyGreetings") [ +// for (greeting : element.greetings) { +// members += greeting.toMethod(greeting.name, greeting.newTypeRef(typeof(String))) [ +// it.body [''' +// return "Hello «greeting.name»"; +// '''] +// ] +// } +// ]) } diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/util/PropertiesInferenceHelper.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/util/PropertiesInferenceHelper.java new file mode 100644 index 0000000000..19a0328779 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/util/PropertiesInferenceHelper.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.checkcfg.util; + +import com.avaloq.tools.ddk.check.check.FormalParameter; +import com.avaloq.tools.ddk.check.check.impl.CheckFactoryImpl; +import com.avaloq.tools.ddk.checkcfg.CheckCfgUtil; +import com.avaloq.tools.ddk.checkcfg.ICheckCfgPropertySpecification; +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration; +import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckcfgPackage; +import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfigurableSection; +import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredParameter; +import com.avaloq.tools.ddk.xtext.util.ParseTreeUtil; +import com.google.inject.Inject; +import java.util.Collection; +import java.util.List; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.common.types.JvmTypeReference; +import org.eclipse.xtext.resource.IResourceServiceProvider; +import org.eclipse.xtext.xbase.XBooleanLiteral; +import org.eclipse.xtext.xbase.XExpression; +import org.eclipse.xtext.xbase.XListLiteral; +import org.eclipse.xtext.xbase.XNumberLiteral; +import org.eclipse.xtext.xbase.XStringLiteral; +import org.eclipse.xtext.xbase.jvmmodel.JvmTypeReferenceBuilder; + +@SuppressWarnings("nls") +public class PropertiesInferenceHelper { + + @Inject + private JvmTypeReferenceBuilder.Factory typeRefBuilderFactory; + + private static final String BOOLEAN = "boolean"; + private static final String STRING = "java.lang.String"; + private static final String NUMBER = "int"; + private static final String STRING_LIST = "List"; + private static final String NUMBER_LIST = "List"; + private static final String BOOLEAN_LIST = "List"; + + public EList getProperties(final CheckConfiguration checkConfiguration, final EList properties) { + final JvmTypeReferenceBuilder referenceBuilder = typeRefBuilderFactory.create(checkConfiguration.eResource().getResourceSet()); + + // get all ConfigurableSections + final List configurableSections = EcoreUtil2.getAllContentsOfType(checkConfiguration, ConfigurableSection.class); + // the CheckConfiguration itself is a configurable section + configurableSections.add(0, checkConfiguration); + + // infer properties for all sections + for (final ConfigurableSection section : configurableSections) { + final EList parameters = section.getParameterConfigurations(); + for (final ConfiguredParameter parameter : parameters) { + final FormalParameter formalParameter = inferFormalParameter(parameter, referenceBuilder); + if (formalParameter != null) { + properties.add(formalParameter); + } + } + } + + // add contributed properties + final Collection contributions = CheckCfgUtil.getAllPropertyContributions(); + for (final ICheckCfgPropertySpecification contribution : contributions) { + final FormalParameter formalParameter = inferFormalParameter(contribution, referenceBuilder); + if (formalParameter != null) { + properties.add(formalParameter); + } + } + return properties; + } + + public JvmTypeReference inferType(final ICheckCfgPropertySpecification contribution, final JvmTypeReferenceBuilder referenceBuilder) { + return switch (contribution.getType()) { + case BOOLEAN -> referenceBuilder.typeRef(BOOLEAN); + case NUMBER -> referenceBuilder.typeRef(NUMBER); + case STRING -> referenceBuilder.typeRef(STRING); + case NUMBERS -> referenceBuilder.typeRef(NUMBER_LIST); + case STRINGS -> referenceBuilder.typeRef(STRING_LIST); + case BOOLEANS -> referenceBuilder.typeRef(BOOLEAN_LIST); + }; + } + + public JvmTypeReference inferListType(final XListLiteral newValue, final JvmTypeReferenceBuilder referenceBuilder) { + if (newValue.getElements().isEmpty()) { + return null; + } + final XExpression firstElement = newValue.getElements().get(0); + if (firstElement instanceof XBooleanLiteral) { + return referenceBuilder.typeRef(BOOLEAN_LIST); + } else if (firstElement instanceof XNumberLiteral) { + return referenceBuilder.typeRef(NUMBER_LIST); + } else if (firstElement instanceof XStringLiteral) { + return referenceBuilder.typeRef(STRING_LIST); + } else { + return null; + } + } + + public JvmTypeReference inferType(final ConfiguredParameter parameter, final JvmTypeReferenceBuilder referenceBuilder) { + final XExpression newValue = parameter.getNewValue(); + if (newValue instanceof XBooleanLiteral) { + return referenceBuilder.typeRef(BOOLEAN); + } else if (newValue instanceof XNumberLiteral) { + return referenceBuilder.typeRef(NUMBER); + } else if (newValue instanceof XStringLiteral) { + return referenceBuilder.typeRef(STRING); + } else if (newValue instanceof XListLiteral xListLiteral) { + return inferListType(xListLiteral, referenceBuilder); + } else { + return null; + } + } + + public FormalParameter inferFormalParameter(final ConfiguredParameter parameter, final JvmTypeReferenceBuilder referenceBuilder) { + if (parameter == null) { + return null; + } + return inferFormalParameter(ParseTreeUtil.getParsedString(parameter, CheckcfgPackage.Literals.CONFIGURED_PARAMETER__PARAMETER), + inferType(parameter, referenceBuilder)); + } + + public FormalParameter inferFormalParameter(final ICheckCfgPropertySpecification contribution, final JvmTypeReferenceBuilder referenceBuilder) { + if (contribution == null) { + return null; + } + return inferFormalParameter(contribution.getName(), inferType(contribution, referenceBuilder)); + } + + public FormalParameter inferFormalParameter(final String name, final JvmTypeReference type) { + if (type == null) { + return null; + } + final FormalParameter formalParameter = CheckFactoryImpl.eINSTANCE.createFormalParameter(); + formalParameter.setName(name); + formalParameter.setType(type); + return formalParameter; + } + + public static PropertiesInferenceHelper getHelper() { + return IResourceServiceProvider.Registry.INSTANCE.getResourceServiceProvider(URI.createURI("DUMMY.checkcfg")).get( + PropertiesInferenceHelper.class); + } + +} diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/util/PropertiesInferenceHelper.xtend b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/util/PropertiesInferenceHelper.xtend deleted file mode 100644 index ca58fb18a2..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/util/PropertiesInferenceHelper.xtend +++ /dev/null @@ -1,140 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.checkcfg.util - -import com.avaloq.tools.ddk.check.check.FormalParameter -import com.avaloq.tools.ddk.check.check.impl.CheckFactoryImpl -import com.avaloq.tools.ddk.checkcfg.CheckCfgUtil -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckConfiguration -import com.avaloq.tools.ddk.checkcfg.checkcfg.CheckcfgPackage -import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfigurableSection -import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredParameter -import com.avaloq.tools.ddk.xtext.util.ParseTreeUtil -import com.google.inject.Inject -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.common.util.URI -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.common.types.JvmTypeReference -import org.eclipse.xtext.resource.IResourceServiceProvider -import org.eclipse.xtext.xbase.XBooleanLiteral -import org.eclipse.xtext.xbase.XNumberLiteral -import org.eclipse.xtext.xbase.XStringLiteral -import org.eclipse.xtext.xbase.jvmmodel.JvmTypeReferenceBuilder -import com.avaloq.tools.ddk.checkcfg.ICheckCfgPropertySpecification -import org.eclipse.xtext.xbase.XListLiteral - -class PropertiesInferenceHelper { - @Inject JvmTypeReferenceBuilder.Factory typeRefBuilderFactory; - - static val BOOLEAN = "boolean" - static val STRING = "java.lang.String" - static val NUMBER = "int" - static val STRING_LIST = "List" - static val NUMBER_LIST = "List" - static val BOOLEAN_LIST = "List" - - def getProperties(CheckConfiguration checkConfiguration, EList properties) { - val referenceBuilder = typeRefBuilderFactory.create(checkConfiguration.eResource().resourceSet) - - // get all ConfigurableSections - val configurableSections = EcoreUtil2.getAllContentsOfType(checkConfiguration, typeof(ConfigurableSection)) - // the CheckConfiguration itself is a configurable section - configurableSections.add(0, checkConfiguration) - - // infer properties for all sections - for (section : configurableSections) { - val parameters = section.parameterConfigurations; - for (parameter : parameters) { - val formalParameter = parameter.inferFormalParameter(referenceBuilder) - if (formalParameter !== null) { - properties.add(formalParameter) - } - } - } - - // add contributed properties - val contributions = CheckCfgUtil.allPropertyContributions - for (contribution : contributions) { - val formalParameter = contribution.inferFormalParameter(referenceBuilder) - if (formalParameter !== null) { - properties.add(formalParameter) - } - } - properties - } - - def inferType(ICheckCfgPropertySpecification contribution, JvmTypeReferenceBuilder referenceBuilder) { - switch contribution.type { - case ICheckCfgPropertySpecification.PropertyType.BOOLEAN: referenceBuilder.typeRef(BOOLEAN) - case ICheckCfgPropertySpecification.PropertyType.NUMBER: referenceBuilder.typeRef(NUMBER) - case ICheckCfgPropertySpecification.PropertyType.STRING: referenceBuilder.typeRef(STRING) - case ICheckCfgPropertySpecification.PropertyType.NUMBERS: referenceBuilder.typeRef(NUMBER_LIST) - case ICheckCfgPropertySpecification.PropertyType.STRINGS: referenceBuilder.typeRef(STRING_LIST) - case ICheckCfgPropertySpecification.PropertyType.BOOLEANS: referenceBuilder.typeRef(BOOLEAN_LIST) - default: null - } - } - - def inferListType(XListLiteral newValue, JvmTypeReferenceBuilder referenceBuilder) { - if (newValue.elements.size < 1) { - return null - } - switch newValue.elements.get(0) { - XBooleanLiteral: referenceBuilder.typeRef(BOOLEAN_LIST) - XNumberLiteral: referenceBuilder.typeRef(NUMBER_LIST) - XStringLiteral: referenceBuilder.typeRef(STRING_LIST) - default: null - } - } - - def inferType(ConfiguredParameter parameter, JvmTypeReferenceBuilder referenceBuilder) { - val newValue = parameter.newValue - switch newValue { - XBooleanLiteral: referenceBuilder.typeRef(BOOLEAN) - XNumberLiteral: referenceBuilder.typeRef(NUMBER) - XStringLiteral: referenceBuilder.typeRef(STRING) - XListLiteral: inferListType(newValue, referenceBuilder) - default: null - } - } - - def inferFormalParameter(ConfiguredParameter parameter, JvmTypeReferenceBuilder referenceBuilder) { - if (parameter === null) { - return null - } - inferFormalParameter(ParseTreeUtil.getParsedString(parameter, CheckcfgPackage.Literals.CONFIGURED_PARAMETER__PARAMETER), - inferType(parameter, referenceBuilder)) - } - - def inferFormalParameter(ICheckCfgPropertySpecification contribution, JvmTypeReferenceBuilder referenceBuilder) { - if (contribution === null) { - return null - } - inferFormalParameter(contribution.name, inferType(contribution, referenceBuilder)) - } - - def inferFormalParameter(String name, JvmTypeReference type) { - if (type === null) { - return null - } - val formalParameter = CheckFactoryImpl.eINSTANCE.createFormalParameter(); - formalParameter.name = name - formalParameter.type = type - formalParameter - } - - def static getHelper() { - IResourceServiceProvider.Registry.INSTANCE.getResourceServiceProvider(URI.createURI("DUMMY.checkcfg")).get( - typeof(PropertiesInferenceHelper)); - } - -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/ConfiguredParameterChecks.java b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/ConfiguredParameterChecks.java new file mode 100644 index 0000000000..a4e2b65071 --- /dev/null +++ b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/ConfiguredParameterChecks.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * Contributors: + * Avaloq Group AG - initial API and implementation + */ +package com.avaloq.tools.ddk.checkcfg.validation; + +import com.avaloq.tools.ddk.check.check.FormalParameter; +import com.avaloq.tools.ddk.check.validation.FormalParameterCheckBase; +import com.avaloq.tools.ddk.checkcfg.CheckCfgUtil; +import com.avaloq.tools.ddk.checkcfg.ICheckCfgPropertySpecification; +import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredParameter; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import org.eclipse.xtext.validation.Check; +import org.eclipse.xtext.xbase.XExpression; +import org.eclipse.xtext.xbase.XListLiteral; +import org.eclipse.xtext.xbase.XStringLiteral; + +/** + * Checkcfg formal parameter checks. + */ +@SuppressWarnings("nls") +public class ConfiguredParameterChecks extends FormalParameterCheckBase { + + private static Map> allowedPropertyValues = Maps.newHashMap(); + + /** + * Verifies that numeric literals in the default values of formal parameters are all integral values. + * @param parameter the configured parameter to check. + */ + @Check + public void checkFormalParameterNumbersAreIntegers(final ConfiguredParameter parameter) { + checkRightHandSideHasOnlyIntegralNumbers(parameter.getNewValue(), IssueCodes.FORMAL_PARAMETER_MUST_BE_INTEGER); + } + + /** + * Verifies that formal parameters defined via extension point only set values expected by the extension. + * @param parameter to check. + */ + @Check + public void checkFormalParameterValuesAreValid(final ConfiguredParameter parameter) { + FormalParameter formalParam = parameter.getParameter(); + String name = formalParam != null ? formalParam.getName() : null; + final String propertyName = name != null ? name.toLowerCase(Locale.ENGLISH) : null; + if (propertyName != null) { + Set permitted = allowedPropertyValues.get(propertyName); + if (permitted == null) { + ICheckCfgPropertySpecification spec = CheckCfgUtil.getPropertySpecification(propertyName); + String[] expected = spec != null ? spec.getExpectedValues() : null; + if (expected == null) { + expected = new String[0]; + } + permitted = Sets.newHashSet(expected); + allowedPropertyValues.put(propertyName, permitted); + } + final XExpression value = parameter.getNewValue(); + if (!permitted.isEmpty() && value != null) { + List expressions; + if (value instanceof XListLiteral xListLiteral) { + expressions = xListLiteral.getElements(); + } else { + expressions = Collections.singletonList(value); + } + for (XExpression expression : expressions) { + if (!(expression instanceof XStringLiteral) || !permitted.contains(((XStringLiteral) expression).getValue().toLowerCase(Locale.ENGLISH))) { + error("Not a meaningful value for " + propertyName, expression, null, IssueCodes.PARAMETER_VALUE_NOT_ALLOWED); + } + } + } + } + } +} diff --git a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/ConfiguredParameterChecks.xtend b/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/ConfiguredParameterChecks.xtend deleted file mode 100644 index 073c6856e0..0000000000 --- a/com.avaloq.tools.ddk.checkcfg.core/src/com/avaloq/tools/ddk/checkcfg/validation/ConfiguredParameterChecks.xtend +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * Contributors: - * Avaloq Group AG - initial API and implementation - */ -package com.avaloq.tools.ddk.checkcfg.validation - -import com.avaloq.tools.ddk.check.validation.FormalParameterCheckBase -import com.avaloq.tools.ddk.checkcfg.CheckCfgUtil -import com.avaloq.tools.ddk.checkcfg.checkcfg.ConfiguredParameter -import com.google.common.collect.Maps -import com.google.common.collect.Sets -import java.util.Collections -import java.util.Locale -import java.util.Map -import java.util.Set -import org.eclipse.xtext.validation.Check -import org.eclipse.xtext.xbase.XListLiteral -import org.eclipse.xtext.xbase.XStringLiteral - -/** - * Checkcfg formal parameter checks. - */ -class ConfiguredParameterChecks extends FormalParameterCheckBase { - static Map> allowedPropertyValues = Maps.newHashMap() - - /** - * Verifies that numeric literals in the default values of formal parameters are all integral values. - * @param parameterto check. - */ - @Check - def checkFormalParameterNumbersAreIntegers(ConfiguredParameter parameter) { - checkRightHandSideHasOnlyIntegralNumbers(parameter.getNewValue(), IssueCodes.FORMAL_PARAMETER_MUST_BE_INTEGER) - } - - /** - * Verifies that formal parameters defined via extension point only set values expected by the extension. - * @param parameter to check. - */ - @Check - def checkFormalParameterValuesAreValid(ConfiguredParameter parameter) { - val propertyName = parameter.parameter?.name?.toLowerCase(Locale.ENGLISH) - if (propertyName !== null) { - var permitted = allowedPropertyValues.get(propertyName) - if (permitted === null) { - val expected = CheckCfgUtil.getPropertySpecification(propertyName)?.expectedValues ?: newArrayOfSize(0) - permitted = Sets.newHashSet(expected) - allowedPropertyValues.put(propertyName, permitted) - } - val value = parameter.newValue - if (!permitted.isNullOrEmpty && value !== null) { - val expressions = if (value instanceof XListLiteral) value.elements else Collections.singletonList(value) - for (expression : expressions) { - if (!(expression instanceof XStringLiteral) || !permitted.contains((expression as XStringLiteral).value.toLowerCase(Locale.ENGLISH))) { - error('''Not a meaningful value for «propertyName»''', expression, null, IssueCodes.PARAMETER_VALUE_NOT_ALLOWED) - } - } - } - } - } - -} diff --git a/com.avaloq.tools.ddk.checkcfg.core/xtend-gen/.gitignore b/com.avaloq.tools.ddk.checkcfg.core/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.checkcfg.ide/.classpath b/com.avaloq.tools.ddk.checkcfg.ide/.classpath index 5a4d66ff35..f7054419e4 100644 --- a/com.avaloq.tools.ddk.checkcfg.ide/.classpath +++ b/com.avaloq.tools.ddk.checkcfg.ide/.classpath @@ -6,11 +6,6 @@
- - - - - diff --git a/com.avaloq.tools.ddk.checkcfg.ide/build.properties b/com.avaloq.tools.ddk.checkcfg.ide/build.properties index 3f5513de7b..2d6ac57da2 100644 --- a/com.avaloq.tools.ddk.checkcfg.ide/build.properties +++ b/com.avaloq.tools.ddk.checkcfg.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.checkcfg.ide/xtend-gen/.gitignore b/com.avaloq.tools.ddk.checkcfg.ide/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.sample.helloworld.ide/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.sample.helloworld.ide/META-INF/MANIFEST.MF index 6c814c5c10..0fd768c82b 100644 --- a/com.avaloq.tools.ddk.sample.helloworld.ide/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.sample.helloworld.ide/META-INF/MANIFEST.MF @@ -10,7 +10,6 @@ Require-Bundle: com.avaloq.tools.ddk.sample.helloworld, org.eclipse.compare, org.eclipse.xtext.builder, org.eclipse.xtext.xbase.lib, - org.eclipse.xtend.lib;resolution:=optional, org.antlr.runtime, com.avaloq.tools.ddk.check.runtime.core, com.google.inject, diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/.classpath b/com.avaloq.tools.ddk.sample.helloworld.ui.test/.classpath index 7b1ace8da9..8a7d45dab6 100644 --- a/com.avaloq.tools.ddk.sample.helloworld.ui.test/.classpath +++ b/com.avaloq.tools.ddk.sample.helloworld.ui.test/.classpath @@ -2,7 +2,6 @@ - diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/build.properties b/com.avaloq.tools.ddk.sample.helloworld.ui.test/build.properties index 877df0f1d3..9d45ce6c63 100644 --- a/com.avaloq.tools.ddk.sample.helloworld.ui.test/build.properties +++ b/com.avaloq.tools.ddk.sample.helloworld.ui.test/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ src-gen/,\ - xtend-gen/,\ resource/ bin.includes = .,\ META-INF/ diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckConfigurationIsAppliedTest.xtend b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckConfigurationIsAppliedTest.java similarity index 51% rename from com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckConfigurationIsAppliedTest.xtend rename to com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckConfigurationIsAppliedTest.java index 60376ba251..41b238cead 100644 --- a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckConfigurationIsAppliedTest.xtend +++ b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckConfigurationIsAppliedTest.java @@ -8,33 +8,35 @@ * Contributors: * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.sample.helloworld.check +package com.avaloq.tools.ddk.sample.helloworld.check; -import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase -import com.google.common.collect.Lists -import com.google.inject.Inject -import java.util.List -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil -import com.avaloq.tools.ddk.sample.helloworld.helloWorld.Model -import com.avaloq.tools.ddk.sample.helloworld.ui.internal.HelloworldActivator -import com.avaloq.tools.ddk.sample.helloworld.helloWorld.HelloWorldPackage -import com.avaloq.tools.ddk.sample.helloworld.validation.ExecutionEnvironmentIssueCodes -import com.avaloq.tools.ddk.sample.helloworld.ui.HelloWorldUiInjectorProvider -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test +import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase; +import com.avaloq.tools.ddk.sample.helloworld.helloWorld.HelloWorldPackage; +import com.avaloq.tools.ddk.sample.helloworld.helloWorld.Model; +import com.avaloq.tools.ddk.sample.helloworld.ui.HelloWorldUiInjectorProvider; +import com.avaloq.tools.ddk.sample.helloworld.ui.internal.HelloworldActivator; +import com.avaloq.tools.ddk.sample.helloworld.validation.ExecutionEnvironmentIssueCodes; +import com.google.common.collect.Lists; +import com.google.inject.Inject; +import com.google.inject.Injector; +import java.util.List; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; -@InjectWith(typeof(HelloWorldUiInjectorProvider)) -@ExtendWith(typeof(InjectionExtension)) -class CheckConfigurationIsAppliedTest extends AbstractCheckTestCase { +@InjectWith(HelloWorldUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +public class CheckConfigurationIsAppliedTest extends AbstractCheckTestCase { @Inject - ValidationTestHelper helper + private ValidationTestHelper helper; - override getInjector() { - HelloworldActivator::instance.getInjector(HelloworldActivator::COM_AVALOQ_TOOLS_DDK_SAMPLE_HELLOWORLD_HELLOWORLD) + @Override + public Injector getInjector() { + return HelloworldActivator.getInstance().getInjector(HelloworldActivator.COM_AVALOQ_TOOLS_DDK_SAMPLE_HELLOWORLD_HELLOWORLD); } /* @@ -43,8 +45,8 @@ override getInjector() { * Eclipse has the Check runtime plugins installed, code will automatically be generated * for those resources. In order to avoid that the file extensions have been ommitted. */ - def List getRequiredSourceFileNames() { - Lists::newArrayList('.settings/com.avaloq.tools.ddk.checkcfg.core.prefs', 'Greetings') + public List getRequiredSourceFileNames() { + return Lists.newArrayList(".settings/com.avaloq.tools.ddk.checkcfg.core.prefs", "Greetings"); } /* @@ -52,13 +54,12 @@ def List getRequiredSourceFileNames() { * and applied: the severity is changed from ERROR to WARNING. */ @Test - def void testCheckConfigurationIsApplied() { + public void testCheckConfigurationIsApplied() throws Exception { // sources are copied into the project and then built by the Xtext builder - addSourcesToWorkspace(typeof(CheckConfigurationIsAppliedTest), requiredSourceFileNames) + addSourcesToWorkspace(CheckConfigurationIsAppliedTest.class, getRequiredSourceFileNames()); // wait for build to finish, otherwise included catalog may not be resolvable - IResourcesSetupUtil::waitForBuild - val model = getModel("Greetings") as Model - helper.assertWarning(model.greetings.get(0), HelloWorldPackage$Literals::GREETING, ExecutionEnvironmentIssueCodes::FRANZNAME) + IResourcesSetupUtil.waitForBuild(); + final Model model = (Model) getModel("Greetings"); + helper.assertWarning(model.getGreetings().get(0), HelloWorldPackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.FRANZNAME); } - } diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckExecutionEnvironmentProjectTest.java b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckExecutionEnvironmentProjectTest.java new file mode 100644 index 0000000000..9fd0ae2887 --- /dev/null +++ b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckExecutionEnvironmentProjectTest.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.sample.helloworld.check; + +import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase; +import com.avaloq.tools.ddk.sample.helloworld.helloWorld.HelloWorldPackage; +import com.avaloq.tools.ddk.sample.helloworld.helloWorld.Model; +import com.avaloq.tools.ddk.sample.helloworld.ui.HelloWorldUiInjectorProvider; +import com.avaloq.tools.ddk.sample.helloworld.ui.internal.HelloworldActivator; +import com.avaloq.tools.ddk.sample.helloworld.validation.ExecutionEnvironmentIssueCodes; +import com.avaloq.tools.ddk.sample.helloworld.validation.IssueCodes; +import com.avaloq.tools.ddk.sample.helloworld.validation.LibraryChecksIssueCodes; +import com.google.inject.Inject; +import com.google.inject.Injector; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + + +@InjectWith(HelloWorldUiInjectorProvider.class) +@ExtendWith(InjectionExtension.class) +public class CheckExecutionEnvironmentProjectTest extends AbstractCheckTestCase { + + @Inject + private ValidationTestHelper helper; + + @Inject + private ParseHelper parser; + + @Override + protected Injector getInjector() { + return HelloworldActivator.getInstance().getInjector(HelloworldActivator.COM_AVALOQ_TOOLS_DDK_SAMPLE_HELLOWORLD_HELLOWORLD); + } + + @Test + public void testFranz() throws Exception { + final Model model = parser.parse("Hello Franz!"); + helper.assertError(model, HelloWorldPackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.FRANZNAME); + } + + @Test + public void testFrans() throws Exception { + final Model model = parser.parse("Hello Frans!"); + helper.assertNoError(model, ExecutionEnvironmentIssueCodes.FRANZNAME); + helper.assertNoError(model, ExecutionEnvironmentIssueCodes.NAMELENGTH); + } + + @Test + public void testGreetingNameIssue() throws Exception { + final Model model = parser.parse("Hello GreetingNameTooLong!"); + helper.assertError(model, HelloWorldPackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.NAMELENGTH); + } + + /* + * Tests that both validations emerging from the Java validator as well as such emerging from the check based + * validator appear. (Fixed by CheckCompositeEValidator). + */ + @Test + //@BugTest("AIG-709") // this plugin should be ACF independent + public void testBugAig709() throws Exception { + final Model model = parser.parse("Hello GreetingNameTooLong!"); + helper.assertError(model, HelloWorldPackage.Literals.GREETING, ExecutionEnvironmentIssueCodes.NAMELENGTH); + helper.assertWarning(model, HelloWorldPackage.Literals.GREETING, IssueCodes.GREETING_NAME_PREFIX); + } + + @Test + public void testLibraryInjection() throws Exception { + final Model model = parser.parse("Hello Peter!"); + helper.assertWarning(model, HelloWorldPackage.Literals.GREETING, LibraryChecksIssueCodes.CHECK_CATALOG_IS_ACTIVE); + helper.assertNoError(model, LibraryChecksIssueCodes.CACHE_DOESNT_WORK); + helper.assertNoError(model, LibraryChecksIssueCodes.CACHE_INJECTION_FAILED); + helper.assertNoError(model, LibraryChecksIssueCodes.FORMAL_PARAMETERS); + } +} diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckExecutionEnvironmentProjectTest.xtend b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckExecutionEnvironmentProjectTest.xtend deleted file mode 100644 index e3df63d53d..0000000000 --- a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/check/CheckExecutionEnvironmentProjectTest.xtend +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.sample.helloworld.check - -import com.avaloq.tools.ddk.check.core.test.AbstractCheckTestCase -import com.google.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.util.ParseHelper -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test - -import com.avaloq.tools.ddk.sample.helloworld.ui.internal.HelloworldActivator -import com.avaloq.tools.ddk.sample.helloworld.helloWorld.Model -import com.avaloq.tools.ddk.sample.helloworld.helloWorld.HelloWorldPackage -import com.avaloq.tools.ddk.sample.helloworld.validation.ExecutionEnvironmentIssueCodes -import com.avaloq.tools.ddk.sample.helloworld.validation.LibraryChecksIssueCodes -import com.avaloq.tools.ddk.sample.helloworld.validation.IssueCodes -import com.avaloq.tools.ddk.sample.helloworld.ui.HelloWorldUiInjectorProvider - -@InjectWith(HelloWorldUiInjectorProvider) -@ExtendWith(typeof(InjectionExtension)) -class CheckExecutionEnvironmentProjectTest extends AbstractCheckTestCase { - - @Inject - ValidationTestHelper helper - - @Inject - ParseHelper parser - - override getInjector() { - HelloworldActivator::instance.getInjector(HelloworldActivator::COM_AVALOQ_TOOLS_DDK_SAMPLE_HELLOWORLD_HELLOWORLD) - } - - @Test - def void testFranz() { - val model = parser.parse("Hello Franz!") - helper.assertError(model, HelloWorldPackage.Literals::GREETING, ExecutionEnvironmentIssueCodes::FRANZNAME) - } - - @Test - def void testFrans() { - val model = parser.parse("Hello Frans!") - helper.assertNoError(model, ExecutionEnvironmentIssueCodes::FRANZNAME) - helper.assertNoError(model, ExecutionEnvironmentIssueCodes::NAMELENGTH) - } - - @Test - def void testGreetingNameIssue() { - val model = parser.parse("Hello GreetingNameTooLong!") - helper.assertError(model, HelloWorldPackage.Literals::GREETING, ExecutionEnvironmentIssueCodes::NAMELENGTH) - } - - /* - * Tests that both validations emerging from the Java validator as well as such emerging from the check based - * validator appear. (Fixed by CheckCompositeEValidator). - */ - @Test - //@BugTest("AIG-709") // this plugin should be ACF independent - def void testBugAig709() { - val model = parser.parse("Hello GreetingNameTooLong!") - helper.assertError(model, HelloWorldPackage.Literals::GREETING, ExecutionEnvironmentIssueCodes::NAMELENGTH) - helper.assertWarning(model, HelloWorldPackage.Literals::GREETING, IssueCodes::GREETING_NAME_PREFIX) - } - - @Test - def void testLibraryInjection() { - val model = parser.parse("Hello Peter!"); - helper.assertWarning(model, HelloWorldPackage.Literals::GREETING, LibraryChecksIssueCodes::CHECK_CATALOG_IS_ACTIVE); - helper.assertNoError(model, LibraryChecksIssueCodes::CACHE_DOESNT_WORK); - helper.assertNoError(model, LibraryChecksIssueCodes::CACHE_INJECTION_FAILED); - helper.assertNoError(model, LibraryChecksIssueCodes::FORMAL_PARAMETERS); - } -} diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/label/IssueLabelTest.java b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/label/IssueLabelTest.java new file mode 100644 index 0000000000..601ae7ef7c --- /dev/null +++ b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/label/IssueLabelTest.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.sample.helloworld.label; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Map; + +import com.avaloq.tools.ddk.check.runtime.label.ICheckRuleLabelProvider; +import com.avaloq.tools.ddk.sample.helloworld.validation.LibraryChecksIssueCodes; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import org.junit.jupiter.api.Test; + +/** + * End-to-end test for getting Check labels. + */ +public class IssueLabelTest { + + /** + * End-to-end test for getting Check labels. + */ + @Test + public void testGetLabel() { + + // ARRANGE + final ICheckRuleLabelProvider checkRuleLabelProvider = Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + } + }).getInstance(ICheckRuleLabelProvider.class); + + final Map expectedMap = Map.of( + // @Format-Off + LibraryChecksIssueCodes.CACHE_DOESNT_WORK, "Cache doesn't work", + LibraryChecksIssueCodes.CACHE_INJECTION_FAILED, "Cache injection failed", + LibraryChecksIssueCodes.CHECK_CATALOG_IS_ACTIVE, "Check catalog is active", + LibraryChecksIssueCodes.FORMAL_PARAMETERS, "Formal Parameters" + // @Format-On + ); + + for (final Map.Entry entry : expectedMap.entrySet()) { + // ACT + final String label = checkRuleLabelProvider.getLabel(entry.getKey()); + + // ASSERT + assertNotNull(label, "Label should be returned for key " + entry.getKey()); + assertEquals(entry.getValue(), label, "Correct label should be returned for key " + entry.getKey()); + } + } +} diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/label/IssueLabelTest.xtend b/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/label/IssueLabelTest.xtend deleted file mode 100644 index 3cf4767ff0..0000000000 --- a/com.avaloq.tools.ddk.sample.helloworld.ui.test/src/com/avaloq/tools/ddk/sample/helloworld/label/IssueLabelTest.xtend +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.sample.helloworld.label - -import com.google.inject.AbstractModule -import com.google.inject.Guice - -import com.avaloq.tools.ddk.sample.helloworld.validation.LibraryChecksIssueCodes -import com.avaloq.tools.ddk.check.runtime.label.ICheckRuleLabelProvider -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertNotNull -import static org.junit.jupiter.api.Assertions.assertEquals - -/** - * End-to-end test for getting Check labels. - */ -class IssueLabelTest { - - /** - * End-to-end test for getting Check labels. - */ - @Test - def testGetLabel() { - - // ARRANGE - val checkRuleLabelProvider = Guice.createInjector(new AbstractModule() { - protected override configure() {} - }).getInstance(ICheckRuleLabelProvider); - - val expectedMap = #{ - // @Format-Off - LibraryChecksIssueCodes.CACHE_DOESNT_WORK -> "Cache doesn't work", - LibraryChecksIssueCodes.CACHE_INJECTION_FAILED -> "Cache injection failed", - LibraryChecksIssueCodes.CHECK_CATALOG_IS_ACTIVE -> "Check catalog is active", - LibraryChecksIssueCodes.FORMAL_PARAMETERS -> "Formal Parameters" - // @Format-On - } - - for (entry : expectedMap.entrySet) { - // ACT - val label = checkRuleLabelProvider.getLabel(entry.key); - - // ASSERT - assertNotNull(label,"Label should be returned for key " + entry.key); - assertEquals(entry.value, label, "Correct label should be returned for key " + entry.key); - } - } -} diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui.test/xtend-gen/.gitignore b/com.avaloq.tools.ddk.sample.helloworld.ui.test/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.sample.helloworld.ui/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.sample.helloworld.ui/META-INF/MANIFEST.MF index 598b442862..ecebf6c89c 100644 --- a/com.avaloq.tools.ddk.sample.helloworld.ui/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.sample.helloworld.ui/META-INF/MANIFEST.MF @@ -16,12 +16,12 @@ Require-Bundle: com.avaloq.tools.ddk.sample.helloworld, org.eclipse.compare, org.eclipse.xtext.builder, org.eclipse.xtext.xbase.lib, - org.eclipse.xtend.lib;resolution:=optional, org.antlr.runtime, com.avaloq.tools.ddk.check.runtime.core, com.avaloq.tools.ddk.xtext.ui, com.avaloq.tools.ddk.sample.helloworld.ide, - com.avaloq.tools.ddk.xtext.ide + com.avaloq.tools.ddk.xtext.ide, + org.eclipse.xtend.lib Import-Package: org.apache.log4j Bundle-RequiredExecutionEnvironment: JavaSE-21 Export-Package: com.avaloq.tools.ddk.sample.helloworld.ui.quickfix, diff --git a/com.avaloq.tools.ddk.sample.helloworld/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.sample.helloworld/META-INF/MANIFEST.MF index c48639acf1..1734cf32c7 100644 --- a/com.avaloq.tools.ddk.sample.helloworld/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.sample.helloworld/META-INF/MANIFEST.MF @@ -15,7 +15,6 @@ Require-Bundle: org.eclipse.xtext, org.eclipse.xtext.util, org.eclipse.xtext.ide, org.eclipse.emf.common, - org.eclipse.xtend.lib, com.avaloq.tools.ddk.xtext, org.eclipse.emf.mwe2.launch;resolution:=optional, org.eclipse.core.runtime, diff --git a/com.avaloq.tools.ddk.xtext.check.generator/.classpath b/com.avaloq.tools.ddk.xtext.check.generator/.classpath index 26f051475d..a17f989fe8 100644 --- a/com.avaloq.tools.ddk.xtext.check.generator/.classpath +++ b/com.avaloq.tools.ddk.xtext.check.generator/.classpath @@ -1,7 +1,6 @@ - diff --git a/com.avaloq.tools.ddk.xtext.check.generator/build.properties b/com.avaloq.tools.ddk.xtext.check.generator/build.properties index 3820ac88f3..6b7927735f 100644 --- a/com.avaloq.tools.ddk.xtext.check.generator/build.properties +++ b/com.avaloq.tools.ddk.xtext.check.generator/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/CheckValidatorFragment2.xtend b/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/CheckValidatorFragment2.java similarity index 53% rename from com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/CheckValidatorFragment2.xtend rename to com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/CheckValidatorFragment2.java index 64899f75c7..54198fe24a 100644 --- a/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/CheckValidatorFragment2.xtend +++ b/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/CheckValidatorFragment2.java @@ -9,24 +9,26 @@ * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.check.generator +package com.avaloq.tools.ddk.xtext.check.generator; -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess -import com.avaloq.tools.ddk.check.runtime.validation.AbstractCheckValidator -import com.avaloq.tools.ddk.check.runtime.validation.DefaultCheckValidator +import com.avaloq.tools.ddk.check.runtime.validation.AbstractCheckValidator; +import com.avaloq.tools.ddk.check.runtime.validation.DefaultCheckValidator; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; -class CheckValidatorFragment2 extends AbstractXtextGeneratorFragment { +@SuppressWarnings("nls") +public class CheckValidatorFragment2 extends AbstractXtextGeneratorFragment { - static val RUNTIME_PLUGIN = "com.avaloq.tools.ddk.check.runtime.core" + private static final String RUNTIME_PLUGIN = "com.avaloq.tools.ddk.check.runtime.core"; - override generate() { - new GuiceModuleAccess.BindingFactory().addTypeToTypeEagerSingleton(AbstractCheckValidator.typeRef, - DefaultCheckValidator.typeRef).contributeTo(language.runtimeGenModule) + @Override + public void generate() { + new GuiceModuleAccess.BindingFactory().addTypeToTypeEagerSingleton(TypeReference.typeRef(AbstractCheckValidator.class), + TypeReference.typeRef(DefaultCheckValidator.class)).contributeTo(getLanguage().getRuntimeGenModule()); - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest.requiredBundles += RUNTIME_PLUGIN + if (getProjectConfig().getRuntime().getManifest() != null) { + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add(RUNTIME_PLUGIN); } } -} \ No newline at end of file +} diff --git a/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/quickfix/CheckQuickfixProviderFragment2.java b/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/quickfix/CheckQuickfixProviderFragment2.java new file mode 100644 index 0000000000..895fd952f7 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/quickfix/CheckQuickfixProviderFragment2.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.check.generator.quickfix; + +import com.google.inject.Inject; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.TypeReference; + +@SuppressWarnings("nls") +public class CheckQuickfixProviderFragment2 extends AbstractXtextGeneratorFragment { + + private static final String UI_PLUGIN = "com.avaloq.tools.ddk.check.runtime.ui"; + + @Inject + private XtextGeneratorNaming xtextGeneratorNaming; + + @Inject + private FileAccessFactory fileAccessFactory; + + protected TypeReference getQuickfixProviderClass(final Grammar g) { + return new TypeReference( + xtextGeneratorNaming.getEclipsePluginBasePackage(g) + ".quickfix." + GrammarUtil.getSimpleName(g) + "QuickfixProvider" + ); + } + + @Override + public void generate() { + if (getProjectConfig().getEclipsePlugin().getManifest() != null) { + getProjectConfig().getEclipsePlugin().getManifest().getRequiredBundles().add(UI_PLUGIN); + } + + if (getProjectConfig().getEclipsePlugin().getSrc() != null) { + generateQuickfixProvider(); + } + + if (getProjectConfig().getEclipsePlugin().getPluginXml() != null) { + addRegistrationToPluginXml(); + } + } + + protected void generateQuickfixProvider() { + // CHECKSTYLE:CONSTANTS-OFF + StringConcatenationClient content = new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation builder) { + builder.append("public class "); + builder.append(getQuickfixProviderClass(getGrammar()).getSimpleName()); + builder.append(" extends DefaultCheckQuickfixProvider {"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append("// @Fix(MyJavaValidator.INVALID_NAME)"); + builder.newLine(); + builder.append("// public void capitalizeName(final Issue issue, IssueResolutionAcceptor acceptor) {"); + builder.newLine(); + builder.append("// acceptor.accept(issue, \"Capitalize name\", \"Capitalize the name.\", \"upcase.png\", new IModification() {"); + builder.newLine(); + builder.append("// public void apply(IModificationContext context) throws BadLocationException {"); + builder.newLine(); + builder.append("// IXtextDocument xtextDocument = context.getXtextDocument();"); + builder.newLine(); + builder.append("// String firstLetter = xtextDocument.get(issue.getOffset(), 1);"); + builder.newLine(); + builder.append("// xtextDocument.replace(issue.getOffset(), 1, firstLetter.toUpperCase());"); + builder.newLine(); + builder.append("// }"); + builder.newLine(); + builder.append("// });"); + builder.newLine(); + builder.append("// }"); + builder.newLine(); + builder.newLine(); + builder.append("}"); + builder.newLine(); + } + }; + // CHECKSTYLE:CONSTANTS-ON + fileAccessFactory.createJavaFile(getQuickfixProviderClass(getGrammar()), content).writeTo(getProjectConfig().getEclipsePlugin().getSrc()); + } + + protected void addRegistrationToPluginXml() { + final TypeReference executableExtensionFactory = xtextGeneratorNaming.getEclipsePluginExecutableExtensionFactory(getGrammar()); + // CHECKSTYLE:CONSTANTS-OFF + StringConcatenation builder = new StringConcatenation(); + builder.append(""); + builder.newLine(); + builder.append(""); + builder.newLine(); + builder.append(" "); + builder.append(""); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(""); + builder.newLine(); + builder.append(""); + builder.newLine(); + // CHECKSTYLE:CONSTANTS-ON + getProjectConfig().getEclipsePlugin().getPluginXml().getEntries().add(builder.toString()); + } +} diff --git a/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/quickfix/CheckQuickfixProviderFragment2.xtend b/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/quickfix/CheckQuickfixProviderFragment2.xtend deleted file mode 100644 index 1d8885c764..0000000000 --- a/com.avaloq.tools.ddk.xtext.check.generator/src/com/avaloq/tools/ddk/xtext/check/generator/quickfix/CheckQuickfixProviderFragment2.xtend +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.check.generator.quickfix - -import com.google.inject.Inject -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import org.eclipse.xtext.xtext.generator.model.TypeReference - -import static extension org.eclipse.xtext.GrammarUtil.* - -class CheckQuickfixProviderFragment2 extends AbstractXtextGeneratorFragment { - - static val UI_PLUGIN = "com.avaloq.tools.ddk.check.runtime.ui" - - @Inject extension XtextGeneratorNaming - - @Inject FileAccessFactory fileAccessFactory - - def protected TypeReference getQuickfixProviderClass(Grammar g) { - return new TypeReference( - g.eclipsePluginBasePackage + ".quickfix." + g.simpleName + "QuickfixProvider" - ) - } - - override generate() { - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.eclipsePlugin.manifest.requiredBundles += UI_PLUGIN - } - - if (projectConfig.eclipsePlugin.src !== null) { - generateQuickfixProvider - } - - if (projectConfig.eclipsePlugin.pluginXml !== null) { - addRegistrationToPluginXml - } - } - - protected def generateQuickfixProvider() { - fileAccessFactory.createJavaFile(grammar.quickfixProviderClass, ''' - public class «grammar.quickfixProviderClass.simpleName» extends DefaultCheckQuickfixProvider { - - // @Fix(MyJavaValidator.INVALID_NAME) - // public void capitalizeName(final Issue issue, IssueResolutionAcceptor acceptor) { - // acceptor.accept(issue, "Capitalize name", "Capitalize the name.", "upcase.png", new IModification() { - // public void apply(IModificationContext context) throws BadLocationException { - // IXtextDocument xtextDocument = context.getXtextDocument(); - // String firstLetter = xtextDocument.get(issue.getOffset(), 1); - // xtextDocument.replace(issue.getOffset(), 1, firstLetter.toUpperCase()); - // } - // }); - // } - - } - ''').writeTo(projectConfig.eclipsePlugin.src) - } - - protected def addRegistrationToPluginXml() { - val executableExtensionFactory = grammar.eclipsePluginExecutableExtensionFactory - projectConfig.eclipsePlugin.pluginXml.entries += ''' - - - - - - ''' - } -} diff --git a/com.avaloq.tools.ddk.xtext.check.generator/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.check.generator/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.export.generator/.classpath b/com.avaloq.tools.ddk.xtext.export.generator/.classpath index 8d8d754076..eb603c114d 100644 --- a/com.avaloq.tools.ddk.xtext.export.generator/.classpath +++ b/com.avaloq.tools.ddk.xtext.export.generator/.classpath @@ -7,6 +7,5 @@ -
diff --git a/com.avaloq.tools.ddk.xtext.export.generator/build.properties b/com.avaloq.tools.ddk.xtext.export.generator/build.properties index 3820ac88f3..6b7927735f 100644 --- a/com.avaloq.tools.ddk.xtext.export.generator/build.properties +++ b/com.avaloq.tools.ddk.xtext.export.generator/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.export.generator/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFragment2.java b/com.avaloq.tools.ddk.xtext.export.generator/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFragment2.java new file mode 100644 index 0000000000..860e447299 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export.generator/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFragment2.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.export.generator; + +import com.avaloq.tools.ddk.xtext.export.ExportStandaloneSetup; +import com.avaloq.tools.ddk.xtext.resource.IFingerprintComputer; +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.naming.IQualifiedNameProvider; +import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; +import org.eclipse.xtext.resource.IFragmentProvider; +import org.eclipse.xtext.resource.IResourceDescription; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; + +@SuppressWarnings("nls") +public class ExportFragment2 extends AbstractXtextGeneratorFragment { + + @Inject + private XtextGeneratorNaming xtextGeneratorNaming; + + private boolean hasExports = true; + + /** + * Class-wide logger. + */ + private static final Logger LOGGER = LogManager.getLogger(ExportFragment2.class); + + private static final String DDK_XTEXT_RUNTIME_BUNDLE = "com.avaloq.tools.ddk.xtext"; + + public void setHasExports(final boolean hasExports) { + this.hasExports = hasExports; + } + + @Override + public void generate() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("executing generate for " + getClass().getName()); + } + final String namingPackage = xtextGeneratorNaming.getRuntimeBasePackage(getGrammar()) + ".naming"; + final String namingPrefix = namingPackage + "." + GrammarUtil.getSimpleName(getGrammar()); + final String resourcePackage = xtextGeneratorNaming.getRuntimeBasePackage(getGrammar()) + ".resource"; + final String resourcePrefix = resourcePackage + "." + GrammarUtil.getSimpleName(getGrammar()); + + if (hasExports) { + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(IQualifiedNameProvider.class), new TypeReference(namingPrefix + "ExportedNamesProvider")) + .addTypeToType(TypeReference.typeRef(IFingerprintComputer.class), new TypeReference(resourcePrefix + "FingerprintComputer")) + .addTypeToType(TypeReference.typeRef(IDefaultResourceDescriptionStrategy.class), new TypeReference(resourcePrefix + "ResourceDescriptionStrategy")) + .addTypeToType(TypeReference.typeRef(IFragmentProvider.class), new TypeReference(resourcePrefix + "FragmentProvider")) + .addTypeToType(TypeReference.typeRef(IResourceDescription.Manager.class), new TypeReference(resourcePrefix + "ResourceDescriptionManager")) + .contributeTo(getLanguage().getRuntimeGenModule()); + } else { + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(IResourceDescription.Manager.class), new TypeReference(resourcePrefix + "ResourceDescriptionManager")) + .contributeTo(getLanguage().getRuntimeGenModule()); + } + + if (getProjectConfig().getRuntime().getManifest() != null) { + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add("org.eclipse.emf.ecore"); + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add(DDK_XTEXT_RUNTIME_BUNDLE); + if (hasExports) { + getProjectConfig().getRuntime().getManifest().getExportedPackages().add(namingPackage); + } + + getProjectConfig().getRuntime().getManifest().getExportedPackages().add(resourcePackage); + } + if (getProjectConfig().getEclipsePlugin().getManifest() != null) { + getProjectConfig().getEclipsePlugin().getManifest().getRequiredBundles().add(DDK_XTEXT_RUNTIME_BUNDLE); + } + + ExportStandaloneSetup.doSetup(); + } +} diff --git a/com.avaloq.tools.ddk.xtext.export.generator/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFragment2.xtend b/com.avaloq.tools.ddk.xtext.export.generator/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFragment2.xtend deleted file mode 100644 index 574c429435..0000000000 --- a/com.avaloq.tools.ddk.xtext.export.generator/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFragment2.xtend +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.ExportStandaloneSetup -import com.avaloq.tools.ddk.xtext.resource.IFingerprintComputer -import com.google.inject.Inject -import org.apache.logging.log4j.Logger -import org.apache.logging.log4j.LogManager; -import org.eclipse.xtext.naming.IQualifiedNameProvider -import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy -import org.eclipse.xtext.resource.IFragmentProvider -import org.eclipse.xtext.resource.IResourceDescription -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess -import org.eclipse.xtext.xtext.generator.model.TypeReference - -import static org.eclipse.xtext.GrammarUtil.* - -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* - -class ExportFragment2 extends AbstractXtextGeneratorFragment { - - @Inject extension XtextGeneratorNaming - var hasExports = true - /** - * Class-wide logger. - */ - static final Logger LOGGER = LogManager::getLogger(ExportFragment2) - - val DDK_XTEXT_RUNTIME_BUNDLE = "com.avaloq.tools.ddk.xtext" - - def setHasExports (boolean hasExports) { - this.hasExports = hasExports - } - - override void generate() { - if (LOGGER.isInfoEnabled()) { - LOGGER.info('''executing generate for «getClass().getName()»'''.toString) - } - val namingPackage = grammar.runtimeBasePackage + '.naming' - val namingPrefix = namingPackage + '.' + getSimpleName(grammar) - val resourcePackage = grammar.runtimeBasePackage + '.resource' - val resourcePrefix = resourcePackage + '.' + getSimpleName(grammar) - - if (hasExports) { - new GuiceModuleAccess.BindingFactory() - .addTypeToType(IQualifiedNameProvider.typeRef, new TypeReference(namingPrefix + "ExportedNamesProvider")) - .addTypeToType(IFingerprintComputer.typeRef, new TypeReference(resourcePrefix + "FingerprintComputer")) - .addTypeToType(IDefaultResourceDescriptionStrategy.typeRef, new TypeReference(resourcePrefix + "ResourceDescriptionStrategy")) - .addTypeToType(IFragmentProvider.typeRef, new TypeReference(resourcePrefix + "FragmentProvider")) - .addTypeToType(IResourceDescription.Manager.typeRef, new TypeReference(resourcePrefix + "ResourceDescriptionManager")) - .contributeTo(language.runtimeGenModule) - } - else { - new GuiceModuleAccess.BindingFactory() - .addTypeToType(IResourceDescription.Manager.typeRef, new TypeReference(resourcePrefix + "ResourceDescriptionManager")) - .contributeTo(language.runtimeGenModule) - } - - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest.requiredBundles += "org.eclipse.emf.ecore" - projectConfig.runtime.manifest.requiredBundles += DDK_XTEXT_RUNTIME_BUNDLE - if (hasExports) { - projectConfig.runtime.manifest.exportedPackages += namingPackage - } - - projectConfig.runtime.manifest.exportedPackages += resourcePackage - } - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.eclipsePlugin.manifest.requiredBundles += DDK_XTEXT_RUNTIME_BUNDLE - } - - ExportStandaloneSetup.doSetup() - } -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.export.generator/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.export.generator/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.export.ide/.classpath b/com.avaloq.tools.ddk.xtext.export.ide/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.xtext.export.ide/.classpath +++ b/com.avaloq.tools.ddk.xtext.export.ide/.classpath @@ -6,11 +6,6 @@
- - - - - diff --git a/com.avaloq.tools.ddk.xtext.export.ide/.project b/com.avaloq.tools.ddk.xtext.export.ide/.project index 47ffefb3f5..2d3d91bb56 100644 --- a/com.avaloq.tools.ddk.xtext.export.ide/.project +++ b/com.avaloq.tools.ddk.xtext.export.ide/.project @@ -65,35 +65,5 @@ 1 PARENT-1-PROJECT_LOC/ddk-configuration/.pmd - - .settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - - - .settings/org.eclipse.core.resources.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.resources.prefs - - - .settings/org.eclipse.core.runtime.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.runtime.prefs - - - .settings/org.eclipse.jdt.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.core.prefs - - - .settings/org.eclipse.jdt.ui.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.ui.prefs - - - .settings/org.eclipse.pde.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.pde.core.prefs - diff --git a/com.avaloq.tools.ddk.xtext.export.ide/build.properties b/com.avaloq.tools.ddk.xtext.export.ide/build.properties index 3f5513de7b..2d6ac57da2 100644 --- a/com.avaloq.tools.ddk.xtext.export.ide/build.properties +++ b/com.avaloq.tools.ddk.xtext.export.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.export.ide/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.export.ide/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.export/.classpath b/com.avaloq.tools.ddk.xtext.export/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.xtext.export/.classpath +++ b/com.avaloq.tools.ddk.xtext.export/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.export/build.properties b/com.avaloq.tools.ddk.xtext.export/build.properties index 5839f7a848..b533d62244 100644 --- a/com.avaloq.tools.ddk.xtext.export/build.properties +++ b/com.avaloq.tools.ddk.xtext.export/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ .,\ plugin.xml,\ diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFeatureExtensionGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFeatureExtensionGenerator.java new file mode 100644 index 0000000000..a84501ea4a --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFeatureExtensionGenerator.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.inject.Inject; + + +@SuppressWarnings({"PMD.UnusedFormalParameter", "nls"}) +public class ExportFeatureExtensionGenerator { + + @Inject + private Naming naming; + + @Inject + private ExportGeneratorX exportGeneratorX; + + public CharSequence generate(final ExportModel it, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + // CHECKSTYLE:CONSTANTS-OFF + final StringBuilder sb = new StringBuilder(2048); + sb.append("package "); + sb.append(naming.toJavaPackage(exportGeneratorX.getExportFeatureExtension(it))); + sb.append(";\n"); + sb.append('\n'); + sb.append("import org.eclipse.xtext.naming.IQualifiedNameProvider;\n"); + sb.append("import com.avaloq.tools.ddk.xtext.resource.AbstractExportFeatureExtension;\n"); + sb.append("import com.avaloq.tools.ddk.xtext.resource.AbstractResourceDescriptionStrategy;\n"); + sb.append("import com.avaloq.tools.ddk.xtext.resource.AbstractSelectorFragmentProvider;\n"); + sb.append("import com.avaloq.tools.ddk.xtext.resource.IFingerprintComputer;\n"); + sb.append("import com.google.inject.Inject;\n"); + sb.append('\n'); + sb.append("import "); + sb.append(exportGeneratorX.getExportedNamesProvider(it)); + sb.append(";\n"); + sb.append('\n'); + sb.append('\n'); + sb.append("public class "); + sb.append(naming.toSimpleName(exportGeneratorX.getExportFeatureExtension(it))); + sb.append(" extends AbstractExportFeatureExtension {\n"); + sb.append('\n'); + sb.append(" @Inject\n"); + sb.append(" private "); + sb.append(naming.toSimpleName(exportGeneratorX.getExportedNamesProvider(it))); + sb.append(" namesProvider;\n"); + sb.append('\n'); + sb.append(" @Inject\n"); + sb.append(" private "); + sb.append(naming.toSimpleName(exportGeneratorX.getFingerprintComputer(it))); + sb.append(" fingerprintComputer;\n"); + sb.append('\n'); + sb.append(" @Inject\n"); + sb.append(" private "); + sb.append(naming.toSimpleName(exportGeneratorX.getFragmentProvider(it))); + sb.append(" fragmentProvider;\n"); + sb.append('\n'); + sb.append(" @Inject\n"); + sb.append(" private "); + sb.append(naming.toSimpleName(exportGeneratorX.getResourceDescriptionStrategy(it))); + sb.append(" resourceDescriptionStrategy;\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" protected IQualifiedNameProvider getNamesProvider() {\n"); + sb.append(" return namesProvider;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" protected IFingerprintComputer getFingerprintComputer() {\n"); + sb.append(" return fingerprintComputer;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" protected AbstractSelectorFragmentProvider getFragmentProvider() {\n"); + sb.append(" return fragmentProvider;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" protected AbstractResourceDescriptionStrategy getResourceDescriptionStrategy() {\n"); + sb.append(" return resourceDescriptionStrategy;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append("}\n"); + // CHECKSTYLE:CONSTANTS-ON + return sb; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFeatureExtensionGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFeatureExtensionGenerator.xtend deleted file mode 100644 index a357b85f3b..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportFeatureExtensionGenerator.xtend +++ /dev/null @@ -1,77 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject - -class ExportFeatureExtensionGenerator { - - @Inject extension Naming - @Inject extension ExportGeneratorX - - def generate(ExportModel it, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - ''' - package «exportFeatureExtension.toJavaPackage»; - - import org.eclipse.xtext.naming.IQualifiedNameProvider; - import com.avaloq.tools.ddk.xtext.resource.AbstractExportFeatureExtension; - import com.avaloq.tools.ddk.xtext.resource.AbstractResourceDescriptionStrategy; - import com.avaloq.tools.ddk.xtext.resource.AbstractSelectorFragmentProvider; - import com.avaloq.tools.ddk.xtext.resource.IFingerprintComputer; - import com.google.inject.Inject; - - import «exportedNamesProvider»; - - - public class «exportFeatureExtension.toSimpleName» extends AbstractExportFeatureExtension { - - @Inject - private «exportedNamesProvider.toSimpleName» namesProvider; - - @Inject - private «fingerprintComputer.toSimpleName» fingerprintComputer; - - @Inject - private «fragmentProvider.toSimpleName» fragmentProvider; - - @Inject - private «resourceDescriptionStrategy.toSimpleName» resourceDescriptionStrategy; - - @Override - protected IQualifiedNameProvider getNamesProvider() { - return namesProvider; - } - - @Override - protected IFingerprintComputer getFingerprintComputer() { - return fingerprintComputer; - } - - @Override - protected AbstractSelectorFragmentProvider getFragmentProvider() { - return fragmentProvider; - } - - @Override - protected AbstractResourceDescriptionStrategy getResourceDescriptionStrategy() { - return resourceDescriptionStrategy; - } - - } - ''' - } - -} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGenerator.java new file mode 100644 index 0000000000..bb51cbef22 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGenerator.java @@ -0,0 +1,143 @@ +/* + * generated by Xtext + */ +package com.avaloq.tools.ddk.xtext.export.generator; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.generator.IFileSystemAccess; +import org.eclipse.xtext.generator.IFileSystemAccess2; +import org.eclipse.xtext.generator.IGenerator2; +import org.eclipse.xtext.generator.IGeneratorContext; + +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.inject.Inject; + + +/** + * Generates code from your model files on save. + * + * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation + */ +@SuppressWarnings("nls") +public class ExportGenerator implements IGenerator2 { + + @Inject + private ExportGeneratorSupport generatorSupport; + @Inject + private Naming naming; + @Inject + private ExportGeneratorX exportGeneratorX; + + @Inject + private GenModelUtilX genModelUtil; + + @Inject + private ExportedNamesProviderGenerator exportedNamesProviderGenerator; + @Inject + private ResourceDescriptionManagerGenerator resourceDescriptionManagerGenerator; + @Inject + private ResourceDescriptionStrategyGenerator resourceDescriptionStrategyGenerator; + @Inject + private ResourceDescriptionConstantsGenerator resourceDescriptionConstantsGenerator; + @Inject + private FingerprintComputerGenerator fingerprintComputerGenerator; + @Inject + private FragmentProviderGenerator fragmentProviderGenerator; + @Inject + private ExportFeatureExtensionGenerator exportFeatureExtensionGenerator; + + private CompilationContext compilationContext; + + @Override + public void doGenerate(final Resource input, final IFileSystemAccess2 fsa, final IGeneratorContext context) { + if (input == null || input.getContents().isEmpty() || !(input.getContents().get(0) instanceof ExportModel)) { + return; + } + final ExportModel model = (ExportModel) input.getContents().get(0); + genModelUtil.setResource(model.eResource()); + IProject project = null; + if (input.getURI().isPlatformResource()) { + final org.eclipse.core.resources.IResource res = ResourcesPlugin.getWorkspace().getRoot().findMember(input.getURI().toPlatformString(true)); + if (res != null) { + project = res.getProject(); + } + } + + generatorSupport.executeWithProjectResourceLoader(project, () -> { + compilationContext = generatorSupport.getCompilationContext(model, genModelUtil); + + generateExportedNamesProvider(model, fsa); + generateResourceDescriptionManager(model, fsa); + generateResourceDescriptionStrategy(model, fsa); + generateResourceDescriptionConstants(model, fsa); + generateFingerprintComputer(model, fsa); + generateFragmentProvider(model, fsa); + generateFeatureExtension(model, fsa); + }); + } + + public void generateExportedNamesProvider(final ExportModel model, final IFileSystemAccess fsa) { + final String fileName = naming.toFileName(exportGeneratorX.getExportedNamesProvider(model)); + fsa.generateFile(fileName, exportedNamesProviderGenerator.generate(model, compilationContext, genModelUtil)); + } + + public void generateResourceDescriptionManager(final ExportModel model, final IFileSystemAccess fsa) { + if (!model.isExtension()) { + final String fileName = naming.toFileName(exportGeneratorX.getResourceDescriptionManager(model)); + fsa.generateFile(fileName, ExportOutputConfigurationProvider.STUB_OUTPUT, resourceDescriptionManagerGenerator.generate(model, compilationContext, genModelUtil)); + } + } + + public void generateResourceDescriptionStrategy(final ExportModel model, final IFileSystemAccess fsa) { + final String fileName = naming.toFileName(exportGeneratorX.getResourceDescriptionStrategy(model)); + fsa.generateFile(fileName, resourceDescriptionStrategyGenerator.generate(model, compilationContext, genModelUtil)); + } + + public void generateResourceDescriptionConstants(final ExportModel model, final IFileSystemAccess fsa) { + final String fileName = naming.toFileName(exportGeneratorX.getResourceDescriptionConstants(model)); + fsa.generateFile(fileName, resourceDescriptionConstantsGenerator.generate(model, compilationContext, genModelUtil)); + } + + public void generateFingerprintComputer(final ExportModel model, final IFileSystemAccess fsa) { + final String fileName = naming.toFileName(exportGeneratorX.getFingerprintComputer(model)); + fsa.generateFile(fileName, fingerprintComputerGenerator.generate(model, compilationContext, genModelUtil)); + } + + public void generateFragmentProvider(final ExportModel model, final IFileSystemAccess fsa) { + final String fileName = naming.toFileName(exportGeneratorX.getFragmentProvider(model)); + if (model.getExports().stream().anyMatch(e -> e.isFingerprint() && e.getFragmentAttribute() != null) || model.isExtension()) { + fsa.generateFile(fileName, fragmentProviderGenerator.generate(model, compilationContext, genModelUtil)); + } else if (!model.getExports().isEmpty()) { + fsa.generateFile(fileName, String.format(""" + package %s; + + import com.avaloq.tools.ddk.xtext.linking.ShortFragmentProvider; + + + public class %s extends ShortFragmentProvider { + + } + """, naming.toJavaPackage(exportGeneratorX.getFragmentProvider(model)), naming.toSimpleName(exportGeneratorX.getFragmentProvider(model)))); + } + } + + public void generateFeatureExtension(final ExportModel model, final IFileSystemAccess fsa) { + if (model.isExtension()) { + final String fileName = naming.toFileName(exportGeneratorX.getExportFeatureExtension(model)); + fsa.generateFile(fileName, exportFeatureExtensionGenerator.generate(model, compilationContext, genModelUtil)); + } + } + + @Override + public void afterGenerate(final Resource input, final IFileSystemAccess2 fsa, final IGeneratorContext context) { + } + + @Override + public void beforeGenerate(final Resource input, final IFileSystemAccess2 fsa, final IGeneratorContext context) { + } +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGenerator.xtend deleted file mode 100644 index 18a87369e2..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGenerator.xtend +++ /dev/null @@ -1,136 +0,0 @@ -/* - * generated by Xtext - */ -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject -import org.eclipse.core.resources.IProject -import org.eclipse.core.resources.ResourcesPlugin -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.IFileSystemAccess -import org.eclipse.xtext.generator.IGenerator2 -import org.eclipse.xtext.generator.IGeneratorContext -import org.eclipse.xtext.generator.IFileSystemAccess2 - -/** - * Generates code from your model files on save. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#code-generation - */ -class ExportGenerator implements IGenerator2 { - - @Inject - extension ExportGeneratorSupport generatorSupport - @Inject - extension Naming - @Inject - extension ExportGeneratorX - - @Inject - GenModelUtilX genModelUtil - - @Inject - ExportedNamesProviderGenerator exportedNamesProviderGenerator - @Inject - ResourceDescriptionManagerGenerator resourceDescriptionManagerGenerator - @Inject - ResourceDescriptionStrategyGenerator resourceDescriptionStrategyGenerator - @Inject - ResourceDescriptionConstantsGenerator resourceDescriptionConstantsGenerator - @Inject - FingerprintComputerGenerator fingerprintComputerGenerator - @Inject - FragmentProviderGenerator fragmentProviderGenerator - @Inject - ExportFeatureExtensionGenerator exportFeatureExtensionGenerator - - CompilationContext compilationContext - - override void doGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) { - if (input === null || input.contents.empty || !(input.contents.head instanceof ExportModel)) { - return - } - val model = input.contents.head as ExportModel - genModelUtil.resource = model.eResource - var IProject project = null - if (input.URI.isPlatformResource) { - val res = ResourcesPlugin.workspace.root.findMember(input.URI.toPlatformString(true)) - if (res !== null) { - project = res.project - } - } - - generatorSupport.executeWithProjectResourceLoader(project, [ - compilationContext = generatorSupport.getCompilationContext(model, genModelUtil) - - generateExportedNamesProvider(model, fsa) - generateResourceDescriptionManager(model, fsa) - generateResourceDescriptionStrategy(model, fsa) - generateResourceDescriptionConstants(model, fsa) - generateFingerprintComputer(model, fsa) - generateFragmentProvider(model, fsa) - generateFeatureExtension(model, fsa) - ]) - } - - def generateExportedNamesProvider(ExportModel model, IFileSystemAccess fsa) { - val fileName = model.exportedNamesProvider.toFileName - fsa.generateFile(fileName, exportedNamesProviderGenerator.generate(model, compilationContext, genModelUtil)) - } - - def generateResourceDescriptionManager(ExportModel model, IFileSystemAccess fsa) { - if(!model.extension){ - val fileName = model.resourceDescriptionManager.toFileName - fsa.generateFile(fileName, ExportOutputConfigurationProvider.STUB_OUTPUT, resourceDescriptionManagerGenerator.generate(model, compilationContext, genModelUtil)) - } - } - - def generateResourceDescriptionStrategy(ExportModel model, IFileSystemAccess fsa) { - val fileName = model.resourceDescriptionStrategy.toFileName - fsa.generateFile(fileName, resourceDescriptionStrategyGenerator.generate(model, compilationContext, genModelUtil)) - } - - def generateResourceDescriptionConstants(ExportModel model, IFileSystemAccess fsa) { - val fileName = model.resourceDescriptionConstants.toFileName - fsa.generateFile(fileName, resourceDescriptionConstantsGenerator.generate(model, compilationContext, genModelUtil)) - } - - def generateFingerprintComputer(ExportModel model, IFileSystemAccess fsa) { - val fileName = model.fingerprintComputer.toFileName - fsa.generateFile(fileName, fingerprintComputerGenerator.generate(model, compilationContext, genModelUtil)) - } - - def generateFragmentProvider(ExportModel model, IFileSystemAccess fsa) { - val fileName = model.fragmentProvider.toFileName - if (model.exports.exists(e|e.fingerprint && e.fragmentAttribute !== null) || model.isExtension) { - fsa.generateFile(fileName, fragmentProviderGenerator.generate(model, compilationContext, genModelUtil)) - } else if (!model.exports.empty){ - fsa.generateFile(fileName, ''' - package «model.fragmentProvider.toJavaPackage»; - - import com.avaloq.tools.ddk.xtext.linking.ShortFragmentProvider; - - - public class «model.fragmentProvider.toSimpleName» extends ShortFragmentProvider { - - } - ''') - } - } - - def generateFeatureExtension(ExportModel model, IFileSystemAccess fsa) { - if (model.extension) { - val fileName = model.exportFeatureExtension.toFileName - fsa.generateFile(fileName, exportFeatureExtensionGenerator.generate(model, compilationContext, genModelUtil)) - } - } - - override afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {} - - override beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {} - -} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.java new file mode 100644 index 0000000000..27e2bbbcfa --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.java @@ -0,0 +1,270 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.xtext.Grammar; + +import com.avaloq.tools.ddk.xtext.export.export.Export; +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.export.export.Interface; +import com.avaloq.tools.ddk.xtext.export.export.UserData; +import com.avaloq.tools.ddk.xtext.expression.generator.EClassComparator; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtil2; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtil; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.common.collect.ListMultimap; +import com.google.inject.Inject; + + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class ExportGeneratorX { + + private static final int URI_PACKAGE_START_INDEX = 3; + + @Inject + private Naming naming; + + public String getName(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + return uri.trimFileExtension().lastSegment(); + } + + public Grammar getGrammar(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // Grammar should be set correctly for export extensions, not yet for normal export sources + if (model.getTargetGrammar() != null) { + return model.getTargetGrammar(); + } + final org.eclipse.emf.ecore.resource.Resource grammarResource = model.eResource().getResourceSet().getResource( + uri.trimSegments(1).appendSegment(uri.trimFileExtension().lastSegment() + ".xtext"), true); + return grammarResource != null && !grammarResource.getContents().isEmpty() + ? (Grammar) grammarResource.getContents().get(0) + : null; + } + + public String getExportedNamesProvider(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return String.join(".", uri.segmentsList().subList(URI_PACKAGE_START_INDEX, uri.segmentCount() - 1)) + ".naming." + getName(model) + "ExportedNamesProvider"; + } + + public String getResourceDescriptionManager(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return String.join(".", uri.segmentsList().subList(URI_PACKAGE_START_INDEX, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionManager"; + } + + public String getResourceDescriptionManager(final Grammar grammar) { + return naming.toJavaPackage(grammar.getName()) + ".resource." + naming.toSimpleName(grammar.getName()) + "ResourceDescriptionManager"; + } + + public String getResourceDescriptionStrategy(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return String.join(".", uri.segmentsList().subList(URI_PACKAGE_START_INDEX, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionStrategy"; + } + + public String getResourceDescriptionConstants(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return String.join(".", uri.segmentsList().subList(URI_PACKAGE_START_INDEX, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionConstants"; + } + + public String getFingerprintComputer(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return String.join(".", uri.segmentsList().subList(URI_PACKAGE_START_INDEX, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FingerprintComputer"; + } + + public String getFragmentProvider(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) + return String.join(".", uri.segmentsList().subList(URI_PACKAGE_START_INDEX, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FragmentProvider"; + } + + public String getExportFeatureExtension(final ExportModel model) { + final org.eclipse.emf.common.util.URI uri = model.eResource().getURI(); + // TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above + return String.join(".", uri.segmentsList().subList(URI_PACKAGE_START_INDEX, uri.segmentCount() - 1)) + ".resource." + model.getName() + "ExportFeatureExtension"; + } + + /** + * Return the export specification for a type's supertype, if any, or null otherwise. + * + * @param it + * the export specification + * @return the export specification for the supertype, or {@code null} + */ + public Export superType(final Export it) { + if (it.getType().getESuperTypes().isEmpty()) { + return null; + } else { + return exportForType((ExportModel) it.eContainer(), it.getType().getESuperTypes().get(0)); + } + } + + /** + * Return the export specification for a given type. + * + * @param it + * the export model + * @param type + * the type to look for + * @return the matching export, or {@code null} + */ + public Export exportForType(final ExportModel it, final EClassifier type) { + return it.getExports().stream() + .filter(c -> c.getType().getName().equals(type.getName()) && c.getType().getEPackage().getNsURI().equals(type.getEPackage().getNsURI())) + .findFirst() + .orElse(null); + } + + /** + * Return a combined list of all user data specifications; including those on supertypes. + * + *

Public dispatcher for the dispatch methods.

+ * + * @param it + * the export or {@code null} + * @return combined list of user data + * @throws IllegalArgumentException + * if the parameter type is not handled + */ + public List allUserData(final Object it) { + if (it instanceof Export e) { + return _allUserData(e); + } else if (it == null) { + return _allUserData((Void) null); + } else { + throw new IllegalArgumentException("Unhandled parameter type: " + it); + } + } + + /** + * Return a combined list of all user data specifications; including those on supertypes. + * + * @param it + * the export + * @return combined list of user data + */ + protected List _allUserData(final Export it) { + final List result = allUserData(superType(it)); + result.addAll(it.getUserData()); + return result; + } + + /** + * Sentinel for the above. + * + * @param it + * unused sentinel parameter + * @return empty list + */ + protected List _allUserData(final Void it) { + return new ArrayList<>(); + } + + /** + * Return all the interface specification for the supertypes of a type. + * + * @param it + * the interface specification + * @param type + * the EClass type + * @return list of super interfaces + */ + public List getSuperInterfaces(final Interface it, final EClass type) { + if (type.getESuperTypes().isEmpty()) { + return new ArrayList<>(); + } else { + return getInterfacesForType((ExportModel) it.eContainer(), type.getESuperTypes().get(0)); + } + } + + /** + * Return all interface specifications that apply to a certain type; including those that are defined for supertypes. + * + * @param it + * the export model + * @param type + * the EClass type + * @return list of matching interfaces + */ + public List getInterfacesForType(final ExportModel it, final EClass type) { + final List filtered = it.getInterfaces().stream() + .filter(f -> f.getType() == type) + .collect(java.util.stream.Collectors.toList()); + final List result = filtered.isEmpty() ? new ArrayList<>() : new ArrayList<>(List.of(filtered.get(0))); + if (!type.getESuperTypes().isEmpty()) { + result.addAll(getInterfacesForType(it, type.getESuperTypes().get(0))); + } + return result; + } + + /** + * Returns a constant name for an Attribute field. + * + * @param attribute + * the attribute + * @param exportType + * the export type + * @return the constant name + */ + public String constantName(final EAttribute attribute, final EClass exportType) { + return (GenModelUtil2.format(exportType.getName()) + "__" + GenModelUtil2.format(attribute.getName())).toUpperCase(); + } + + /** + * Returns a constant name for a UserData field. + * + * @param data + * the user data + * @param exportType + * the export type + * @return the constant name + */ + public String constantName(final UserData data, final EClass exportType) { + return (GenModelUtil2.format(exportType.getName()) + "__" + GenModelUtil2.format(data.getName())).toUpperCase(); + } + + /** + * Sort exports according to package and type (more specific rules must come first). + * + * @param exports + * exports to sort + * @return sorted map of all exports + */ + public ListMultimap sortedExportsByEPackage(final Collection exports) { + return EClassComparator.sortedEPackageGroups(exports, e -> e.getType()); + } + + /** + * Returns a type map for the given exports. + * + * @param exports + * export objects to map + * @param grammar + * Xtext grammar + * @return mappings + */ + public Map typeMap(final Collection exports, final Grammar grammar) { + return GeneratorUtil.typeMap(exports, grammar, e -> e.getType()); + } +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend deleted file mode 100644 index b88fbe9000..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportGeneratorX.xtend +++ /dev/null @@ -1,187 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.Export -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.export.export.Interface -import com.avaloq.tools.ddk.xtext.export.export.UserData -import com.avaloq.tools.ddk.xtext.expression.generator.EClassComparator -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtil2 -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtil -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.common.collect.ListMultimap -import com.google.inject.Inject -import java.util.Collection -import java.util.List -import java.util.Map -import org.eclipse.emf.ecore.EAttribute -import org.eclipse.emf.ecore.EClass -import org.eclipse.emf.ecore.EClassifier -import org.eclipse.emf.ecore.EPackage -import org.eclipse.xtext.Grammar - -class ExportGeneratorX { - - @Inject - extension Naming - - def String getName(ExportModel model) { - val uri = model.eResource().getURI(); - return uri.trimFileExtension().lastSegment(); - } - - def Grammar getGrammar(ExportModel model) { - val uri = model.eResource.URI - // Grammar should be set correctly for export extensions, not yet for normal export sources - if(model.targetGrammar !== null) { - return model.targetGrammar; - } - val grammarResource = model.eResource.resourceSet.getResource(uri.trimSegments(1).appendSegment(uri.trimFileExtension.lastSegment + '.xtext'), true) - return grammarResource?.contents.head as Grammar - } - - def String getExportedNamesProvider(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".naming." + getName(model) + "ExportedNamesProvider"; - } - - def String getResourceDescriptionManager(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionManager"; - } - - def String getResourceDescriptionManager(Grammar grammar) { - return grammar.name.toJavaPackage + ".resource." + grammar.name.toSimpleName + "ResourceDescriptionManager"; - } - - def String getResourceDescriptionStrategy(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionStrategy"; - } - - def String getResourceDescriptionConstants(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "ResourceDescriptionConstants"; - } - - def String getFingerprintComputer(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FingerprintComputer"; - } - - def String getFragmentProvider(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO this is a hack; to support modularization we should probably add name to export models (as with scope models) - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + getName(model) + "FragmentProvider"; - } - - def String getExportFeatureExtension(ExportModel model) { - val uri = model.eResource().getURI(); - // TODO we still need to add a package to the models. Extension models already have a name in contrast to cases above - return String.join(".", uri.segmentsList().subList(3, uri.segmentCount() - 1)) + ".resource." + model.name + "ExportFeatureExtension"; - } - - /** - * Return the export specification for a type's supertype, if any, or null otherwise. - */ - def Export superType(Export it) { - if (type.ESuperTypes.isEmpty) null else (eContainer() as ExportModel).exportForType(type.ESuperTypes.get(0)) - } - - /** - * Return the export specification for a given type. - */ - def Export exportForType(ExportModel it, EClassifier type) { - exports.findFirst(c|c.type.name == type.name && c.type.EPackage.nsURI == type.EPackage.nsURI) - } - - /** - * Return a combined list of all user data specifications; including those on supertypes. - */ - def dispatch List allUserData(Export it) { - val result = superType.allUserData() - result.addAll(it.userData) - return result - } - - /** - * Sentinel for the above. - */ - def dispatch List allUserData(Void it) { - newArrayList - } - - /** - * Return all the interface specification for the supertypes of a type. - */ - def List getSuperInterfaces(Interface it, EClass type) { - if (type.ESuperTypes.isEmpty) newArrayList else (eContainer() as ExportModel).getInterfacesForType(type.ESuperTypes.get(0)) - } - - /** - * Return all interface specifications that apply to a certain type; including those that are defined for supertypes. - */ - def List getInterfacesForType(ExportModel it, EClass type) { - val f = interfaces.filter(f|f.type == type) - val result = if (f.isEmpty) newArrayList else newArrayList(f.get(0)) - if (!type.ESuperTypes.isEmpty) { - result.addAll(getInterfacesForType(type.ESuperTypes.get(0))) - } - return result - } - - /** - * Returns a constant name for an Attribute field - */ - def String constantName(EAttribute attribute, EClass exportType) { - (GenModelUtil2.format(exportType.name) + "__" + GenModelUtil2.format(attribute.name)).toUpperCase() - } - - - /** - * Returns a constant name for a UserData field - */ - def String constantName(UserData data, EClass exportType) { - (GenModelUtil2.format(exportType.name) + "__" + GenModelUtil2.format(data.name)).toUpperCase() - } - - /** - * Sort exports according to package and type (more specific rules must come first). - * - * @param exports - * exports to sort - * @return sorted map of all exports - */ - def ListMultimap sortedExportsByEPackage(Collection exports) { - return EClassComparator.sortedEPackageGroups(exports, [e|e.type]) - } - - /** - * Returns a type map for the given exports. - * - * @param exports - * export objects to map - * @param grammar - * Xtext grammar - * @return mappings - */ - def Map typeMap(Collection exports, Grammar grammar) { - return GeneratorUtil.typeMap(exports, grammar, [e|e.type]) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportedNamesProviderGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportedNamesProviderGenerator.java new file mode 100644 index 0000000000..52fc354492 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportedNamesProviderGenerator.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.export.generator; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.xtext.Grammar; + +import com.avaloq.tools.ddk.xtext.export.export.Export; +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.common.collect.ListMultimap; +import com.google.inject.Inject; + + +@SuppressWarnings("nls") +public class ExportedNamesProviderGenerator { + + @Inject + private CodeGenerationX codeGenerationX; + @Inject + private GeneratorUtilX generatorUtilX; + @Inject + private Naming naming; + @Inject + private ExportGeneratorX exportGeneratorX; + + public CharSequence generate(final ExportModel it, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + final Grammar grammar = exportGeneratorX.getGrammar(it); + // CHECKSTYLE:CONSTANTS-OFF + final StringBuilder sb = new StringBuilder(2048); + sb.append("package ").append(naming.toJavaPackage(exportGeneratorX.getExportedNamesProvider(it))).append(";\n"); + sb.append('\n'); + sb.append("import org.eclipse.emf.ecore.EClass;\n"); + sb.append("import org.eclipse.emf.ecore.EObject;\n"); + sb.append("import org.eclipse.emf.ecore.EPackage;\n"); + sb.append("import org.eclipse.xtext.naming.QualifiedName;\n"); + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.xtext.naming.AbstractExportedNameProvider;\n"); + sb.append('\n'); + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Qualified name provider for grammar "); + String grammarName = grammar != null ? grammar.getName() : null; + sb.append(grammarName != null ? grammarName : naming.toSimpleName(exportGeneratorX.getExportedNamesProvider(it))); + sb.append(" providing the qualified names for exported objects.\n"); + sb.append(" */\n"); + sb.append("public class ").append(naming.toSimpleName(exportGeneratorX.getExportedNamesProvider(it))).append(" extends AbstractExportedNameProvider {\n"); + sb.append('\n'); + if (!it.getExports().isEmpty()) { + final List types = it.getExports(); + sb.append(" @Override\n"); + sb.append(" public QualifiedName qualifiedName(final EObject object) {\n"); + sb.append(" EClass eClass = object.eClass();\n"); + sb.append(" EPackage ePackage = eClass.getEPackage();\n"); + final Set exportedEClasses = types.stream().map(Export::getType).collect(Collectors.toSet()); + final ListMultimap exportsMap = exportGeneratorX.sortedExportsByEPackage(types); + List sortedPackages = exportsMap.keySet().stream() + .sorted((a, b) -> a.getNsURI().compareTo(b.getNsURI())) + .collect(Collectors.toList()); + for (EPackage p : sortedPackages) { + sb.append(" if (ePackage == ").append(genModelUtil.qualifiedPackageInterfaceName(p)).append(".eINSTANCE) {\n"); + sb.append(" int classifierID = eClass.getClassifierID();\n"); + sb.append(" switch (classifierID) {\n"); + for (EClassifier classifier : p.getEClassifiers()) { + if (classifier instanceof EClass c && exportedEClasses.stream().anyMatch(e -> e.isSuperTypeOf(c))) { + sb.append(" case ").append(genModelUtil.classifierIdLiteral(c)).append(": {\n"); + sb.append(" return qualifiedName((").append(genModelUtil.instanceClassName(c)).append(") object);\n"); + sb.append(" }\n"); + } + } + sb.append(" default:\n"); + sb.append(" return null;\n"); + sb.append(" }\n"); + sb.append(" }\n"); + } + sb.append(" return null;\n"); + sb.append(" }\n"); + sb.append('\n'); + for (Export c : types) { + sb.append(" /**\n"); + sb.append(" * Return the qualified name under which a ").append(c.getType().getName()).append(" object is exported, or null if the object should not be exported.\n"); + sb.append(" *\n"); + sb.append(" * @param obj\n"); + sb.append(" * The object to be exported\n"); + sb.append(" * @return The object's qualified name, or null if the object is not to be exported\n"); + sb.append(" */\n"); + sb.append(" protected QualifiedName qualifiedName(final ").append(genModelUtil.instanceClassName(c.getType())).append(" obj) {\n"); + sb.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(c))).append('\n'); + if (c.getNaming() != null) { + sb.append(" final Object name = ").append(codeGenerationX.javaExpression(c.getNaming(), ctx.clone("obj", c.getType()))).append(";\n"); + sb.append(" return name != null ? "); + if (c.isQualifiedName()) { + sb.append("getConverter().toQualifiedName(String.valueOf(name))"); + } else { + sb.append("qualifyWithContainerName(obj, String.valueOf(name))"); + } + sb.append(" : null;\n"); + } else { + sb.append(" return "); + if (c.isQualifiedName()) { + sb.append("getConverter().toQualifiedName(getResolver().apply(obj))"); + } else { + sb.append("qualifyWithContainerName(obj, getResolver().apply(obj))"); + } + sb.append("; // \"name\" attribute by default\n"); + } + sb.append(" }\n"); + sb.append('\n'); + } + } + sb.append("}\n"); + // CHECKSTYLE:CONSTANTS-ON + return sb; + } +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportedNamesProviderGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportedNamesProviderGenerator.xtend deleted file mode 100644 index 7b4fe1bdca..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ExportedNamesProviderGenerator.xtend +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject -import org.eclipse.emf.ecore.EClass - -class ExportedNamesProviderGenerator { - - @Inject extension CodeGenerationX - @Inject extension GeneratorUtilX - @Inject extension Naming - @Inject extension ExportGeneratorX - - def generate(ExportModel it, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - val grammar = grammar - ''' - package «exportedNamesProvider.toJavaPackage()»; - - import org.eclipse.emf.ecore.EClass; - import org.eclipse.emf.ecore.EObject; - import org.eclipse.emf.ecore.EPackage; - import org.eclipse.xtext.naming.QualifiedName; - - import com.avaloq.tools.ddk.xtext.naming.AbstractExportedNameProvider; - - - /** - * Qualified name provider for grammar «grammar?.name?:exportedNamesProvider.toSimpleName» providing the qualified names for exported objects. - */ - public class «exportedNamesProvider.toSimpleName» extends AbstractExportedNameProvider { - - «IF !exports.isEmpty» - «val types = exports» - @Override - public QualifiedName qualifiedName(final EObject object) { - EClass eClass = object.eClass(); - EPackage ePackage = eClass.getEPackage(); - «val exportedEClasses = types.map[type].toSet()» - «val exportsMap = types.sortedExportsByEPackage()» - «FOR p : exportsMap.keySet().sortBy[nsURI]» - if (ePackage == «p.qualifiedPackageInterfaceName()».eINSTANCE) { - int classifierID = eClass.getClassifierID(); - switch (classifierID) { - «FOR c : p.EClassifiers.filter(EClass).filter(c|exportedEClasses.exists(e|e.isSuperTypeOf(c)))» - case «c.classifierIdLiteral()»: { - return qualifiedName((«c.instanceClassName()») object); - } - «ENDFOR» - default: - return null; - } - } - «ENDFOR» - return null; - } - - «FOR c : types» - /** - * Return the qualified name under which a «c.type.name» object is exported, or null if the object should not be exported. - * - * @param obj - * The object to be exported - * @return The object's qualified name, or null if the object is not to be exported - */ - protected QualifiedName qualifiedName(final «c.type.instanceClassName()» obj) { - «javaContributorComment(c.location())» - «IF c.naming !== null» - final Object name = «c.naming.javaExpression(ctx.clone('obj', c.type))»; - return name != null ? «IF c.qualifiedName»getConverter().toQualifiedName(String.valueOf(name))«ELSE»qualifyWithContainerName(obj, String.valueOf(name))«ENDIF» : null; - «ELSE» - return «IF c.qualifiedName»getConverter().toQualifiedName(getResolver().apply(obj))«ELSE»qualifyWithContainerName(obj, getResolver().apply(obj))«ENDIF»; // "name" attribute by default - «ENDIF» - } - - «ENDFOR» - «ENDIF» - } - ''' - } -} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FingerprintComputerGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FingerprintComputerGenerator.java new file mode 100644 index 0000000000..a3d22b7765 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FingerprintComputerGenerator.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; + +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.export.export.Interface; +import com.avaloq.tools.ddk.xtext.export.export.InterfaceExpression; +import com.avaloq.tools.ddk.xtext.export.export.InterfaceField; +import com.avaloq.tools.ddk.xtext.export.export.InterfaceItem; +import com.avaloq.tools.ddk.xtext.export.export.InterfaceNavigation; +import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.inject.Inject; + + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class FingerprintComputerGenerator { + + @Inject + private CodeGenerationX codeGenerationX; + @Inject + private GeneratorUtilX generatorUtilX; + @Inject + private Naming naming; + @Inject + private ExportGeneratorX exportGeneratorX; + + public CharSequence generate(final ExportModel it, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + // CHECKSTYLE:CONSTANTS-OFF + final StringBuilder sb = new StringBuilder(2048); + sb.append("package ").append(naming.toJavaPackage(exportGeneratorX.getFingerprintComputer(it))).append(";\n"); + sb.append('\n'); + sb.append("import org.eclipse.emf.ecore.EObject;\n"); + if (!it.getInterfaces().isEmpty()) { + sb.append("import org.eclipse.emf.ecore.EPackage;\n"); + sb.append("import org.eclipse.emf.ecore.util.Switch;\n"); + } + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.xtext.resource.AbstractStreamingFingerprintComputer;\n"); + sb.append('\n'); + sb.append("import com.google.common.hash.Hasher;\n"); + sb.append('\n'); + sb.append('\n'); + sb.append("public class ").append(naming.toSimpleName(exportGeneratorX.getFingerprintComputer(it))).append(" extends AbstractStreamingFingerprintComputer {\n"); + sb.append('\n'); + if (it.getInterfaces().isEmpty()) { + sb.append(" // no fingerprint defined\n"); + sb.append(" @Override\n"); + sb.append(" public String computeFingerprint(final org.eclipse.emf.ecore.resource.Resource resource) {\n"); + sb.append(" return null;\n"); + sb.append(" }\n"); + sb.append('\n'); + } + sb.append(" private final ThreadLocal hasherAccess = new ThreadLocal();\n"); + sb.append('\n'); + + final Set packages = it.getInterfaces().stream() + .map(f -> f.getType().getEPackage()) + .collect(Collectors.toCollection(java.util.LinkedHashSet::new)); + final List sortedPackages = packages.stream() + .sorted((a, b) -> a.getNsURI().compareTo(b.getNsURI())) + .collect(Collectors.toList()); + + for (EPackage p : sortedPackages) { + sb.append(" private final Switch ").append(p.getName()).append("Switch = new ").append(genModelUtil.qualifiedSwitchClassName(p)).append("() {\n"); + final List interfacesForPackage = it.getInterfaces().stream() + .filter(f -> f.getType().getEPackage() == p) + .collect(Collectors.toList()); + for (Interface f : interfacesForPackage) { + sb.append('\n'); + sb.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(f))).append('\n'); + sb.append(" @Override\n"); + sb.append(" public Hasher case").append(f.getType().getName()).append("(final ").append(genModelUtil.instanceClassName(f.getType())).append(" obj) {\n"); + sb.append(" final Hasher hasher = hasherAccess.get();\n"); + if (f.getGuard() != null) { + sb.append(" if (!(").append(codeGenerationX.javaExpression(f.getGuard(), ctx.clone("obj", f.getType()))).append(")) {\n"); + sb.append(" return hasher;\n"); + sb.append(" }\n"); + } + sb.append(" hasher.putUnencodedChars(obj.eClass().getName()).putChar(ITEM_SEP);\n"); + final List superFPs = exportGeneratorX.getSuperInterfaces(f, f.getType()); + for (Interface superFingerprint : superFPs) { + for (InterfaceItem superItem : superFingerprint.getItems()) { + sb.append(" ").append(doProfile(superItem, ctx, genModelUtil, superFingerprint.getType())); + } + } + for (InterfaceItem item : f.getItems()) { + sb.append(" ").append(doProfile(item, ctx, genModelUtil, f.getType())); + } + sb.append(" return hasher;\n"); + sb.append(" }\n"); + } + sb.append(" };\n"); + sb.append('\n'); + } + + sb.append(" @Override\n"); + sb.append(" protected void fingerprint(final EObject object, final Hasher hasher) {\n"); + sb.append(" hasherAccess.set(hasher);\n"); + if (!it.getInterfaces().isEmpty()) { + sb.append(" final EPackage ePackage = object.eClass().getEPackage();\n"); + for (EPackage p : sortedPackages) { + sb.append(" if (ePackage == ").append(genModelUtil.qualifiedPackageInterfaceName(p)).append(".eINSTANCE) {\n"); + sb.append(" ").append(p.getName()).append("Switch.doSwitch(object);\n"); + sb.append(" }\n"); + } + } + sb.append(" hasherAccess.set(null);\n"); + sb.append(" }\n"); + sb.append("}\n"); + // CHECKSTYLE:CONSTANTS-ON + return sb; + } + + /** + * Public dispatcher for doProfile. + * + * @param it + * the interface item + * @param ctx + * the compilation context + * @param genModelUtil + * the gen model utility + * @param type + * the EClass type + * @return the generated profile code + */ + public CharSequence doProfile(final InterfaceItem it, final CompilationContext ctx, final GenModelUtilX genModelUtil, final EClass type) { + if (it instanceof InterfaceExpression interfaceExpression) { + return _doProfile(interfaceExpression, ctx, genModelUtil, type); + } else if (it instanceof InterfaceField interfaceField) { + return _doProfile(interfaceField, ctx, genModelUtil, type); + } else if (it instanceof InterfaceNavigation interfaceNavigation) { + return _doProfile(interfaceNavigation, ctx, genModelUtil, type); + } else { + return _doProfileDefault(it, ctx, genModelUtil, type); + } + } + + protected CharSequence _doProfileDefault(final InterfaceItem it, final CompilationContext ctx, final GenModelUtilX genModelUtil, final EClass type) { + return "ERROR" + it.toString() + " " + generatorUtilX.javaContributorComment(generatorUtilX.location(it)); + } + + // CHECKSTYLE:CONSTANTS-OFF + protected CharSequence _doProfile(final InterfaceField it, final CompilationContext ctx, final GenModelUtilX genModelUtil, final EClass type) { + final StringBuilder sb = new StringBuilder(128); + if (it.getField().isMany() && it.isUnordered()) { + sb.append("fingerprintFeature(obj, ").append(genModelUtil.literalIdentifier(it.getField())).append(", FingerprintOrder.UNORDERED, hasher);\n"); + } else { + sb.append("fingerprintFeature(obj, ").append(genModelUtil.literalIdentifier(it.getField())).append(", hasher);\n"); + } + sb.append("hasher.putChar(ITEM_SEP);\n"); + return sb; + } + + protected CharSequence _doProfile(final InterfaceNavigation it, final CompilationContext ctx, final GenModelUtilX genModelUtil, final EClass type) { + final StringBuilder sb = new StringBuilder(128); + if (it.getRef().isMany() && it.isUnordered()) { + sb.append("fingerprintRef(obj, ").append(genModelUtil.literalIdentifier(it.getRef())).append(", FingerprintOrder.UNORDERED, hasher);\n"); + } else { + sb.append("fingerprintRef(obj, ").append(genModelUtil.literalIdentifier(it.getRef())).append(", hasher);\n"); + } + sb.append("hasher.putChar(ITEM_SEP);\n"); + return sb; + } + + protected CharSequence _doProfile(final InterfaceExpression it, final CompilationContext ctx, final GenModelUtilX genModelUtil, final EClass type) { + final StringBuilder sb = new StringBuilder(128); + sb.append("fingerprintExpr(").append(codeGenerationX.javaExpression(it.getExpr(), ctx.clone("obj", type))) + .append(", obj, FingerprintOrder.").append(it.isUnordered() ? "UNORDERED" : "ORDERED") + .append(", FingerprintIndirection.").append(it.isRef() ? "INDIRECT" : "DIRECT") + .append(", hasher);\n"); + sb.append("hasher.putChar(ITEM_SEP);\n"); + return sb; + } + // CHECKSTYLE:CONSTANTS-ON +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FingerprintComputerGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FingerprintComputerGenerator.xtend deleted file mode 100644 index b0eeb2641c..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FingerprintComputerGenerator.xtend +++ /dev/null @@ -1,134 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.export.export.InterfaceExpression -import com.avaloq.tools.ddk.xtext.export.export.InterfaceField -import com.avaloq.tools.ddk.xtext.export.export.InterfaceItem -import com.avaloq.tools.ddk.xtext.export.export.InterfaceNavigation -import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject -import org.eclipse.emf.ecore.EClass - -class FingerprintComputerGenerator { - - @Inject extension CodeGenerationX - @Inject extension GeneratorUtilX - @Inject extension Naming - @Inject extension ExportGeneratorX - - def generate(ExportModel it, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - ''' - package «fingerprintComputer.toJavaPackage»; - - import org.eclipse.emf.ecore.EObject; - «IF !interfaces.isEmpty» - import org.eclipse.emf.ecore.EPackage; - import org.eclipse.emf.ecore.util.Switch; - «ENDIF» - - import com.avaloq.tools.ddk.xtext.resource.AbstractStreamingFingerprintComputer; - - import com.google.common.hash.Hasher; - - - public class «fingerprintComputer.toSimpleName» extends AbstractStreamingFingerprintComputer { - - «IF interfaces.isEmpty» - // no fingerprint defined - @Override - public String computeFingerprint(final org.eclipse.emf.ecore.resource.Resource resource) { - return null; - } - - «ENDIF» - private ThreadLocal hasherAccess = new ThreadLocal(); - - «FOR p : interfaces.map[type.EPackage].toSet.sortBy[nsURI]» - private final Switch «p.name»Switch = new «p.qualifiedSwitchClassName()»() { - «FOR f : interfaces.filter[type.EPackage == p]» - - «javaContributorComment(f.location())» - @Override - public Hasher case«f.type.name»(final «f.type.instanceClassName()» obj) { - final Hasher hasher = hasherAccess.get(); - «IF f.guard !== null» - if (!(«f.guard.javaExpression(ctx.clone('obj', f.type))»)) { - return hasher; - } - «ENDIF» - hasher.putUnencodedChars(obj.eClass().getName()).putChar(ITEM_SEP); - «val superFPs = f.getSuperInterfaces(f.type)» - «FOR superFingerprint : superFPs» - «FOR superItem : superFingerprint.items» - «doProfile(superItem, ctx, genModelUtil, superFingerprint.type)» - «ENDFOR» - «ENDFOR» - «FOR item : f.items» - «doProfile(item, ctx, genModelUtil, f.type)» - «ENDFOR» - return hasher; - } - «ENDFOR» - }; - - «ENDFOR» - @Override - protected void fingerprint(final EObject object, Hasher hasher) { - hasherAccess.set(hasher); - «IF !interfaces.isEmpty» - final EPackage ePackage = object.eClass().getEPackage(); - «FOR p : interfaces.map[type.EPackage].toSet().sortBy[nsURI]» - if (ePackage == «p.qualifiedPackageInterfaceName()».eINSTANCE) { - «p.name»Switch.doSwitch(object); - } - «ENDFOR» - «ENDIF» - hasherAccess.set(null); - } - } - ''' - } - - def dispatch doProfile(InterfaceItem it, CompilationContext ctx, extension GenModelUtilX genModelUtil, EClass type) { - 'ERROR' + it.toString + ' ' + javaContributorComment(it.location()) - } - - def dispatch doProfile(InterfaceField it, CompilationContext ctx, extension GenModelUtilX genModelUtil, EClass type) ''' - «IF field.many && (unordered == true) » - fingerprintFeature(obj, «field.literalIdentifier()», FingerprintOrder.UNORDERED, hasher); - «ELSE» - fingerprintFeature(obj, «field.literalIdentifier()», hasher); - «ENDIF» - hasher.putChar(ITEM_SEP); - ''' - - def dispatch doProfile(InterfaceNavigation it, CompilationContext ctx, extension GenModelUtilX genModelUtil, EClass type) ''' - «IF ref.many && (unordered == true) » - fingerprintRef(obj, «ref.literalIdentifier()», FingerprintOrder.UNORDERED, hasher); - «ELSE» - fingerprintRef(obj, «ref.literalIdentifier()», hasher); - «ENDIF» - hasher.putChar(ITEM_SEP); - ''' - - def dispatch doProfile(InterfaceExpression it, CompilationContext ctx, extension GenModelUtilX genModelUtil, EClass type) ''' - fingerprintExpr(«expr.javaExpression(ctx.clone('obj', type))», obj, FingerprintOrder.«if (unordered) "UNORDERED" else "ORDERED"», FingerprintIndirection.«if (ref) "INDIRECT" else "DIRECT"», hasher); - hasher.putChar(ITEM_SEP); - ''' - -} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FragmentProviderGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FragmentProviderGenerator.java new file mode 100644 index 0000000000..cc211a9326 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FragmentProviderGenerator.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.export.generator; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.xtext.Grammar; + +import com.avaloq.tools.ddk.xtext.export.export.Export; +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.common.collect.ListMultimap; +import com.google.inject.Inject; + + +@SuppressWarnings({"PMD.UnusedFormalParameter", "nls"}) +public class FragmentProviderGenerator { + + @Inject + private GeneratorUtilX generatorUtilX; + @Inject + private Naming naming; + @Inject + private ExportGeneratorX exportGeneratorX; + + public CharSequence generate(final ExportModel it, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + final Grammar grammar = exportGeneratorX.getGrammar(it); + final List fingerprintedExports = it.getExports().stream() + .filter(e -> e.isFingerprint() && e.getFragmentAttribute() != null) + .collect(Collectors.toList()); + // CHECKSTYLE:CONSTANTS-OFF + final StringBuilder sb = new StringBuilder(2048); + sb.append("package ").append(naming.toJavaPackage(exportGeneratorX.getFragmentProvider(it))).append(";\n"); + sb.append('\n'); + if (!fingerprintedExports.isEmpty()) { + sb.append("import org.eclipse.emf.ecore.EClass;\n"); + } + if (!fingerprintedExports.isEmpty() || it.isExtension()) { + sb.append("import org.eclipse.emf.ecore.EObject;\n"); + } + if (!fingerprintedExports.isEmpty()) { + sb.append("import org.eclipse.emf.ecore.EPackage;\n"); + } + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.xtext.resource.AbstractSelectorFragmentProvider;\n"); + sb.append('\n'); + sb.append('\n'); + sb.append("public class ").append(naming.toSimpleName(exportGeneratorX.getFragmentProvider(it))).append(" extends AbstractSelectorFragmentProvider {\n"); + sb.append('\n'); + if (!fingerprintedExports.isEmpty()) { + sb.append(" @Override\n"); + sb.append(" public boolean appendFragmentSegment(final EObject object, StringBuilder builder) {\n"); + sb.append(" EClass eClass = object.eClass();\n"); + sb.append(" EPackage ePackage = eClass.getEPackage();\n"); + final Map typeMap = exportGeneratorX.typeMap(fingerprintedExports, grammar); + final ListMultimap sortedExportsMap = exportGeneratorX.sortedExportsByEPackage(fingerprintedExports); + for (EPackage p : sortedExportsMap.keySet()) { + sb.append(" if (ePackage == ").append(genModelUtil.qualifiedPackageInterfaceName(p)).append(".eINSTANCE) {\n"); + sb.append(" int classifierID = eClass.getClassifierID();\n"); + sb.append(" switch (classifierID) {\n"); + for (EClassifier classifier : p.getEClassifiers()) { + if (classifier instanceof EClass c && fingerprintedExports.stream().map(Export::getType).anyMatch(e -> e.isSuperTypeOf(c))) { + final Export e = typeMap.get(c); + sb.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(e))).append('\n'); + sb.append(" case ").append(genModelUtil.classifierIdLiteral(c)).append(": {\n"); + sb.append(" return appendFragmentSegment((").append(genModelUtil.instanceClassName(c)).append(") object, builder);\n"); + sb.append(" }\n"); + } + } + sb.append(" default:\n"); + sb.append(" return super.appendFragmentSegment(object, builder);\n"); + sb.append(" }\n"); + sb.append(" }\n"); + } + sb.append(" return super.appendFragmentSegment(object, builder);\n"); + sb.append(" }\n"); + } + sb.append('\n'); + if (it.isExtension()) { + sb.append(" @Override\n"); + sb.append(" protected boolean appendFragmentSegmentFallback(final EObject object, StringBuilder builder) {\n"); + sb.append(" // For export extension we must return false, so the logic will try other extensions\n"); + sb.append(" return false;\n"); + sb.append(" }\n"); + sb.append('\n'); + } + for (Export e : fingerprintedExports) { + sb.append(" protected boolean appendFragmentSegment(final ").append(genModelUtil.instanceClassName(e.getType())).append(" obj, StringBuilder builder) {\n"); + sb.append(" return computeSelectorFragmentSegment(obj, ").append(genModelUtil.literalIdentifier(e.getFragmentAttribute())).append(", ").append(e.isFragmentUnique()).append(", builder);\n"); + sb.append(" }\n"); + sb.append('\n'); + } + sb.append("}\n"); + // CHECKSTYLE:CONSTANTS-ON + return sb; + } +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FragmentProviderGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FragmentProviderGenerator.xtend deleted file mode 100644 index c91937bbb5..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/FragmentProviderGenerator.xtend +++ /dev/null @@ -1,94 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject -import org.eclipse.emf.ecore.EClass - -class FragmentProviderGenerator { - - @Inject extension GeneratorUtilX - @Inject extension Naming - @Inject extension ExportGeneratorX - - def generate(ExportModel it, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - val grammar = grammar - val fingerprintedExports = exports.filter[fingerprint && fragmentAttribute !== null].toList - ''' - package «fragmentProvider.toJavaPackage»; - - «IF !fingerprintedExports.isEmpty» - import org.eclipse.emf.ecore.EClass; - «ENDIF» - «IF !fingerprintedExports.isEmpty || it.extension» - import org.eclipse.emf.ecore.EObject; - «ENDIF» - «IF !fingerprintedExports.isEmpty» - import org.eclipse.emf.ecore.EPackage; - «ENDIF» - - import com.avaloq.tools.ddk.xtext.resource.AbstractSelectorFragmentProvider; - - - public class «getFragmentProvider().toSimpleName()» extends AbstractSelectorFragmentProvider { - - «IF !fingerprintedExports.isEmpty» - @Override - public boolean appendFragmentSegment(final EObject object, StringBuilder builder) { - EClass eClass = object.eClass(); - EPackage ePackage = eClass.getEPackage(); - «val typeMap = fingerprintedExports.typeMap(grammar)» - «val sortedExportsMap = fingerprintedExports.sortedExportsByEPackage()» - «FOR p : sortedExportsMap.keySet()» - if (ePackage == «p.qualifiedPackageInterfaceName()».eINSTANCE) { - int classifierID = eClass.getClassifierID(); - switch (classifierID) { - «FOR c : p.EClassifiers.filter(EClass).filter(c|fingerprintedExports.map[type].exists(e|e.isSuperTypeOf(c)))» - «val e = typeMap.get(c)» - «javaContributorComment(e.location())» - case «c.classifierIdLiteral()»: { - return appendFragmentSegment((«c.instanceClassName()») object, builder); - } - «ENDFOR» - default: - return super.appendFragmentSegment(object, builder); - } - } - «ENDFOR» - return super.appendFragmentSegment(object, builder); - } - «ENDIF» - - «IF it.extension» - @Override - protected boolean appendFragmentSegmentFallback(final EObject object, StringBuilder builder) { - // For export extension we must return false, so the logic will try other extensions - return false; - } - - «ENDIF» - «FOR e : fingerprintedExports» - protected boolean appendFragmentSegment(final «e.type.instanceClassName()» obj, StringBuilder builder) { - return computeSelectorFragmentSegment(obj, «e.fragmentAttribute.literalIdentifier()», «e.fragmentUnique», builder); - } - - «ENDFOR» - } - ''' - } - -} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionConstantsGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionConstantsGenerator.java new file mode 100644 index 0000000000..04b5fb959e --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionConstantsGenerator.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EAttribute; + +import com.avaloq.tools.ddk.xtext.export.export.Export; +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.export.export.UserData; +import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.inject.Inject; + + +@SuppressWarnings({"PMD.UnusedFormalParameter", "nls"}) +public class ResourceDescriptionConstantsGenerator { + + @Inject + private CodeGenerationX codeGenerationX; + + @Inject + private Naming naming; + + @Inject + private ExportGeneratorX exportGeneratorX; + + public CharSequence generate(final ExportModel it, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + // CHECKSTYLE:CONSTANTS-OFF + final StringBuilder sb = new StringBuilder(512); + sb.append("package "); + sb.append(naming.toJavaPackage(exportGeneratorX.getResourceDescriptionConstants(it))); + sb.append(";\n"); + sb.append('\n'); + sb.append("public interface "); + sb.append(naming.toSimpleName(exportGeneratorX.getResourceDescriptionConstants(it))); + sb.append(" {\n"); + final EList types = it.getExports(); + for (final Export c : types) { + if (!c.getType().isAbstract()) { + final EList a = c.getAllEAttributes(); + final List d = exportGeneratorX.allUserData(c); + if (!a.isEmpty() || !d.isEmpty()) { + sb.append(" // Export "); + sb.append(c.getType().getName()); + sb.append('\n'); + if (!a.isEmpty()) { + for (final EAttribute attr : a) { + sb.append(" public static final String "); + sb.append(exportGeneratorX.constantName(attr, c.getType())); + sb.append(" = \""); + sb.append(codeGenerationX.javaEncode(attr.getName())); + sb.append("\"; //$NON-NLS-1$\n"); + } + } + if (!d.isEmpty()) { + for (final UserData data : d) { + sb.append(" public static final String "); + sb.append(exportGeneratorX.constantName(data, c.getType())); + sb.append(" = \""); + sb.append(codeGenerationX.javaEncode(data.getName())); + sb.append("\"; //$NON-NLS-1$\n"); + } + } + sb.append('\n'); + } + } + } + sb.append("}\n"); + // CHECKSTYLE:CONSTANTS-ON + return sb; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionConstantsGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionConstantsGenerator.xtend deleted file mode 100644 index 8b7280a4b0..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionConstantsGenerator.xtend +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject - -class ResourceDescriptionConstantsGenerator { - - @Inject extension CodeGenerationX - @Inject extension Naming - @Inject extension ExportGeneratorX - - def generate(ExportModel it, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - ''' - package «getResourceDescriptionConstants().toJavaPackage()»; - - public interface «getResourceDescriptionConstants().toSimpleName()» { - «val types = it.exports» - «FOR c : types.filter[!it.type.abstract]» - «val a = c.allEAttributes» - «val d = c.allUserData()» - «IF !a.isEmpty || !d.isEmpty» - // Export «c.type.name» - «IF !a.isEmpty» - «FOR attr : a» - public static final String «attr.constantName(c.type)» = "«attr.name.javaEncode()»"; //$NON-NLS-1$ - «ENDFOR» - «ENDIF» - «IF !d.isEmpty» - «FOR data : d» - public static final String «data.constantName(c.type)» = "«data.name.javaEncode()»"; //$NON-NLS-1$ - «ENDFOR» - «ENDIF» - - «ENDIF» - «ENDFOR» - } - ''' - } - -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionManagerGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionManagerGenerator.java new file mode 100644 index 0000000000..277aec069b --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionManagerGenerator.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.xtext.Grammar; + +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.inject.Inject; + + +@SuppressWarnings({"PMD.UnusedFormalParameter", "nls"}) +public class ResourceDescriptionManagerGenerator { + + @Inject + private Naming naming; + + @Inject + private ExportGeneratorX exportGeneratorX; + + public CharSequence generate(final ExportModel model, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + final Grammar grammar = exportGeneratorX.getGrammar(model); + final List usedGrammars = grammar != null ? grammar.getUsedGrammars() : new ArrayList<>(); + final Grammar extendedGrammar = (usedGrammars.isEmpty() || usedGrammars.get(0).getName().endsWith(".Terminals")) ? null : usedGrammars.get(0); + // CHECKSTYLE:CONSTANTS-OFF + final StringBuilder sb = new StringBuilder(2048); + sb.append("package "); + sb.append(naming.toJavaPackage(exportGeneratorX.getResourceDescriptionManager(model))); + sb.append(";\n"); + sb.append('\n'); + sb.append("import java.util.Set;\n"); + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.xtext.resource.AbstractCachingResourceDescriptionManager;\n"); + if (extendedGrammar != null) { + sb.append("import "); + sb.append(exportGeneratorX.getResourceDescriptionManager(extendedGrammar)); + sb.append(";\n"); + sb.append("import com.google.common.collect.ImmutableSet;\n"); + sb.append("import com.google.common.collect.Sets;\n"); + } + sb.append("import com.google.inject.Singleton;\n"); + sb.append('\n'); + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Resource description manager for "); + sb.append(exportGeneratorX.getName(model)); + sb.append(" resources.\n"); + sb.append(" */\n"); + sb.append("@Singleton\n"); + sb.append("public class "); + sb.append(naming.toSimpleName(exportGeneratorX.getResourceDescriptionManager(model))); + sb.append(" extends AbstractCachingResourceDescriptionManager {\n"); + sb.append('\n'); + sb.append(" public static final Set INTERESTING_EXTS = "); + if (extendedGrammar != null) { + sb.append("ImmutableSet.copyOf(Sets.union("); + sb.append(naming.toSimpleName(exportGeneratorX.getResourceDescriptionManager(extendedGrammar))); + sb.append(".INTERESTING_EXTS, of(/*add extensions here*/)));\n"); + } else { + sb.append("all();\n"); + } + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" protected Set getInterestingExtensions() {\n"); + sb.append(" return INTERESTING_EXTS;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append("}\n"); + // CHECKSTYLE:CONSTANTS-ON + return sb; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionManagerGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionManagerGenerator.xtend deleted file mode 100644 index b449d4a6c0..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionManagerGenerator.xtend +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject - -class ResourceDescriptionManagerGenerator { - - @Inject extension Naming - @Inject extension ExportGeneratorX - - def generate(ExportModel model, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - val grammar = model.grammar - val usedGrammars = if (grammar !== null) grammar.usedGrammars else newArrayList - val extendedGrammar = if (usedGrammars.isEmpty || usedGrammars.head.name.endsWith('.Terminals')) null else usedGrammars.head - ''' - package «model.resourceDescriptionManager.toJavaPackage»; - - import java.util.Set; - - import com.avaloq.tools.ddk.xtext.resource.AbstractCachingResourceDescriptionManager; - «IF extendedGrammar !== null» - import «extendedGrammar.resourceDescriptionManager»; - import com.google.common.collect.ImmutableSet; - import com.google.common.collect.Sets; - «ENDIF» - import com.google.inject.Singleton; - - - /** - * Resource description manager for «model.name» resources. - */ - @Singleton - public class «model.resourceDescriptionManager.toSimpleName» extends AbstractCachingResourceDescriptionManager { - - public static final Set INTERESTING_EXTS = «IF extendedGrammar !== null»ImmutableSet.copyOf(Sets.union(«extendedGrammar.resourceDescriptionManager.toSimpleName».INTERESTING_EXTS, of(/*add extensions here*/)));«ELSE»all();«ENDIF» - - @Override - protected Set getInterestingExtensions() { - return INTERESTING_EXTS; - } - - } - ''' - } - -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionStrategyGenerator.java b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionStrategyGenerator.java new file mode 100644 index 0000000000..2b32ddbce6 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionStrategyGenerator.java @@ -0,0 +1,252 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.export.generator; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EPackage; + +import com.avaloq.tools.ddk.xtext.export.export.Export; +import com.avaloq.tools.ddk.xtext.export.export.ExportModel; +import com.avaloq.tools.ddk.xtext.export.export.UserData; +import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.google.inject.Inject; + + +@SuppressWarnings("nls") +public class ResourceDescriptionStrategyGenerator { + + @Inject + private CodeGenerationX codeGenerationX; + @Inject + private GeneratorUtilX generatorUtilX; + @Inject + private Naming naming; + @Inject + private ExportGeneratorX exportGeneratorX; + + public CharSequence generate(final ExportModel it, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + // CHECKSTYLE:CONSTANTS-OFF + final StringBuilder sb = new StringBuilder(2048); + sb.append("package ").append(naming.toJavaPackage(exportGeneratorX.getResourceDescriptionStrategy(it))).append(";\n"); + sb.append('\n'); + sb.append("import java.util.Map;\n"); + sb.append("import java.util.Set;\n"); + sb.append('\n'); + sb.append("import org.eclipse.emf.ecore.EClass;\n"); + sb.append("import org.eclipse.emf.ecore.EObject;\n"); + sb.append("import org.eclipse.emf.ecore.EPackage;\n"); + sb.append("import org.eclipse.emf.ecore.resource.Resource;\n"); + sb.append("import org.eclipse.emf.ecore.util.Switch;\n"); + sb.append("import org.eclipse.xtext.resource.IEObjectDescription;\n"); + sb.append("import org.eclipse.xtext.util.IAcceptor;\n"); + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.xtext.resource.AbstractResourceDescriptionStrategy;\n"); + if (it.getExports().stream().anyMatch(e -> e.isFingerprint() || e.isResourceFingerprint())) { + sb.append("import com.avaloq.tools.ddk.xtext.resource.IFingerprintComputer;\n"); + } + if (it.getExports().stream().anyMatch(e -> e.isLookup())) { + sb.append("import com.avaloq.tools.ddk.xtext.resource.DetachableEObjectDescription;\n"); + } + sb.append("import com.avaloq.tools.ddk.xtext.resource.extensions.AbstractForwardingResourceDescriptionStrategyMap;\n"); + sb.append("import com.google.common.collect.ImmutableMap;\n"); + sb.append("import com.google.common.collect.ImmutableSet;\n"); + + final Collection types = it.getExports(); + sb.append('\n'); + sb.append('\n'); + sb.append("public class ").append(naming.toSimpleName(exportGeneratorX.getResourceDescriptionStrategy(it))).append(" extends AbstractResourceDescriptionStrategy {\n"); + sb.append('\n'); + + // EXPORTED_ECLASSES + sb.append(" private static final Set EXPORTED_ECLASSES = ImmutableSet.copyOf(new EClass[] {\n"); + final Map e = exportGeneratorX.typeMap(types, exportGeneratorX.getGrammar(it)); + final List sortedKeys = e.keySet().stream() + .sorted((a, b) -> genModelUtil.literalIdentifier(a).compareTo(genModelUtil.literalIdentifier(b))) + .collect(Collectors.toList()); + for (int i = 0; i < sortedKeys.size(); i++) { + sb.append(" ").append(genModelUtil.literalIdentifier(sortedKeys.get(i))); + if (i < sortedKeys.size() - 1) { + sb.append(','); + } + sb.append('\n'); + } + sb.append(" });\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public Set getExportedEClasses(final Resource resource) {\n"); + sb.append(" return EXPORTED_ECLASSES;\n"); + sb.append(" }\n"); + sb.append('\n'); + + if (!types.isEmpty()) { + sb.append(" private final ThreadLocal> acceptor = new ThreadLocal>();\n"); + sb.append('\n'); + + final Set packageSet = types.stream() + .filter(c -> !c.getType().isAbstract()) + .map(c -> c.getType().getEPackage()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + final List sortedPackages = packageSet.stream() + .sorted((a, b) -> a.getNsURI().compareTo(b.getNsURI())) + .collect(Collectors.toList()); + + for (EPackage p : sortedPackages) { + sb.append(" private final Switch ").append(p.getName()).append("ExportSwitch = new ").append(genModelUtil.qualifiedSwitchClassName(p)).append("() {\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public Boolean defaultCase(final EObject obj) {\n"); + sb.append(" return true;\n"); + sb.append(" }\n"); + + final List exportsForPackage = types.stream() + .filter(c -> !c.getType().isAbstract() && c.getType().getEPackage() == p) + .collect(Collectors.toList()); + + for (Export c : exportsForPackage) { + sb.append('\n'); + sb.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(c))).append('\n'); + sb.append(" @Override\n"); + sb.append(" public Boolean case").append(c.getType().getName()).append("(final ").append(genModelUtil.instanceClassName(c.getType())).append(" obj) {\n"); + final String guard = codeGenerationX.javaExpression(c.getGuard(), ctx.clone("obj", c.getType())); + if (c.getGuard() == null) { + sb.append(generateCaseBody(it, c, ctx, genModelUtil)); + } else if (!"false".equalsIgnoreCase(guard)) { + sb.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(c.getGuard()))).append('\n'); + sb.append(" if (").append(guard).append(") {\n"); + sb.append(generateCaseBody(it, c, ctx, genModelUtil)); + sb.append(" }\n"); + } + sb.append('\n'); + + // can Type contain any nested types ? + final Set nonAbstractTypeNames = types.stream() + .map(t -> t.getType()) + .filter(t -> !t.isAbstract()) + .map(t -> t.getName()) + .collect(Collectors.toSet()); + sb.append(" // can ").append(c.getType().getName()).append(" contain any nested ").append(nonAbstractTypeNames).append(" objects ?\n"); + final Set nonAbstractTypes = types.stream() + .map(t -> t.getType()) + .filter(t -> !t.isAbstract()) + .collect(Collectors.toSet()); + sb.append(" return ").append(generatorUtilX.canContain(c.getType(), nonAbstractTypes, exportGeneratorX.getGrammar(it))).append(";\n"); + sb.append(" }\n"); + } + sb.append(" };\n"); + sb.append('\n'); + } + + sb.append(" @Override\n"); + sb.append(" protected boolean doCreateEObjectDescriptions(final EObject object, final IAcceptor acceptor) {\n"); + sb.append(" try {\n"); + sb.append(" this.acceptor.set(acceptor);\n"); + sb.append(" final EPackage ePackage = object.eClass().getEPackage();\n"); + for (EPackage p : sortedPackages) { + sb.append(" if (ePackage == ").append(genModelUtil.qualifiedPackageInterfaceName(p)).append(".eINSTANCE) {\n"); + sb.append(" return ").append(p.getName()).append("ExportSwitch.doSwitch(object);\n"); + sb.append(" }\n"); + } + if (it.isExtension()) { + sb.append(" // Extension does not have to cover all EPackages of the language\n"); + sb.append(" return false;\n"); + } else { + sb.append(" // TODO: generate code for other possible epackages (as defined by grammar)\n"); + sb.append(" return true;\n"); + } + sb.append(" } finally {\n"); + sb.append(" this.acceptor.set(null);\n"); + sb.append(" }\n"); + sb.append(" }\n"); + sb.append('\n'); + } + sb.append("}\n"); + // CHECKSTYLE:CONSTANTS-ON + return sb; + } + + // CHECKSTYLE:CONSTANTS-OFF + public CharSequence generateCaseBody(final ExportModel it, final Export c, final CompilationContext ctx, final GenModelUtilX genModelUtil) { + final List a = c.getAllEAttributes(); + final List d = exportGeneratorX.allUserData(c); + final StringBuilder sb = new StringBuilder(512); + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + if (!a.isEmpty() || !d.isEmpty() || c.isFingerprint() || c.isResourceFingerprint() || c.isLookup()) { + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + sb.append(" // Use a forwarding map to delay calculation as much as possible; otherwise we may get recursive EObject resolution attempts\n"); + sb.append(" Map data = new AbstractForwardingResourceDescriptionStrategyMap() {\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" protected void fill(final ImmutableMap.Builder builder) {\n"); + sb.append(" Object value = null;\n"); + if (c.isFingerprint()) { + sb.append(" // Fingerprint\n"); + sb.append(" value = getFingerprint(obj);\n"); + sb.append(" if (value != null) {\n"); + sb.append(" builder.put(IFingerprintComputer.OBJECT_FINGERPRINT, value.toString());\n"); + sb.append(" }\n"); + } else if (c.isResourceFingerprint()) { + sb.append(" // Resource fingerprint\n"); + sb.append(" value = getFingerprint(obj);\n"); + sb.append(" if (value != null) {\n"); + sb.append(" builder.put(IFingerprintComputer.RESOURCE_FINGERPRINT, value.toString());\n"); + sb.append(" }\n"); + } + if (c.isLookup()) { + sb.append(" // Allow lookups\n"); + if (c.getLookupPredicate() != null) { + sb.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(c.getLookupPredicate()))).append('\n'); + sb.append(" if (").append(codeGenerationX.javaExpression(c.getLookupPredicate(), ctx.clone("obj", c.getType()))).append(") {\n"); + sb.append(" builder.put(DetachableEObjectDescription.ALLOW_LOOKUP, Boolean.TRUE.toString());\n"); + sb.append(" }\n"); + } else { + sb.append(" builder.put(DetachableEObjectDescription.ALLOW_LOOKUP, Boolean.TRUE.toString());\n"); + } + } + if (!a.isEmpty()) { + sb.append(" // Exported attributes\n"); + for (EAttribute attr : a) { + sb.append(" value = obj.eGet(").append(genModelUtil.literalIdentifier(attr)).append(", false);\n"); + sb.append(" if (value != null) {\n"); + sb.append(" builder.put(").append(naming.toSimpleName(exportGeneratorX.getResourceDescriptionConstants(it))).append('.').append(exportGeneratorX.constantName(attr, c.getType())).append(", value.toString());\n"); + sb.append(" }\n"); + } + } + if (!d.isEmpty()) { + sb.append(" // User data\n"); + for (UserData data : d) { + sb.append(" value = ").append(codeGenerationX.javaExpression(data.getExpr(), ctx.clone("obj", c.getType()))).append(";\n"); + sb.append(" if (value != null) {\n"); + sb.append(" builder.put(").append(naming.toSimpleName(exportGeneratorX.getResourceDescriptionConstants(it))).append('.').append(exportGeneratorX.constantName(data, c.getType())).append(", value.toString());\n"); + sb.append(" }\n"); + } + } + sb.append(" }\n"); + sb.append(" };\n"); + sb.append(" acceptEObjectDescription(obj, data, acceptor.get());\n"); + } else { + sb.append(" acceptEObjectDescription(obj, acceptor.get());\n"); + } + return sb; + } + // CHECKSTYLE:CONSTANTS-ON +} diff --git a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionStrategyGenerator.xtend b/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionStrategyGenerator.xtend deleted file mode 100644 index d3ab3e3c69..0000000000 --- a/com.avaloq.tools.ddk.xtext.export/src/com/avaloq/tools/ddk/xtext/export/generator/ResourceDescriptionStrategyGenerator.xtend +++ /dev/null @@ -1,190 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.export.generator - -import com.avaloq.tools.ddk.xtext.export.export.Export -import com.avaloq.tools.ddk.xtext.export.export.ExportModel -import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.google.inject.Inject - -class ResourceDescriptionStrategyGenerator { - - @Inject extension CodeGenerationX - @Inject extension GeneratorUtilX - @Inject extension Naming - @Inject extension ExportGeneratorX - - def generate(ExportModel it, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - ''' - package «resourceDescriptionStrategy.toJavaPackage»; - - import java.util.Map; - import java.util.Set; - - import org.eclipse.emf.ecore.EClass; - import org.eclipse.emf.ecore.EObject; - import org.eclipse.emf.ecore.EPackage; - import org.eclipse.emf.ecore.resource.Resource; - import org.eclipse.emf.ecore.util.Switch; - import org.eclipse.xtext.resource.IEObjectDescription; - import org.eclipse.xtext.util.IAcceptor; - - import com.avaloq.tools.ddk.xtext.resource.AbstractResourceDescriptionStrategy; - «IF exports.exists[e|e.fingerprint||e.resourceFingerprint]» - import com.avaloq.tools.ddk.xtext.resource.IFingerprintComputer; - «ENDIF» - «IF exports.exists(e|e.lookup)» - import com.avaloq.tools.ddk.xtext.resource.DetachableEObjectDescription; - «ENDIF» - import com.avaloq.tools.ddk.xtext.resource.extensions.AbstractForwardingResourceDescriptionStrategyMap; - import com.google.common.collect.ImmutableMap; - import com.google.common.collect.ImmutableSet; - «val types = exports» - - - public class «resourceDescriptionStrategy.toSimpleName» extends AbstractResourceDescriptionStrategy { - - private static final Set EXPORTED_ECLASSES = ImmutableSet.copyOf(new EClass[] { - «val e = types.typeMap(grammar)» - «FOR c : e.keySet.sortBy[literalIdentifier] SEPARATOR ',\n'»«c.literalIdentifier»«ENDFOR» - }); - - @Override - public Set getExportedEClasses(final Resource resource) { - return EXPORTED_ECLASSES; - } - - «IF !types.isEmpty» - private final ThreadLocal> acceptor = new ThreadLocal>(); - - «FOR p : types.filter[!type.abstract].map[type.EPackage].toSet.sortBy[nsURI]» - private final Switch «p.name»ExportSwitch = new «p.qualifiedSwitchClassName»() { - - @Override - public Boolean defaultCase(final EObject obj) { - return true; - } - «FOR c : types.filter[!type.abstract && type.EPackage == p]» - - «javaContributorComment(c.location)» - @Override - public Boolean case«c.type.name»(final «c.type.instanceClassName()» obj) { - «val guard = c.guard.javaExpression(ctx.clone('obj', c.type))» - «IF c.guard === null» - «generateCaseBody(c, ctx, genModelUtil)» - «ELSEIF !guard.equalsIgnoreCase("false")» - «javaContributorComment(c.guard.location)» - if («guard») { - «generateCaseBody(c, ctx, genModelUtil)» - } - «ENDIF» - - // can «c.type.name» contain any nested «types.map[type].filter[!abstract].map[name].toSet» objects ? - return «c.type.canContain(types.map[type].filter[!abstract].toSet, grammar)»; - } - «ENDFOR» - }; - - «ENDFOR» - @Override - protected boolean doCreateEObjectDescriptions(final EObject object, final IAcceptor acceptor) { - try { - this.acceptor.set(acceptor); - final EPackage ePackage = object.eClass().getEPackage(); - «FOR p : types.filter[!type.abstract].map[type.EPackage].toSet.sortBy[nsURI]» - if (ePackage == «p.qualifiedPackageInterfaceName».eINSTANCE) { - return «p.name»ExportSwitch.doSwitch(object); - } - «ENDFOR» - «IF it.extension» - // Extension does not have to cover all EPackages of the language - return false; - «ELSE» - // TODO: generate code for other possible epackages (as defined by grammar) - return true; - «ENDIF» - } finally { - this.acceptor.set(null); - } - } - - «ENDIF» - } - ''' - } - - def generateCaseBody(ExportModel it, Export c, CompilationContext ctx, extension GenModelUtilX genModelUtil) { - val a = c.allEAttributes - val d = c.allUserData - ''' - «IF !a.isEmpty || !d.isEmpty || c.fingerprint || c.resourceFingerprint || c.lookup » - // Use a forwarding map to delay calculation as much as possible; otherwise we may get recursive EObject resolution attempts - Map data = new AbstractForwardingResourceDescriptionStrategyMap() { - - @Override - protected void fill(final ImmutableMap.Builder builder) { - Object value = null; - «IF c.fingerprint» - // Fingerprint - value = getFingerprint(obj); - if (value != null) { - builder.put(IFingerprintComputer.OBJECT_FINGERPRINT, value.toString()); - } - «ELSEIF c.resourceFingerprint» - // Resource fingerprint - value = getFingerprint(obj); - if (value != null) { - builder.put(IFingerprintComputer.RESOURCE_FINGERPRINT, value.toString()); - } - «ENDIF» - «IF c.lookup» - // Allow lookups - «IF c.lookupPredicate !== null» - «javaContributorComment(c.lookupPredicate.location)» - if («c.lookupPredicate.javaExpression(ctx.clone('obj', c.type))») { - builder.put(DetachableEObjectDescription.ALLOW_LOOKUP, Boolean.TRUE.toString()); - } - «ELSE» - builder.put(DetachableEObjectDescription.ALLOW_LOOKUP, Boolean.TRUE.toString()); - «ENDIF» - «ENDIF» - «IF !a.isEmpty » - // Exported attributes - «FOR attr : a» - value = obj.eGet(«attr.literalIdentifier», false); - if (value != null) { - builder.put(«resourceDescriptionConstants.toSimpleName».«attr.constantName(c.type)», value.toString()); - } - «ENDFOR» - «ENDIF» - «IF !d.isEmpty » - // User data - «FOR data : d» - value = «data.expr.javaExpression(ctx.clone('obj', c.type))»; - if (value != null) { - builder.put(«resourceDescriptionConstants.toSimpleName».«data.constantName(c.type)», value.toString()); - } - «ENDFOR» - «ENDIF» - } - }; - acceptEObjectDescription(obj, data, acceptor.get()); - «ELSE» - acceptEObjectDescription(obj, acceptor.get()); - «ENDIF» - ''' - } -} diff --git a/com.avaloq.tools.ddk.xtext.export/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.export/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.expression.ide/.classpath b/com.avaloq.tools.ddk.xtext.expression.ide/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.xtext.expression.ide/.classpath +++ b/com.avaloq.tools.ddk.xtext.expression.ide/.classpath @@ -6,11 +6,6 @@
- - - - - diff --git a/com.avaloq.tools.ddk.xtext.expression.ide/.project b/com.avaloq.tools.ddk.xtext.expression.ide/.project index 0db12dc67d..568dbc3b69 100644 --- a/com.avaloq.tools.ddk.xtext.expression.ide/.project +++ b/com.avaloq.tools.ddk.xtext.expression.ide/.project @@ -65,35 +65,5 @@ 1 PARENT-1-PROJECT_LOC/ddk-configuration/.pmd - - .settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - - - .settings/org.eclipse.core.resources.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.resources.prefs - - - .settings/org.eclipse.core.runtime.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.runtime.prefs - - - .settings/org.eclipse.jdt.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.core.prefs - - - .settings/org.eclipse.jdt.ui.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.ui.prefs - - - .settings/org.eclipse.pde.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.pde.core.prefs - diff --git a/com.avaloq.tools.ddk.xtext.expression.ide/build.properties b/com.avaloq.tools.ddk.xtext.expression.ide/build.properties index 3f5513de7b..2d6ac57da2 100644 --- a/com.avaloq.tools.ddk.xtext.expression.ide/build.properties +++ b/com.avaloq.tools.ddk.xtext.expression.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.expression.ide/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.expression.ide/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.expression/.classpath b/com.avaloq.tools.ddk.xtext.expression/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.xtext.expression/.classpath +++ b/com.avaloq.tools.ddk.xtext.expression/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.expression/build.properties b/com.avaloq.tools.ddk.xtext.expression/build.properties index 5839f7a848..b533d62244 100644 --- a/com.avaloq.tools.ddk.xtext.expression/build.properties +++ b/com.avaloq.tools.ddk.xtext.expression/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ .,\ plugin.xml,\ diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/CodeGenerationX.java b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/CodeGenerationX.java new file mode 100644 index 0000000000..3495dee73c --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/CodeGenerationX.java @@ -0,0 +1,597 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.expression.generator; + +import java.util.ArrayList; +import java.util.List; + +import com.avaloq.tools.ddk.xtext.expression.expression.BooleanLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.BooleanOperation; +import com.avaloq.tools.ddk.xtext.expression.expression.CastedExpression; +import com.avaloq.tools.ddk.xtext.expression.expression.CollectionExpression; +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall; +import com.avaloq.tools.ddk.xtext.expression.expression.Identifier; +import com.avaloq.tools.ddk.xtext.expression.expression.IfExpression; +import com.avaloq.tools.ddk.xtext.expression.expression.IntegerLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.ListLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.Literal; +import com.avaloq.tools.ddk.xtext.expression.expression.NullLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall; +import com.avaloq.tools.ddk.xtext.expression.expression.RealLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.StringLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.SyntaxElement; +import com.avaloq.tools.ddk.xtext.expression.expression.TypeSelectExpression; + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class CodeGenerationX { + + private final ExpressionExtensionsX expressionExtensionsX = new ExpressionExtensionsX(); + + ////////////////////////////////////////////////// + // ENTRY POINTS + ////////////////////////////////////////////////// + public boolean isCompilable(final Expression it, final CompilationContext ctx) { + final String expr = javaExpression(it, ctx); + return expr != null && !expr.contains("/* NOT COMPILABLE: "); + } + + // dispatch javaExpression + protected String _javaExpression(final Void it, final CompilationContext ctx) { + return ""; + } + + protected String _javaExpression(final Expression it, final CompilationContext ctx) { + return notCompilable(it); + } + + public String notCompilable(final Expression it) { + return "/* NOT COMPILABLE: Complex expressions like \"" + expressionExtensionsX.serialize(it) + "\" cannot be translated to Java. Consider rewriting the expression or using a JAVA extension. */"; + } + + ////////////////////////////////////////////////// + // LITERALS + ////////////////////////////////////////////////// + protected String _javaExpression(final StringLiteral it, final CompilationContext ctx) { + return "\"" + javaEncode(it.getVal()) + "\""; + } + + protected String _javaExpression(final BooleanLiteral it, final CompilationContext ctx) { + return it.getVal(); + } + + protected String _javaExpression(final IntegerLiteral it, final CompilationContext ctx) { + return Integer.toString(it.getVal()); + } + + protected String _javaExpression(final NullLiteral it, final CompilationContext ctx) { + return "null"; + } + + protected String _javaExpression(final RealLiteral it, final CompilationContext ctx) { + return it.getVal(); + } + + protected String _javaExpression(final ListLiteral it, final CompilationContext ctx) { + if (it.getElements().isEmpty()) { + return "java.util.Collections.<" + ctx.javaType(ctx.getRequiredType().getName()) + "> emptyList()"; + } else if (it.getElements().size() == 1) { + return "java.util.Collections.singletonList(" + javaExpression(it.getElements().get(0), ctx) + ")"; + } else { + final List mapped = new ArrayList<>(); + for (final Expression e : it.getElements()) { + mapped.add(javaExpression(e, ctx)); + } + return "com.google.common.collect.Lists.newArrayList(" + join(", ", mapped) + ")"; + } + } + + ////////////////////////////////////////////////// + // TYPES AND VARIABLES + ////////////////////////////////////////////////// + protected String _javaExpression(final Identifier it, final CompilationContext ctx) { + if (isThis(it)) { + return ctx.getImplicitVariable(); + } else { + return join("::", it.getId()); + } + } + + public boolean isType(final FeatureCall it, final CompilationContext ctx) { + return it.getName() == null && it.getType() != null && ctx.isType(javaExpression(it.getType(), ctx)); + } + + public boolean isVariable(final Expression it, final CompilationContext ctx) { + return false; + } + + public boolean isVariable(final FeatureCall it, final CompilationContext ctx) { + return it.getTarget() == null && it.getName() == null && ctx.isVariable(javaExpression(it.getType(), ctx)); + } + + public String featureCallTarget(final FeatureCall it, final CompilationContext ctx) { + if (it.getTarget() == null || isThisCall(it.getTarget())) { + return ctx.getImplicitVariable(); + } else { + return javaExpression(it.getTarget(), ctx); + } + } + + ////////////////////////////////////////////////// + // BOOLEAN OPERATIONS + ////////////////////////////////////////////////// + protected String _javaExpression(final BooleanOperation it, final CompilationContext ctx) { + return autoBracket(it, javaExpression(it.getLeft(), ctx) + " " + it.getOperator() + " " + javaExpression(it.getRight(), ctx), ctx); + } + + ////////////////////////////////////////////////// + // COLLECTION OPERATIONS + ////////////////////////////////////////////////// + // TODO finish + protected String _javaExpression(final CollectionExpression it, final CompilationContext ctx) { + if ("select".equals(it.getName())) { + return "com.google.common.collect.Iterables.filter(" + javaExpression(it.getTarget(), ctx) + + ", new com.google.common.base.Predicate() { public boolean apply(Object " + + (it.getVar() != null ? it.getVar() : "e") + ") {return " + + javaExpression(it.getExp(), ctx) + ";} })"; + } else { + return notCompilable(it); + } + } + + protected String _javaExpression(final TypeSelectExpression it, final CompilationContext ctx) { + if (isSimpleNavigation(it, ctx)) { + return "com.google.common.collect.Iterables.filter(" + javaExpression(it.getTarget(), ctx) + ", " + ctx.javaType(javaExpression(it.getType(), ctx)) + ".class)"; + } else { + return notCompilable(it); + } + } + + ////////////////////////////////////////////////// + // TYPE CAST + ////////////////////////////////////////////////// + protected String _javaExpression(final CastedExpression it, final CompilationContext ctx) { + return "((" + ctx.javaType(javaExpression(it.getType(), ctx)) + ") " + javaExpression(it.getTarget(), ctx) + ")"; + } + + ////////////////////////////////////////////////// + // IF EXPRESSIONS + ////////////////////////////////////////////////// + protected String _javaExpression(final IfExpression it, final CompilationContext ctx) { + return autoBracket(it, javaExpression(it.getCondition(), ctx) + " ? " + javaExpression(it.getThenPart(), ctx) + " : " + javaExpression(it.getElsePart(), ctx), ctx); + } + + ////////////////////////////////////////////////// + // FEATURE CALLS + ////////////////////////////////////////////////// + protected String _javaExpression(final FeatureCall it, final CompilationContext ctx) { + if (isThisCall(it)) { + return ctx.getImplicitVariable(); + } else if (isVariable(it, ctx)) { + return javaExpression(it.getType(), ctx); + } else if (isType(it, ctx)) { + return ctx.javaType(javaExpression(it.getType(), ctx)); + } else if (isSimpleFeatureCall(it, ctx)) { + final String cf = expressionExtensionsX.calledFeature(it); + final String suffix; + if ("eContainer".equals(cf)) { + suffix = "eContainer"; + } else if ("isEmpty".equals(cf)) { + suffix = "isEmpty"; + } else { + suffix = featureCallName(toFirstUpper(cf)); + } + return featureCallTarget(it, ctx) + "." + suffix + "()"; + } else if (isSimpleNavigation(it, ctx)) { + return notCompilable(it); + } else { + final String cf = expressionExtensionsX.calledFeature(it); + final String suffix; + if ("eContainer".equals(cf)) { + suffix = "eContainer"; + } else if ("isEmpty".equals(cf)) { + suffix = "isEmpty"; + } else { + suffix = featureCallName(toFirstUpper(cf)); + } + return featureCallTarget(it, ctx) + "." + suffix + "()"; + } + } + + // TODO: actually, we should look at the feature and return "is" only if it has a boolean value... Can this be done?? + public String featureCallName(final String it) { + if (it.startsWith("^")) { + return featureCallName(toFirstUpper(it.substring(1, it.length()))); + } else { + return (it.startsWith("Is") ? "is" : "get") + it; + } + } + + public boolean isSimpleFeatureCall(final Expression it, final CompilationContext ctx) { + return false; + } + + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + public boolean isSimpleFeatureCall(final FeatureCall it, final CompilationContext ctx) { + return it.eClass().getName().contains("FeatureCall") && it.getName() == null && isFeature(it.getType()) && (it.getTarget() == null || isVariable(it.getTarget(), ctx) || isThisCall(it.getTarget())); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + // dispatch isSimpleNavigation + protected boolean _isSimpleNavigation(final Expression it, final CompilationContext ctx) { + return false; + } + + protected boolean _isSimpleNavigation(final TypeSelectExpression it, final CompilationContext ctx) { + return true; + } + + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _isSimpleNavigation(final FeatureCall it, final CompilationContext ctx) { + return it.getName() == null && isFeature(it.getType()) && (it.getTarget() == null || isVariable(it.getTarget(), ctx) || isThisCall(it.getTarget()) || isSimpleNavigation(it.getTarget(), ctx)); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + public boolean isSimpleNavigation(final Expression it, final CompilationContext ctx) { + if (it instanceof TypeSelectExpression typeSelectExpression) { + return _isSimpleNavigation(typeSelectExpression, ctx); + } else if (it instanceof FeatureCall featureCall) { + return _isSimpleNavigation(featureCall, ctx); + } else { + return it != null && _isSimpleNavigation(it, ctx); + } + } + + // dispatch navigationRoot + protected String _navigationRoot(final Void it, final CompilationContext ctx) { + return ""; + } + + protected String _navigationRootExpression(final Expression it, final CompilationContext ctx) { + return ""; + } + + protected String _navigationRoot(final FeatureCall it, final CompilationContext ctx) { + if (it.getTarget() != null) { + return navigationRoot(it.getTarget(), ctx); + } else { + if (isVariable(it, ctx)) { + return javaExpression(it, ctx); + } else { + return ctx.getImplicitVariable(); + } + } + } + + public String navigationRoot(final Expression it, final CompilationContext ctx) { + if (it instanceof FeatureCall featureCall) { + return _navigationRoot(featureCall, ctx); + } else if (it != null) { + return _navigationRootExpression(it, ctx); + } else { + return _navigationRoot((Void) null, ctx); + } + } + + // dispatch navigations + protected List _navigationsVoid(final Void it, final CompilationContext ctx) { + return new ArrayList<>(); + } + + protected List _navigationsExpression(final Expression it, final CompilationContext ctx) { + return new ArrayList<>(); + } + + protected List _navigations(final FeatureCall it, final CompilationContext ctx) { + final List navs = navigations(it.getTarget(), ctx); + if (navs.isEmpty() && (isThisCall(it) || isVariable(it, ctx))) { + return List.of(); + } else { + navs.add(expressionExtensionsX.calledFeature(it)); + return navs; + } + } + + protected List _navigations(final TypeSelectExpression it, final CompilationContext ctx) { + final List navs = navigations(it.getTarget(), ctx); + navs.add("typeSelect(" + qualifiedTypeName(it.getType(), ctx) + ")"); + return navs; + } + + public List navigations(final Expression it, final CompilationContext ctx) { + if (it instanceof TypeSelectExpression typeSelectExpression) { + return _navigations(typeSelectExpression, ctx); + } else if (it instanceof FeatureCall featureCall) { + return _navigations(featureCall, ctx); + } else if (it != null) { + return _navigationsExpression(it, ctx); + } else { + return _navigationsVoid(null, ctx); + } + } + + ////////////////////////////////////////////////// + // OPERATION CALLS + ////////////////////////////////////////////////// + // TODO handle eClass() + // TODO work out if 'this' should be added or not + protected String _javaExpression(final OperationCall it, final CompilationContext ctx) { + if ((it.getTarget() == null || isThisCall(it.getTarget())) && ctx.targetHasOperation(it)) { + final List mapped = new ArrayList<>(); + for (final Expression p : it.getParams()) { + mapped.add(javaExpression(p, ctx)); + } + return (it.getTarget() != null ? javaExpression(it.getTarget(), ctx) + "." : "") + it.getName() + "(" + join(", ", mapped) + ")"; + } else if (isJavaExtensionCall(it, ctx)) { + final List allParams; + if (it.getTarget() != null) { + allParams = new ArrayList<>(); + allParams.add(it.getTarget()); + allParams.addAll(it.getParams()); + } else { + allParams = it.getParams(); + } + final List mapped = new ArrayList<>(); + for (final Expression p : allParams) { + mapped.add(javaExpression(p, ctx)); + } + return calledJavaMethod(it, ctx) + "(" + join(", ", mapped) + ")"; + } else if (expressionExtensionsX.isArithmeticOperatorCall(it, ctx)) { + final List mapped = new ArrayList<>(); + for (final Expression e : it.getParams()) { + mapped.add(javaExpression(e, ctx)); + } + return autoBracket(it, join(" " + it.getName() + " ", mapped), ctx); + } else if (expressionExtensionsX.isSimpleConcatCall(it)) { + final List mapped = new ArrayList<>(); + for (final Expression e : it.getParams()) { + mapped.add(javaExpression(e, ctx)); + } + return join(" + ", mapped); + } else if (expressionExtensionsX.isPrefixExpression(it, ctx)) { + return autoBracket(it, it.getName() + javaExpression(it.getParams().get(0), ctx), ctx); + } else if ("first".equals(it.getName()) && it.getParams().isEmpty() && it.getTarget() != null) { + return javaExpression(it.getTarget(), ctx) + ".get(0)"; + } else if ("isInstance".equals(it.getName()) && it.getParams().size() == 1 && it.getTarget() instanceof FeatureCall && isType((FeatureCall) it.getTarget(), ctx)) { + return autoBracket(it, javaExpression(it.getParams().get(0), ctx) + " instanceof " + javaExpression(it.getTarget(), ctx), ctx); + } else if ("eContainer".equals(it.getName()) && it.getParams().isEmpty()) { + return javaExpression(it.getTarget(), ctx) + ".eContainer()"; + } else if (ctx.isExtension(it.getName())) { + return notCompilable(it); + } else { + final List mapped = new ArrayList<>(); + for (final Expression p : it.getParams()) { + mapped.add(javaExpression(p, ctx)); + } + return (it.getTarget() != null ? javaExpression(it.getTarget(), ctx) + "." : "") + it.getName() + "(" + (it.getParams().isEmpty() ? "" : join(", ", mapped)) + ")"; + } + } + + public boolean isJavaExtensionCall(final Expression it) { + return false; + } + + public boolean isJavaExtensionCall(final OperationCall it, final CompilationContext ctx) { + return !("isInstance".equals(it.getName())) && isSimpleCall(it, ctx) && calledJavaMethod(it, ctx) != null; + } + + public boolean isSimpleCall(final OperationCall it, final CompilationContext ctx) { + return (it.getTarget() == null || isCompilable(it.getTarget(), ctx)) && it.getParams().stream().allMatch(p -> isCompilable(p, ctx)); + } + + public String calledJavaMethod(final OperationCall it, final CompilationContext ctx) { + return ctx.getCalledJavaMethod(it); + } + + ////////////////////////////////////////////////// + // EXPRESSION BRACKETING + ////////////////////////////////////////////////// + public String autoBracket(final Expression it, final String javaCode, final CompilationContext ctx) { + if (requiresBracketing(it, ctx)) { + return "(" + javaCode + ")"; + } else { + return javaCode; + } + } + + // dispatch requiresBracketing (1 param: Expression, ctx) + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _requiresBracketing(final Expression it, final CompilationContext ctx) { + return (expressionExtensionsX.isPrefixExpression(it, ctx) || expressionExtensionsX.isInfixExpression(it, ctx)) && it.eContainer() != null && requiresBracketing(it, it.eContainer(), ctx); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + protected boolean _requiresBracketing(final Literal it, final CompilationContext ctx) { + return false; + } + + public boolean requiresBracketing(final Expression it, final CompilationContext ctx) { + if (it instanceof Literal literal) { + return _requiresBracketing(literal, ctx); + } else { + return it != null && _requiresBracketing(it, ctx); + } + } + + // dispatch requiresBracketing (2 params: Expression, parent, ctx) + protected boolean _requiresBracketingWithObject(final Expression it, final Object parent, final CompilationContext ctx) { + return false; + } + + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _requiresBracketingWithExpression(final Expression it, final Expression parent, final CompilationContext ctx) { + return expressionExtensionsX.isPrefixExpression(it, ctx) && expressionExtensionsX.isPrefixExpression(parent, ctx) + || (expressionExtensionsX.isInfixExpression(it, ctx) && (expressionExtensionsX.isPrefixExpression(parent, ctx) || expressionExtensionsX.isInfixExpression(parent, ctx))); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _requiresBracketing(final OperationCall it, final OperationCall parent, final CompilationContext ctx) { + return expressionExtensionsX.isPrefixExpression(it, ctx) && expressionExtensionsX.isPrefixExpression(parent, ctx) + || (expressionExtensionsX.isInfixExpression(it, ctx) && (expressionExtensionsX.isPrefixExpression(parent, ctx) || (expressionExtensionsX.isInfixExpression(parent, ctx) && !it.getName().equals(parent.getName())))); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + protected boolean _requiresBracketing(final BooleanOperation it, final BooleanOperation parent, final CompilationContext ctx) { + return !it.getOperator().equals(parent.getOperator()); + } + + public boolean requiresBracketing(final Expression it, final Object parent, final CompilationContext ctx) { + if (it instanceof OperationCall operationCall && parent instanceof OperationCall parentOp) { + return _requiresBracketing(operationCall, parentOp, ctx); + } else if (it instanceof BooleanOperation boolOp && parent instanceof BooleanOperation parentBool) { + return _requiresBracketing(boolOp, parentBool, ctx); + } else if (parent instanceof Expression parentExpr) { + return _requiresBracketingWithExpression(it, parentExpr, ctx); + } else { + return _requiresBracketingWithObject(it, parent, ctx); + } + } + + ////////////////////////////////////////////////// + // HELPER FUNCTIONS + ////////////////////////////////////////////////// + // dispatch isThisCall + protected boolean _isThisCall(final Expression it) { + return false; + } + + protected boolean _isThisCall(final FeatureCall it) { + return it.getName() == null && isThis(it.getType()); + } + + public boolean isThisCall(final Expression it) { + if (it instanceof FeatureCall featureCall) { + return _isThisCall(featureCall); + } else { + return it != null && _isThisCall(it); + } + } + + public boolean isFeature(final Identifier it) { + return it.getId() != null && it.getId().size() == 1; + } + + // dispatch isThis + protected boolean _isThis(final Expression it) { + return false; + } + + protected boolean _isThis(final Identifier it) { + return it.getId() != null && it.getId().size() == 1 && "this".equals(it.getId().get(0)); + } + + public boolean isThis(final SyntaxElement it) { + if (it instanceof Identifier identifier) { + return _isThis(identifier); + } else { + return it instanceof Expression expression && _isThis(expression); + } + } + + public String qualifiedTypeName(final Identifier it, final CompilationContext ctx) { + if (it.getId().size() == 2) { + return it.getId().get(0) + "::" + it.getId().get(1); + } else { + return qualifiedTypeName(ctx, it.getId().get(0)); + } + } + + public /*cached*/ String qualifiedTypeName(final CompilationContext it, final String name) { + return it.getQualifiedTypeName(name); + } + + // dispatch javaEncode + protected String _javaEncode(final Expression it) { + return javaEncode(expressionExtensionsX.serialize(it)); + } + + protected String _javaEncode(final String it) { + return org.eclipse.xtext.util.Strings.convertToJavaString(it); + } + + public String javaEncode(final Object it) { + if (it instanceof String s) { + return _javaEncode(s); + } else if (it instanceof Expression expr) { + return _javaEncode(expr); + } else { + return ""; + } + } + + public String join(final String separator, final List strings) { + if (strings.isEmpty()) { + return ""; + } else { + return internalJoin(separator, strings); + } + } + + private String internalJoin(final String separator, final List strings) { + return org.eclipse.xtext.util.Strings.concat(separator, strings); + } + + public String join(final String separator, final String strings) { + return strings; + } + + public String join(final String separator, final Void strings) { + return ""; + } + + // Public dispatcher for javaExpression + public String javaExpression(final SyntaxElement it, final CompilationContext ctx) { + if (it instanceof OperationCall operationCall) { + return _javaExpression(operationCall, ctx); + } else if (it instanceof CollectionExpression collectionExpression) { + return _javaExpression(collectionExpression, ctx); + } else if (it instanceof TypeSelectExpression typeSelectExpression) { + return _javaExpression(typeSelectExpression, ctx); + } else if (it instanceof FeatureCall featureCall) { + return _javaExpression(featureCall, ctx); + } else if (it instanceof BooleanOperation booleanOperation) { + return _javaExpression(booleanOperation, ctx); + } else if (it instanceof CastedExpression castedExpression) { + return _javaExpression(castedExpression, ctx); + } else if (it instanceof IfExpression ifExpression) { + return _javaExpression(ifExpression, ctx); + } else if (it instanceof StringLiteral stringLiteral) { + return _javaExpression(stringLiteral, ctx); + } else if (it instanceof BooleanLiteral booleanLiteral) { + return _javaExpression(booleanLiteral, ctx); + } else if (it instanceof IntegerLiteral integerLiteral) { + return _javaExpression(integerLiteral, ctx); + } else if (it instanceof NullLiteral nullLiteral) { + return _javaExpression(nullLiteral, ctx); + } else if (it instanceof RealLiteral realLiteral) { + return _javaExpression(realLiteral, ctx); + } else if (it instanceof ListLiteral listLiteral) { + return _javaExpression(listLiteral, ctx); + } else if (it instanceof Identifier identifier) { + return _javaExpression(identifier, ctx); + } else if (it instanceof Expression expression) { + return _javaExpression(expression, ctx); + } else if (it != null) { + return ""; + } else { + return _javaExpression((Void) null, ctx); + } + } + + private String toFirstUpper(final String s) { + if (s == null || s.isEmpty()) { + return s; + } + return Character.toUpperCase(s.charAt(0)) + s.substring(1); + } +} diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/CodeGenerationX.xtend b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/CodeGenerationX.xtend deleted file mode 100644 index 010a927aa0..0000000000 --- a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/CodeGenerationX.xtend +++ /dev/null @@ -1,373 +0,0 @@ - -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.expression.generator - -import com.avaloq.tools.ddk.xtext.expression.expression.Expression -import com.avaloq.tools.ddk.xtext.expression.expression.StringLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.BooleanLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.IntegerLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.NullLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.RealLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.ListLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.Identifier -import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall -import com.avaloq.tools.ddk.xtext.expression.expression.BooleanOperation -import com.avaloq.tools.ddk.xtext.expression.expression.CollectionExpression -import com.avaloq.tools.ddk.xtext.expression.expression.TypeSelectExpression -import com.avaloq.tools.ddk.xtext.expression.expression.CastedExpression -import com.avaloq.tools.ddk.xtext.expression.expression.IfExpression -import java.util.List -import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall -import com.avaloq.tools.ddk.xtext.expression.expression.Literal - -class CodeGenerationX { - - extension ExpressionExtensionsX = new ExpressionExtensionsX - - ////////////////////////////////////////////////// - // ENTRY POINTS - ////////////////////////////////////////////////// - def boolean isCompilable(Expression it, CompilationContext ctx) { - val expr = javaExpression(ctx) - expr !== null && !expr.contains('/* NOT COMPILABLE: ') - } - - def dispatch String javaExpression(Void it, CompilationContext ctx) { - '' - } - - def dispatch String javaExpression(Expression it, CompilationContext ctx) { - notCompilable - } - - def String notCompilable(Expression it) { - '/* NOT COMPILABLE: Complex expressions like "' + serialize() + '" cannot be translated to Java. Consider rewriting the expression or using a JAVA extension. */' - } - - ////////////////////////////////////////////////// - // LITERALS - ////////////////////////////////////////////////// - def dispatch String javaExpression(StringLiteral it, CompilationContext ctx) { - '"' + javaEncode(^val) + '"' - } - - def dispatch String javaExpression(BooleanLiteral it, CompilationContext ctx) { - ^val - } - - def dispatch String javaExpression(IntegerLiteral it, CompilationContext ctx) { - ^val.toString() - } - - def dispatch String javaExpression(NullLiteral it, CompilationContext ctx) { - "null" - } - - def dispatch String javaExpression(RealLiteral it, CompilationContext ctx) { - ^val.toString() - } - - def dispatch String javaExpression(ListLiteral it, CompilationContext ctx) { - if (elements.empty) { - "java.util.Collections.<" + ctx.javaType(ctx.requiredType.name) + "> emptyList()" - } else if (elements.size == 1) { - "java.util.Collections.singletonList(" + elements.head.javaExpression(ctx) + ")" - } else { - "com.google.common.collect.Lists.newArrayList(" + ', '.join(elements.map[javaExpression(ctx)]) + ")" - } - } - - ////////////////////////////////////////////////// - // TYPES AND VARIABLES - ////////////////////////////////////////////////// - def dispatch String javaExpression(Identifier it, CompilationContext ctx) { - if (isThis()) ctx.implicitVariable else '::'.join(id) - } - - def boolean isType(FeatureCall it, CompilationContext ctx) { - name === null && type !== null && ctx.isType(type.javaExpression(ctx)) - } - - def boolean isVariable(Expression it, CompilationContext ctx) { - false - } - - def boolean isVariable(FeatureCall it, CompilationContext ctx) { - target === null && name === null && ctx.isVariable(type.javaExpression(ctx)) - } - - def String featureCallTarget(FeatureCall it, CompilationContext ctx) { - if (target === null || target.isThisCall()) - ctx.implicitVariable - else - target.javaExpression(ctx) - } - - ////////////////////////////////////////////////// - // BOOLEAN OPERATIONS - ////////////////////////////////////////////////// - def dispatch String javaExpression(BooleanOperation it, CompilationContext ctx) { - autoBracket(left.javaExpression(ctx) + ' ' + operator + ' ' + right.javaExpression(ctx), ctx) - } - - ////////////////////////////////////////////////// - // COLLECTION OPERATIONS - ////////////////////////////////////////////////// - // TODO finish - def dispatch String javaExpression(CollectionExpression it, CompilationContext ctx) { - if ('select' == name) { - 'com.google.common.collect.Iterables.filter(' + target.javaExpression(ctx) + - ', new com.google.common.base.Predicate() { public boolean apply(Object ' + - (if (^var !== null) ^var else 'e') + ') {return ' + - exp.javaExpression(ctx) + ';} })' - } else { - notCompilable() - } - } - - def dispatch String javaExpression(TypeSelectExpression it, CompilationContext ctx) { - if (isSimpleNavigation(ctx)) - 'com.google.common.collect.Iterables.filter(' + target.javaExpression(ctx) + ', ' + ctx.javaType(type.javaExpression(ctx)) + '.class)' - else notCompilable() - } - - ////////////////////////////////////////////////// - // TYPE CAST - ////////////////////////////////////////////////// - def dispatch String javaExpression(CastedExpression it, CompilationContext ctx) { - '((' + ctx.javaType(type.javaExpression(ctx)) + ') ' + target.javaExpression(ctx) + ')' - } - - ////////////////////////////////////////////////// - // IF EXPRESSIONS - ////////////////////////////////////////////////// - def dispatch String javaExpression(IfExpression it, CompilationContext ctx) { - autoBracket(condition.javaExpression(ctx) + ' ? ' + thenPart.javaExpression(ctx) + ' : ' + elsePart.javaExpression(ctx), ctx) - } - - ////////////////////////////////////////////////// - // FEATURE CALLS - ////////////////////////////////////////////////// - def dispatch String javaExpression(FeatureCall it, CompilationContext ctx) { - if (isThisCall()) { - ctx.implicitVariable - } else if (isVariable(ctx)) { - type.javaExpression(ctx) - } else if (isType(ctx)) { - ctx.javaType(type.javaExpression(ctx)) - } else if (isSimpleFeatureCall(ctx)) { - featureCallTarget(ctx) + '.' + (if (calledFeature() == 'eContainer') 'eContainer' else (if (calledFeature() == 'isEmpty') 'isEmpty' else calledFeature().toFirstUpper().featureCallName())) + '()' - } else if (isSimpleNavigation(ctx)) { - notCompilable() - } else { - featureCallTarget(ctx) + '.' + (if (calledFeature() == 'eContainer') 'eContainer' else (if (calledFeature() == 'isEmpty') 'isEmpty' else calledFeature().toFirstUpper().featureCallName())) + '()' - } - } - - // TODO: actually, we should look at the feature and return "is" only if it has a boolean value... Can this be done?? - def String featureCallName(String it) { - if (it.startsWith('^')) - it.substring(1, it.length).toFirstUpper().featureCallName() - else - (if (it.startsWith('Is')) 'is' else 'get') + it - } - - def boolean isSimpleFeatureCall(Expression it, CompilationContext ctx) { - false - } - - def boolean isSimpleFeatureCall(FeatureCall it, CompilationContext ctx) { - eClass.name.contains('FeatureCall') && name === null && type.isFeature() && (target === null || target.isVariable(ctx) || target.isThisCall()) - } - - def dispatch boolean isSimpleNavigation(Expression it, CompilationContext ctx) { - false - } - - def dispatch boolean isSimpleNavigation(TypeSelectExpression it, CompilationContext ctx) { - true - } - - def dispatch boolean isSimpleNavigation(FeatureCall it, CompilationContext ctx) { - name === null && type.isFeature() && (target === null || target.isVariable(ctx) || target.isThisCall() || target.isSimpleNavigation(ctx)) - } - - def dispatch String navigationRoot(Void it, CompilationContext ctx) { - '' - } - - def dispatch String navigationRoot(Expression it, CompilationContext ctx) { - '' - } - - def dispatch String navigationRoot(FeatureCall it, CompilationContext ctx) { - if (target !== null) target.navigationRoot(ctx) else (if (isVariable(ctx)) javaExpression(ctx) else ctx.implicitVariable) - } - - def dispatch List navigations(Void it, CompilationContext ctx) { - {} - } - - def dispatch List navigations(Expression it, CompilationContext ctx) { - {} - } - - def dispatch List navigations(FeatureCall it, CompilationContext ctx) { - val navs = target.navigations(ctx) - if (navs.isEmpty && (isThisCall() || isVariable(ctx))) #[] else { navs.add(calledFeature()); navs } - } - - def dispatch List navigations(TypeSelectExpression it, CompilationContext ctx) { - val navs = target.navigations(ctx) - navs.add("typeSelect(" + type.qualifiedTypeName(ctx) + ")") - navs - } - - ////////////////////////////////////////////////// - // OPERATION CALLS - ////////////////////////////////////////////////// - // TODO handle eClass() - // TODO work out if 'this' should be added or not - def dispatch String javaExpression(OperationCall it, CompilationContext ctx) { - if ((target === null || target.isThisCall()) && ctx.targetHasOperation(it)) { - (if (target !== null) target.javaExpression(ctx) + '.' else '') + name + '(' + ', '.join(params.map[javaExpression(ctx)]) + ')' - } else if (isJavaExtensionCall(ctx)) { - calledJavaMethod(ctx) + '(' + ', '.join((if (target !== null) { val l = newArrayList(target); l.addAll(params); l } else params).map[javaExpression(ctx)]) + ')' - } else if (isArithmeticOperatorCall(ctx)) { - autoBracket((' ' + name + ' ').join(params.map(e|e.javaExpression(ctx))), ctx) - } else if (isSimpleConcatCall()) { - (' + ').join(params.map(e|e.javaExpression(ctx))) - } else if (isPrefixExpression(ctx)) { - autoBracket(name + params.head.javaExpression(ctx), ctx) - } else if ('first' == name && params.isEmpty && target !== null) { - target.javaExpression(ctx) + '.get(0)' - } else if ('isInstance' == name && params.size == 1 && target instanceof FeatureCall && (target as FeatureCall).isType(ctx)) { - autoBracket(params.head.javaExpression(ctx) + ' instanceof ' + target.javaExpression(ctx), ctx) - } else if ('eContainer' == name && params.isEmpty) { - target.javaExpression(ctx) + '.eContainer()' - } else if (ctx.isExtension(name)) { - notCompilable() - } else { - (if (target !== null) target.javaExpression(ctx) + '.' else '') + name + '(' + (if (params.isEmpty) '' else ', '.join(params.map[javaExpression(ctx)])) + ')' - } - } - - def boolean isJavaExtensionCall(Expression it) { - false - } - - def boolean isJavaExtensionCall(OperationCall it, CompilationContext ctx) { - name != 'isInstance' && isSimpleCall(ctx) && calledJavaMethod(ctx) !== null - } - - def boolean isSimpleCall(OperationCall it, CompilationContext ctx) { - (target === null || target.isCompilable(ctx)) && params.forall(p|p.isCompilable(ctx)) - } - - def String calledJavaMethod(OperationCall it, CompilationContext ctx) { - ctx.calledJavaMethod(it) - } - - def /*cached*/ String calledJavaMethod(CompilationContext it, OperationCall call) { - getCalledJavaMethod(call) - } - - ////////////////////////////////////////////////// - // EXPRESSION BRACKETING - ////////////////////////////////////////////////// - def String autoBracket(Expression it, String javaCode, CompilationContext ctx) { - if (requiresBracketing(ctx)) '(' + javaCode + ')' else javaCode - } - - def dispatch boolean requiresBracketing(Expression it, CompilationContext ctx) { - (isPrefixExpression(ctx) || isInfixExpression(ctx)) && eContainer() !== null && requiresBracketing(it, eContainer(), ctx) - } - - def dispatch boolean requiresBracketing(Literal it, CompilationContext ctx) { - false - } - - def dispatch boolean requiresBracketing(Expression it, Object parent, CompilationContext ctx) { - false - } - - def dispatch boolean requiresBracketing(Expression it, Expression parent, CompilationContext ctx) { - isPrefixExpression(ctx) && parent.isPrefixExpression(ctx) || - (isInfixExpression(ctx) && (parent.isPrefixExpression(ctx) || parent.isInfixExpression(ctx))) - } - - def dispatch boolean requiresBracketing(OperationCall it, OperationCall parent, CompilationContext ctx) { - isPrefixExpression(ctx) && parent.isPrefixExpression(ctx) || - (isInfixExpression(ctx) && (parent.isPrefixExpression(ctx) || (parent.isInfixExpression(ctx) && name != parent.name))) - } - - def dispatch boolean requiresBracketing(BooleanOperation it, BooleanOperation parent, CompilationContext ctx) { - operator != parent.operator - } - - ////////////////////////////////////////////////// - // HELPER FUNCTIONS - ////////////////////////////////////////////////// - def dispatch boolean isThisCall(Expression it) { - false - } - - def dispatch boolean isThisCall(FeatureCall it) { - name === null && type.isThis() - } - - def boolean isFeature(Identifier it) { - id !== null && id.size == 1 - } - - def dispatch boolean isThis(Expression it) { - false - } - - def dispatch boolean isThis(Identifier it) { - id !== null && id.size == 1 && id.head == "this" - } - - def String qualifiedTypeName(Identifier it, CompilationContext ctx) { - if (id.size == 2) id.get(0) + "::" + id.get(1) else ctx.qualifiedTypeName(id.head) - } - - def /*cached*/ String qualifiedTypeName(CompilationContext it, String name) { - getQualifiedTypeName(name) - } - - def dispatch String javaEncode(Expression it) { - javaEncode(serialize()) - } - - def dispatch String javaEncode(String it) { - org.eclipse.xtext.util.Strings.convertToJavaString(it) - } - - def String join(String it, List strings) { - if (strings.isEmpty) '' else internalJoin(strings) - } - - private def String internalJoin(String it, List strings) { - org.eclipse.xtext.util.Strings.concat(it, strings) - } - - def String join(String it, String strings) { - strings - } - - def String join(String it, Void strings) { - '' - } - -} diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/ExpressionExtensionsX.java b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/ExpressionExtensionsX.java new file mode 100644 index 0000000000..168c5b3c3a --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/ExpressionExtensionsX.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. it program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies it distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.expression.generator; + +import com.avaloq.tools.ddk.xtext.expression.expression.BooleanOperation; +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall; +import com.avaloq.tools.ddk.xtext.expression.expression.IfExpression; +import com.avaloq.tools.ddk.xtext.expression.expression.ListLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall; +import org.eclipse.emf.ecore.EObject; + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class ExpressionExtensionsX { + + protected String _serialize(final EObject it) { + return ExpressionExtensions.serialize(it); + } + + protected String _serialize(final Void it) { + return null; + } + + public String serialize(final Object it) { + if (it == null) { + return _serialize((Void) null); + } else if (it instanceof EObject eObject) { + return _serialize(eObject); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + it); + } + } + + protected boolean _isEmptyList(final Expression it) { + return false; + } + + protected boolean _isEmptyList(final ListLiteral it) { + return it.getElements().isEmpty(); + } + + public boolean isEmptyList(final Expression it) { + if (it instanceof ListLiteral listLiteral) { + return _isEmptyList(listLiteral); + } else { + return _isEmptyList(it); + } + } + + public boolean isSimpleConcatCall(final OperationCall it) { + return "+".equals(it.getName()) && it.getType() == null && it.getTarget() == null && !it.getParams().isEmpty(); + } + + public boolean isNumber(final Expression it, final CompilationContext ctx) { + return ctx.findType("Real").isAssignableFrom(ctx.analyze(it)); + } + + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _isArithmeticOperatorCall(final OperationCall it, final CompilationContext ctx) { + return it.getType() == null && it.getTarget() == null && it.getParams().size() > 1 + && ("+".equals(it.getName()) || "-".equals(it.getName()) || "*".equals(it.getName()) || "/".equals(it.getName())) + && it.getParams().stream().allMatch(p -> isNumber(p, ctx)); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + protected boolean _isArithmeticOperatorCall(final Expression it, final CompilationContext ctx) { + return false; + } + + public boolean isArithmeticOperatorCall(final Expression it, final CompilationContext ctx) { + if (it instanceof OperationCall operationCall) { + return _isArithmeticOperatorCall(operationCall, ctx); + } else { + return _isArithmeticOperatorCall(it, ctx); + } + } + + protected boolean _isPrefixExpression(final Expression it, final CompilationContext ctx) { + return false; + } + + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _isPrefixExpression(final OperationCall it, final CompilationContext ctx) { + return it.getType() == null && it.getTarget() == null && it.getParams().size() == 1 + && ("-".equals(it.getName()) || "!".equals(it.getName())); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + public boolean isPrefixExpression(final Expression it, final CompilationContext ctx) { + if (it instanceof OperationCall operationCall) { + return _isPrefixExpression(operationCall, ctx); + } else { + return _isPrefixExpression(it, ctx); + } + } + + protected boolean _isInfixExpression(final Void it, final CompilationContext ctx) { + return false; + } + + protected boolean _isInfixExpression(final Expression it, final CompilationContext ctx) { + return false; + } + + protected boolean _isInfixExpression(final OperationCall it, final CompilationContext ctx) { + return isArithmeticOperatorCall(it, ctx) || "isInstance".equals(it.getName()); + } + + protected boolean _isInfixExpression(final IfExpression it, final CompilationContext ctx) { + return true; + } + + protected boolean _isInfixExpression(final BooleanOperation it, final CompilationContext ctx) { + return true; + } + + public boolean isInfixExpression(final Expression it, final CompilationContext ctx) { + if (it == null) { + return _isInfixExpression((Void) null, ctx); + } else if (it instanceof BooleanOperation booleanOperation) { + return _isInfixExpression(booleanOperation, ctx); + } else if (it instanceof IfExpression ifExpression) { + return _isInfixExpression(ifExpression, ctx); + } else if (it instanceof OperationCall operationCall) { + return _isInfixExpression(operationCall, ctx); + } else { + return _isInfixExpression(it, ctx); + } + } + + public String calledFeature(final FeatureCall it) { + return it.getType().getId().get(0); + } + +} diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/ExpressionExtensionsX.xtend b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/ExpressionExtensionsX.xtend deleted file mode 100644 index e8341936ad..0000000000 --- a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/ExpressionExtensionsX.xtend +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. it program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies it distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.expression.generator - -import com.avaloq.tools.ddk.xtext.expression.expression.BooleanOperation -import com.avaloq.tools.ddk.xtext.expression.expression.Expression -import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall -import com.avaloq.tools.ddk.xtext.expression.expression.IfExpression -import com.avaloq.tools.ddk.xtext.expression.expression.ListLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall -import org.eclipse.emf.ecore.EObject - -class ExpressionExtensionsX { - - def dispatch String serialize(EObject it) { - ExpressionExtensions.serialize(it) - } - - def dispatch String serialize(Void it) { - null - } - - def dispatch boolean isEmptyList(Expression it) { - false - } - - def dispatch boolean isEmptyList(ListLiteral it) { - elements.isEmpty - } - - def boolean isSimpleConcatCall(OperationCall it) { - name == '+' && type === null && target === null && !params.isEmpty - } - - def boolean isNumber(Expression it, CompilationContext ctx) { - ctx.findType('Real').isAssignableFrom(ctx.analyze(it)) - } - - def dispatch boolean isArithmeticOperatorCall(OperationCall it, CompilationContext ctx) { - type === null && target === null && params.size > 1 && (name == '+' || name == '-' || name == '*' || name == '/') && params.forall(p|p.isNumber(ctx)) - } - - def dispatch boolean isArithmeticOperatorCall(Expression it, CompilationContext ctx) { - false - } - - def dispatch boolean isPrefixExpression(Expression it, CompilationContext ctx) { - false - } - - def dispatch boolean isPrefixExpression(OperationCall it, CompilationContext ctx) { - type === null && target === null && params.size == 1 && (name == '-' || name == '!') - } - - def dispatch boolean isInfixExpression(Void it, CompilationContext ctx) { - false - } - - def dispatch boolean isInfixExpression(Expression it, CompilationContext ctx) { - false - } - - def dispatch boolean isInfixExpression(OperationCall it, CompilationContext ctx) { - isArithmeticOperatorCall(ctx) || 'isInstance' == name - } - - def dispatch boolean isInfixExpression(IfExpression it, CompilationContext ctx) { - true - } - - def dispatch boolean isInfixExpression(BooleanOperation it, CompilationContext ctx) { - true - } - - def String calledFeature(FeatureCall it) { - type.id.head - } - -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GenModelUtilX.java b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GenModelUtilX.java new file mode 100644 index 0000000000..f980970bf3 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GenModelUtilX.java @@ -0,0 +1,213 @@ +package com.avaloq.tools.ddk.xtext.expression.generator; + +import com.google.inject.Inject; +import java.util.Iterator; +import java.util.Objects; +import org.eclipse.emf.codegen.ecore.genmodel.GenClass; +import org.eclipse.emf.codegen.ecore.genmodel.GenDataType; +import org.eclipse.emf.codegen.ecore.genmodel.GenModel; +import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage; +import org.eclipse.emf.codegen.ecore.genmodel.GenPackage; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.ENamedElement; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.impl.EPackageImpl; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider; +import org.eclipse.xtext.scoping.IGlobalScopeProvider; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.xbase.lib.StringExtensions; + + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class GenModelUtilX { + + @Inject + private Naming naming; + + @Inject + private IGlobalScopeProvider globalScopeProvider; + + @Inject + private ResourceDescriptionsProvider resourceDescriptionsProvider; + + private Resource context; + + // CHECKSTYLE:CHECK-OFF HiddenField + public void setResource(final Resource context) { + this.context = context; + } + // CHECKSTYLE:CHECK-ON HiddenField + + public /* cached */ String qualifiedPackageInterfaceName(final EPackage it) { + final GenPackage genPackage = genPackage(it); + if (genPackage != null) { + return genPackage.getQualifiedPackageInterfaceName(); + } else if (!Objects.equals(it.getClass(), EPackageImpl.class)) { + return it.getClass().getInterfaces()[0].getName(); + } + return null; + } + + // CHECKSTYLE:CONSTANTS-OFF + public String qualifiedSwitchClassName(final EPackage it) { + final GenPackage genPackage = genPackage(it); + if (genPackage != null && genPackage.isLiteralsInterface()) { + return genPackage.getUtilitiesPackageName() + "." + genPackage.getSwitchClassName(); + } else { + return naming.toJavaPackage(qualifiedPackageInterfaceName(it)) + ".util." + StringExtensions.toFirstUpper(it.getName()) + "Switch"; // heuristic + } + } + + protected /* cached */ String _literalIdentifier(final EStructuralFeature it) { + final EClass eClass = it.getEContainingClass(); + final EPackage ePackage = eClass.getEPackage(); + final GenPackage genPackage = genPackage(ePackage); + if (genPackage != null && genPackage.isLiteralsInterface()) { + return literalIdentifier(eClass) + "__" + format(it.getName()).toUpperCase(); + } else { + return qualifiedPackageInterfaceName(ePackage) + ".eINSTANCE.get" + eClass.getName() + "_" + StringExtensions.toFirstUpper(it.getName()) + "()"; + } + } + + protected /* cached */ String _literalIdentifier(final EClass it) { + final GenPackage genPackage = genPackage(it.getEPackage()); + if (genPackage != null && genPackage.isLiteralsInterface()) { + return genPackage.getQualifiedPackageInterfaceName() + ".Literals." + format(it.getName()).toUpperCase(); + } else { + return qualifiedPackageInterfaceName(it.getEPackage()) + ".eINSTANCE.get" + it.getName() + "()"; + } + } + + // defined to simplify debugging generator problems + protected /* cached */ String _literalIdentifier(final ENamedElement it) { + return "DOES_NOT_EXIST"; + } + + // defined to simplify debugging generator problems + protected /* cached */ String _literalIdentifier(final Void it) { + return "DOES_NOT_EXIST"; + } + + // e.g. EcorePackage.ENAMED_ELEMENT + public /* cached */ String classifierIdLiteral(final EClass it) { + return qualifiedPackageInterfaceName(it.getEPackage()) + "." + format(it.getName()).toUpperCase(); + } + // CHECKSTYLE:CONSTANTS-ON + + protected /* cached */ String _instanceClassName(final Void it) { + return ""; + } + + protected /* cached */ String _instanceClassName(final EClassifier it) { + if (it.getInstanceClassName() != null) { + return it.getInstanceClassName(); + } + return it.getName(); + } + + protected /* cached */ String _instanceClassName(final EDataType it) { + if (it.getInstanceClassName() != null) { + return it.getInstanceClassName(); + } + return genDataType(it).getQualifiedInstanceClassName(); + } + + protected /* cached */ String _instanceClassName(final EClass it) { + if (it.getInstanceClassName() != null) { + return it.getInstanceClassName(); + } + return genClass(it).getQualifiedInterfaceName(); + } + + public /* cached */ GenPackage genPackage(final EModelElement it) { + final EPackage ePackage = EcoreUtil2.getContainerOfType(it, EPackage.class); + if (globalScopeProvider != null && context != null) { + final IScope scope = globalScopeProvider.getScope(context, GenModelPackage.Literals.GEN_MODEL__GEN_PACKAGES, null); + if (scope != null && ePackage != null) { + final IEObjectDescription desc = scope.getSingleElement(QualifiedName.create(ePackage.getNsURI())); + if (desc != null) { + return (GenPackage) EcoreUtil.resolve(desc.getEObjectOrProxy(), context); + } else { + final IResourceDescriptions resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(context); + final Iterator descs = resourceDescriptions.getExportedObjects(GenModelPackage.Literals.GEN_PACKAGE, QualifiedName.create(ePackage.getNsURI()), false).iterator(); + if (descs.hasNext()) { + return (GenPackage) EcoreUtil.resolve(descs.next().getEObjectOrProxy(), context); + } + // In case Xcore is installed GenPackages will be indexed using GenPackage#getQualifiedPackageName() + for (final IEObjectDescription candidate : resourceDescriptions.getExportedObjectsByType(GenModelPackage.Literals.GEN_PACKAGE)) { + if (Objects.equals(candidate.getName().getLastSegment(), ePackage.getName())) { + final GenPackage resolvedCanidate = (GenPackage) EcoreUtil.resolve(candidate.getEObjectOrProxy(), context); + if (!resolvedCanidate.eIsProxy() && Objects.equals(resolvedCanidate.getEcorePackage(), ePackage)) { + return resolvedCanidate; + } + } + } + } + } + } + ResourceSet resourceSet; + if (context != null) { + resourceSet = context.getResourceSet(); + } else if (it.eResource().getResourceSet() != null) { + resourceSet = it.eResource().getResourceSet(); + } else { + resourceSet = new ResourceSetImpl(); + } + return ePackage != null ? GenModelUtil2.findGenPackage(ePackage, resourceSet) : null; + } + + public /* cached */ GenModel genModel(final EModelElement it) { + final GenPackage genPackage = genPackage(it); + return genPackage != null ? genPackage.getGenModel() : null; + } + + public /* cached */ GenClass genClass(final EClass it) { + final GenPackage genPackage = genPackage(it); + return genPackage != null ? (GenClass) genPackage.getGenModel().findGenClassifier(it) : null; + } + + public /* cached */ GenDataType genDataType(final EDataType it) { + final GenPackage genPackage = genPackage(it); + return genPackage != null ? (GenDataType) genPackage.getGenModel().findGenClassifier(it) : null; + } + + public /* cached */ String format(final String name) { + return GenModelUtil2.format(name); + } + + public String literalIdentifier(final ENamedElement it) { + if (it instanceof EClass eClass) { + return _literalIdentifier(eClass); + } else if (it instanceof EStructuralFeature eStructuralFeature) { + return _literalIdentifier(eStructuralFeature); + } else if (it != null) { + return _literalIdentifier(it); + } else { + return _literalIdentifier((Void) null); + } + } + + public String instanceClassName(final EClassifier it) { + if (it instanceof EClass eClass) { + return _instanceClassName(eClass); + } else if (it instanceof EDataType eDataType) { + return _instanceClassName(eDataType); + } else if (it != null) { + return _instanceClassName(it); + } else { + return _instanceClassName((Void) null); + } + } +} diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GenModelUtilX.xtend b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GenModelUtilX.xtend deleted file mode 100644 index b86febc268..0000000000 --- a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GenModelUtilX.xtend +++ /dev/null @@ -1,160 +0,0 @@ -package com.avaloq.tools.ddk.xtext.expression.generator - -import com.google.inject.Inject -import org.eclipse.emf.codegen.ecore.genmodel.GenClass -import org.eclipse.emf.codegen.ecore.genmodel.GenDataType -import org.eclipse.emf.codegen.ecore.genmodel.GenModel -import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage -import org.eclipse.emf.codegen.ecore.genmodel.GenPackage -import org.eclipse.emf.ecore.EClass -import org.eclipse.emf.ecore.EClassifier -import org.eclipse.emf.ecore.EDataType -import org.eclipse.emf.ecore.EModelElement -import org.eclipse.emf.ecore.ENamedElement -import org.eclipse.emf.ecore.EPackage -import org.eclipse.emf.ecore.EStructuralFeature -import org.eclipse.emf.ecore.impl.EPackageImpl -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.naming.QualifiedName -import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider -import org.eclipse.xtext.scoping.IGlobalScopeProvider - -class GenModelUtilX { - - @Inject - extension Naming - - @Inject - IGlobalScopeProvider globalScopeProvider - @Inject - ResourceDescriptionsProvider resourceDescriptionsProvider - - var Resource context - - def setResource(Resource context) { - this.context = context - } - - def /*cached*/ String qualifiedPackageInterfaceName(EPackage it) { - val genPackage = genPackage() - if (genPackage !== null) - return genPackage.qualifiedPackageInterfaceName - else if (it.class != EPackageImpl) - return it.class.interfaces.head.name - return null - } - - def String qualifiedSwitchClassName(EPackage it) { - val genPackage = genPackage() - if (genPackage !== null && genPackage.literalsInterface) - genPackage.utilitiesPackageName + "." + genPackage.switchClassName - else - qualifiedPackageInterfaceName().toJavaPackage + '.util.' + name.toFirstUpper + 'Switch' // heuristic - } - - def /*cached*/ dispatch String literalIdentifier(EStructuralFeature it) { - val eClass = EContainingClass - val ePackage = eClass.EPackage - val genPackage = ePackage.genPackage() - if (genPackage !== null && genPackage.literalsInterface) - eClass.literalIdentifier() + "__" + name.format().toUpperCase() - else - ePackage.qualifiedPackageInterfaceName() + ".eINSTANCE.get" + eClass.name + "_" + name.toFirstUpper() + "()" - } - - def /*cached*/ dispatch String literalIdentifier(EClass it) { - val genPackage = EPackage.genPackage() - if (genPackage !== null && genPackage.literalsInterface) - genPackage.qualifiedPackageInterfaceName + ".Literals." + name.format().toUpperCase() - else - EPackage.qualifiedPackageInterfaceName() + ".eINSTANCE.get" + name + "()" - } - - // defined to simplify debugging generator problems - def /*cached*/ dispatch String literalIdentifier(ENamedElement it) { - "DOES_NOT_EXIST" - } - - // defined to simplify debugging generator problems - def /*cached*/ dispatch String literalIdentifier(Void it) { - "DOES_NOT_EXIST" - } - - // e.g. EcorePackage.ENAMED_ELEMENT - def /*cached*/ String classifierIdLiteral(EClass it) { - EPackage.qualifiedPackageInterfaceName() + "." + name.format().toUpperCase() - } - - def /*cached*/ dispatch String instanceClassName(Void it) { - "" - } - - def /*cached*/ dispatch String instanceClassName(EClassifier it) { - if (instanceClassName !== null) - return instanceClassName - return name - } - - def /*cached*/ dispatch String instanceClassName(EDataType it) { - if (instanceClassName !== null) - return instanceClassName - return genDataType(it).qualifiedInstanceClassName - } - - def /*cached*/ dispatch String instanceClassName(EClass it) { - if (instanceClassName !== null) - return instanceClassName - return genClass(it).qualifiedInterfaceName - } - - def /*cached*/ GenPackage genPackage(EModelElement it) { - val ePackage = EcoreUtil2.getContainerOfType(it, EPackage) - if (globalScopeProvider !== null && context !== null) { - val scope = globalScopeProvider.getScope(context, GenModelPackage.Literals.GEN_MODEL__GEN_PACKAGES, null) - if (scope !== null && ePackage !== null) { - val desc = scope.getSingleElement(QualifiedName.create(ePackage.nsURI)) - if (desc !== null) { - return EcoreUtil.resolve(desc.EObjectOrProxy, context) as GenPackage - } else { - val resourceDescriptions = resourceDescriptionsProvider.getResourceDescriptions(context) - val descs = resourceDescriptions.getExportedObjects(GenModelPackage.Literals.GEN_PACKAGE, QualifiedName.create(ePackage.nsURI), false).iterator - if (descs.hasNext) { - return EcoreUtil.resolve(descs.next.EObjectOrProxy, context) as GenPackage - } - // In case Xcore is installed GenPackages will be indexed using GenPackage#getQualifiedPackageName() - for (candidate : resourceDescriptions.getExportedObjectsByType(GenModelPackage.Literals.GEN_PACKAGE).filter[name.lastSegment == ePackage.name]) { - val resolvedCanidate = EcoreUtil.resolve(candidate.EObjectOrProxy, context) as GenPackage - if (!resolvedCanidate.eIsProxy && resolvedCanidate.getEcorePackage == ePackage) { - return resolvedCanidate - } - } - } - } - } - val resourceSet = if (context !== null) context.resourceSet else if (it.eResource.resourceSet !== null) it.eResource.resourceSet else new ResourceSetImpl - return if (ePackage !== null) GenModelUtil2.findGenPackage(ePackage, resourceSet) else null - } - - def /*cached*/ GenModel genModel(EModelElement it) { - val genPackage = genPackage(it) - return if (genPackage !== null) genPackage.genModel else null - } - - def /*cached*/ GenClass genClass(EClass it) { - val genPackage = genPackage(it) - return if (genPackage !== null) genPackage.genModel.findGenClassifier(it) as GenClass else null - } - - def /*cached*/ GenDataType genDataType(EDataType it) { - val genPackage = genPackage(it) - return if (genPackage !== null) genPackage.genModel.findGenClassifier(it) as GenDataType else null; - } - - def /*cached*/ String format(String name) { - GenModelUtil2.format(name) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtilX.java b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtilX.java new file mode 100644 index 0000000000..616a0d20bf --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtilX.java @@ -0,0 +1,31 @@ +package com.avaloq.tools.ddk.xtext.expression.generator; + +import java.util.Set; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.Grammar; +import com.avaloq.tools.ddk.xtext.util.EObjectUtil; + +@SuppressWarnings("nls") +public class GeneratorUtilX { + + public String xmlContributorComment(final String source) { + return ""; + } + + public String javaContributorComment(final String source) { + return "// contributed by " + source; + } + + public String location(final EObject obj) { + return EObjectUtil.getFileLocation(obj); + } + + public Set allInstantiatedTypes(final Grammar grammar) { + return GeneratorUtil.allInstantiatedTypes(grammar); + } + + public boolean canContain(final EClass eClass, final Set others, final Grammar g) { + return GeneratorUtil.canContain(eClass, others, g); + } +} diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtilX.xtend b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtilX.xtend deleted file mode 100644 index 90f780a714..0000000000 --- a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/GeneratorUtilX.xtend +++ /dev/null @@ -1,30 +0,0 @@ -package com.avaloq.tools.ddk.xtext.expression.generator - -import java.util.Set -import org.eclipse.emf.ecore.EClass -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtext.Grammar -import com.avaloq.tools.ddk.xtext.util.EObjectUtil - -class GeneratorUtilX { - - def String xmlContributorComment(String source) { - '' - } - - def String javaContributorComment(String source) { - '// contributed by ' + source - } - - def String location(EObject obj) { - EObjectUtil.getFileLocation(obj) - } - - def Set allInstantiatedTypes(Grammar it) { - GeneratorUtil.allInstantiatedTypes(it) - } - - def boolean canContain(EClass it, Set others, Grammar g) { - GeneratorUtil.canContain(it, others, g) - } -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/Naming.java b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/Naming.java new file mode 100644 index 0000000000..adcb50b5d4 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/Naming.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.expression.generator; + +import org.eclipse.xtext.util.Strings; + +@SuppressWarnings("nls") +public class Naming { + + // CHECKSTYLE:CONSTANTS-OFF + public String toFileName(final String qualifiedName) { + return toJavaPackage(qualifiedName).replace('.', '/') + '/' + toSimpleName(qualifiedName) + ".java"; + } + + public String toJavaPackage(final String qualifiedName) { + return Strings.skipLastToken(qualifiedName, "."); + } + + public String toSimpleName(final String qualifiedName) { + return Strings.lastToken(qualifiedName, "."); + } + // CHECKSTYLE:CONSTANTS-ON +} diff --git a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/Naming.xtend b/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/Naming.xtend deleted file mode 100644 index 953814b6bc..0000000000 --- a/com.avaloq.tools.ddk.xtext.expression/src/com/avaloq/tools/ddk/xtext/expression/generator/Naming.xtend +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.expression.generator - -import org.eclipse.xtext.util.Strings - -class Naming { - - def toFileName(String qualifiedName) { - qualifiedName.toJavaPackage.replace('.', '/') + '/' + qualifiedName.toSimpleName + ".java" - } - - def toJavaPackage(String qualifiedName) { - Strings.skipLastToken(qualifiedName, '.') - } - - def toSimpleName(String qualifiedName) { - Strings.lastToken(qualifiedName, '.') - } - -} diff --git a/com.avaloq.tools.ddk.xtext.expression/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.expression/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.format.generator/.classpath b/com.avaloq.tools.ddk.xtext.format.generator/.classpath index 8d8d754076..eb603c114d 100644 --- a/com.avaloq.tools.ddk.xtext.format.generator/.classpath +++ b/com.avaloq.tools.ddk.xtext.format.generator/.classpath @@ -7,6 +7,5 @@ - diff --git a/com.avaloq.tools.ddk.xtext.format.generator/build.properties b/com.avaloq.tools.ddk.xtext.format.generator/build.properties index 672bdf9170..b2adb6124f 100644 --- a/com.avaloq.tools.ddk.xtext.format.generator/build.properties +++ b/com.avaloq.tools.ddk.xtext.format.generator/build.properties @@ -1,5 +1,4 @@ -source.. = src/,\ - xtend-gen/ +source.. = src/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.format.generator/src/com/avaloq/tools/ddk/xtext/format/generator/FormatFragment2.java b/com.avaloq.tools.ddk.xtext.format.generator/src/com/avaloq/tools/ddk/xtext/format/generator/FormatFragment2.java new file mode 100644 index 0000000000..aa5007c874 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format.generator/src/com/avaloq/tools/ddk/xtext/format/generator/FormatFragment2.java @@ -0,0 +1,348 @@ +/** + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * Contributors: + * Avaloq Group AG - initial API and implementation + */ +package com.avaloq.tools.ddk.xtext.format.generator; + +import com.avaloq.tools.ddk.xtext.format.FormatStandaloneSetup; +import com.avaloq.tools.ddk.xtext.formatting.AbstractExtendedFormatter; +import com.avaloq.tools.ddk.xtext.formatting.DirectNodeModelStreamer; +import com.avaloq.tools.ddk.xtext.formatting.RegionNodeModelFormatter; +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.formatting.IFormatter; +import org.eclipse.xtext.formatting.INodeModelFormatter; +import org.eclipse.xtext.formatting.INodeModelStreamer; +import org.eclipse.xtext.xtext.generator.AbstractStubGeneratingFragment; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.JavaFileAccess; +import org.eclipse.xtext.xtext.generator.model.ManifestAccess; +import org.eclipse.xtext.xtext.generator.model.TextFileAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtext.xtext.generator.model.XtendFileAccess; +import org.eclipse.xtext.xtext.generator.util.BooleanGeneratorOption; + +/** + * MWE fragment for the format language. + */ +@SuppressWarnings("nls") +public class FormatFragment2 extends AbstractStubGeneratingFragment { + + @Inject + private FileAccessFactory fileAccessFactory; + + @Inject + private XtextGeneratorNaming xtextGeneratorNaming; + + @Inject + private GrammarAccessExtensions grammarAccessExtensions; + + private static final String RUNTIME_PLUGIN = "com.avaloq.tools.ddk.xtext"; + + private final BooleanGeneratorOption generateFormatStub = new BooleanGeneratorOption(true); + + public boolean isGenerateFormatStub() { + return generateFormatStub.get(); + } + + public void setGenerateFormatStub(final boolean generateStub) { + generateFormatStub.set(generateStub); + } + + /** + * Class-wide logger. + */ + private static final Logger LOGGER = LogManager.getLogger(FormatFragment2.class); + + /** + * The model for the format resource. + */ + private String baseFormatterClassName = AbstractExtendedFormatter.class.getName(); + + /** + * Set the super type / base class of the formatter. + * @param baseFormatterClass the FQN of the base formatter + */ + public void setBaseFormatterClassName(final String baseFormatterClass) { + baseFormatterClassName = baseFormatterClass; + } + + /** + * Get the super type / base class of the formatter. + * @return the FQN of the base formatter + */ + public String getBaseFormatterClassName() { + return baseFormatterClassName; + } + + protected TypeReference getFormatterStub(final Grammar grammar) { + return new TypeReference(xtextGeneratorNaming.getRuntimeBasePackage(grammar) + ".formatting." + GrammarUtil.getSimpleName(grammar) + "Formatter"); + } + + protected String getFormatStub(final Grammar grammar) { + final String formatter = getFormatterStub(grammar).getPath(); + return formatter.substring(0, formatter.lastIndexOf('/') + 1) + GrammarUtil.getSimpleName(grammar) + ".format"; + } + + @Override + public void generate() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("executing generate for " + getClass().getName()); + } + + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(IFormatter.class), new TypeReference(FormatGeneratorUtil.getFormatterName(getGrammar(), ""))) + .addTypeToType(TypeReference.typeRef(INodeModelFormatter.class), TypeReference.typeRef(RegionNodeModelFormatter.class)) + .addTypeToType(TypeReference.typeRef(INodeModelStreamer.class), TypeReference.typeRef(DirectNodeModelStreamer.class)) + .contributeTo(getLanguage().getRuntimeGenModule()); + + ManifestAccess manifest = getProjectConfig().getRuntime().getManifest(); + if (manifest != null) { + manifest.getRequiredBundles().add("org.eclipse.emf.ecore"); + manifest.getRequiredBundles().add(RUNTIME_PLUGIN); + } + ManifestAccess eclipsePluginManifest = getProjectConfig().getEclipsePlugin().getManifest(); + if (eclipsePluginManifest != null) { + eclipsePluginManifest.getRequiredBundles().add(RUNTIME_PLUGIN); + } + ManifestAccess genericIdeManifest = getProjectConfig().getGenericIde().getManifest(); + if (genericIdeManifest != null) { + genericIdeManifest.getRequiredBundles().add(RUNTIME_PLUGIN); + } + + FormatStandaloneSetup.doSetup(); + doGenerateStubFiles(); + } + + protected void doGenerateStubFiles() { + if (!isGenerateStub()) { + return; + } + if (isGenerateXtendStub()) { + final XtendFileAccess xtendFile = doGetXtendStubFile(); + if (xtendFile != null) { + xtendFile.writeTo(getProjectConfig().getRuntime().getSrc()); + } + } else { + final JavaFileAccess javaFile = doGetJavaStubFile(); + if (javaFile != null) { + javaFile.writeTo(getProjectConfig().getRuntime().getSrc()); + } + } + if (isGenerateFormatStub()) { + final TextFileAccess formatFile = doGetFormatStubFile(); + if (formatFile != null) { + formatFile.writeTo(getProjectConfig().getRuntime().getSrc()); + } + } + } + + // CHECKSTYLE:CONSTANTS-OFF + protected XtendFileAccess doGetXtendStubFile() { + final XtendFileAccess xtendFile = fileAccessFactory.createXtendFile(getFormatterStub(getGrammar())); + xtendFile.setResourceSet(getLanguage().getResourceSet()); + + xtendFile.setContent(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation builder) { + builder.append("import com.avaloq.tools.ddk.xtext.formatting.ExtendedLineEntry"); + builder.newLine(); + builder.append("import java.util.List"); + builder.newLine(); + builder.newLine(); + builder.append("/**"); + builder.newLine(); + builder.append(" * This class contains custom formatting declarations."); + builder.newLine(); + builder.append(" *"); + builder.newLine(); + builder.append(" * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting"); + builder.newLine(); + builder.append(" * on how and when to use it."); + builder.newLine(); + builder.append(" *"); + builder.newLine(); + builder.append(" * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example"); + builder.newLine(); + builder.append(" */"); + builder.newLine(); + builder.append("class "); + builder.append(getFormatterStub(getGrammar()).getSimpleName()); + builder.append(" extends "); + builder.append(FormatGeneratorUtil.getFormatterName(getGrammar(), "Abstract")); + builder.append(" {"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" @"); + builder.append(Inject.class, " "); + builder.append(" extension "); + builder.append(grammarAccessExtensions.getGrammarAccess(getGrammar()), " "); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" override executeCustomPostFormatAction(ExtendedLineEntry lineEntry, List previousEntries) {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return null"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append(" override protected getMLCommentRule() {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return ML_COMMENTRule"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append(" override protected getSLCommentRule() {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return SL_COMMENTRule"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append(" override protected isUnformattedContent(String content) {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return false"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append("}"); + builder.newLine(); + } + }); + return xtendFile; + } + + protected JavaFileAccess doGetJavaStubFile() { + final JavaFileAccess javaFile = fileAccessFactory.createJavaFile(getFormatterStub(getGrammar())); + javaFile.setResourceSet(getLanguage().getResourceSet()); + + javaFile.setContent(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation builder) { + builder.append("import com.avaloq.tools.ddk.xtext.formatting.ExtendedLineEntry;"); + builder.newLine(); + builder.append("import java.util.List;"); + builder.newLine(); + builder.newLine(); + builder.append("import org.eclipse.xtext.TerminalRule;"); + builder.newLine(); + builder.newLine(); + builder.append("/**"); + builder.newLine(); + builder.append(" * This class contains custom formatting declarations."); + builder.newLine(); + builder.append(" *"); + builder.newLine(); + builder.append(" * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting"); + builder.newLine(); + builder.append(" * on how and when to use it."); + builder.newLine(); + builder.append(" *"); + builder.newLine(); + builder.append(" * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example"); + builder.newLine(); + builder.append(" */"); + builder.newLine(); + builder.append("public class "); + builder.append(getFormatterStub(getGrammar()).getSimpleName()); + builder.append(" extends "); + builder.append(FormatGeneratorUtil.getFormatterName(getGrammar(), "Abstract")); + builder.append(" {"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" @"); + builder.append(Inject.class, " "); + builder.append(" "); + builder.append(grammarAccessExtensions.getGrammarAccess(getGrammar()), " "); + builder.append(" grammarAccess;"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" @Override"); + builder.newLine(); + builder.append(" protected boolean isUnformattedContent(String content) {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return false;"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append(" @Override"); + builder.newLine(); + builder.append(" protected TerminalRule getSLCommentRule() {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return grammarAccess.getSL_COMMENTRule();"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append(" @Override"); + builder.newLine(); + builder.append(" protected TerminalRule getMLCommentRule() {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return grammarAccess.getML_COMMENTRule();"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append(" @Override"); + builder.newLine(); + builder.append(" public String executeCustomPostFormatAction(ExtendedLineEntry lineEntry, List previousEntries) {"); + builder.newLine(); + builder.append(" // TODO Auto-generated method stub"); + builder.newLine(); + builder.append(" return null;"); + builder.newLine(); + builder.append(" }"); + builder.newLine(); + builder.newLine(); + builder.append("}"); + builder.newLine(); + } + }); + return javaFile; + } + + protected TextFileAccess doGetFormatStubFile() { + final TextFileAccess formatFile = fileAccessFactory.createTextFile(getFormatStub(getGrammar())); + + formatFile.setContent(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation builder) { + builder.append("formatter for "); + builder.append(getGrammar().getName()); + builder.newLineIfNotEmpty(); + builder.newLine(); + } + }); + return formatFile; + } + // CHECKSTYLE:CONSTANTS-ON +} diff --git a/com.avaloq.tools.ddk.xtext.format.generator/src/com/avaloq/tools/ddk/xtext/format/generator/FormatFragment2.xtend b/com.avaloq.tools.ddk.xtext.format.generator/src/com/avaloq/tools/ddk/xtext/format/generator/FormatFragment2.xtend deleted file mode 100644 index 957a3d0d9f..0000000000 --- a/com.avaloq.tools.ddk.xtext.format.generator/src/com/avaloq/tools/ddk/xtext/format/generator/FormatFragment2.xtend +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * Contributors: - * Avaloq Group AG - initial API and implementation - */ -package com.avaloq.tools.ddk.xtext.format.generator - -import com.avaloq.tools.ddk.xtext.format.FormatStandaloneSetup -import com.avaloq.tools.ddk.xtext.formatting.AbstractExtendedFormatter -import com.avaloq.tools.ddk.xtext.formatting.DirectNodeModelStreamer -import com.avaloq.tools.ddk.xtext.formatting.RegionNodeModelFormatter -import com.google.inject.Inject -import org.apache.logging.log4j.Logger -import org.apache.logging.log4j.LogManager; -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.formatting.IFormatter -import org.eclipse.xtext.formatting.INodeModelFormatter -import org.eclipse.xtext.formatting.INodeModelStreamer -import org.eclipse.xtext.xtext.generator.AbstractStubGeneratingFragment -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess -import org.eclipse.xtext.xtext.generator.model.TypeReference -import org.eclipse.xtext.xtext.generator.util.BooleanGeneratorOption - -import static org.eclipse.xtext.GrammarUtil.* - -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* - -/** - * MWE fragment for the format language. - */ -class FormatFragment2 extends AbstractStubGeneratingFragment { - - @Inject FileAccessFactory fileAccessFactory - @Inject extension XtextGeneratorNaming - @Inject extension GrammarAccessExtensions - - static final String RUNTIME_PLUGIN = "com.avaloq.tools.ddk.xtext" - - val generateFormatStub = new BooleanGeneratorOption(true) - - def boolean isGenerateFormatStub() { - generateFormatStub.get - } - - def void setGenerateFormatStub(boolean generateStub) { - this.generateFormatStub.set(generateStub) - } - - /** - * Class-wide logger. - */ - static final Logger LOGGER = LogManager::getLogger(typeof(FormatFragment2)) - - /** - * The model for the format resource. - */ - var baseFormatterClassName = AbstractExtendedFormatter.name - - /** - * Set the super type / base class of the formatter. - * @param baseFormatterClass the FQN of the base formatter - */ - def void setBaseFormatterClassName(String baseFormatterClass) { - baseFormatterClassName = baseFormatterClass - } - - /** - * Get the super type / base class of the formatter. - * @return the FQN of the base formatter - */ - def getBaseFormatterClassName() { - return baseFormatterClassName - } - - protected def TypeReference getFormatterStub(Grammar grammar) { - new TypeReference(grammar.runtimeBasePackage + '.formatting.' + getSimpleName(grammar) + 'Formatter') - } - - protected def String getFormatStub(Grammar grammar) { - val formatter = grammar.formatterStub.path - formatter.substring(0, formatter.lastIndexOf('/') + 1) + getSimpleName(grammar) + '.format' - } - - override void generate() { - if (LOGGER.isInfoEnabled()) { - LOGGER.info('''executing generate for «getClass().getName()»'''.toString) - } - - new GuiceModuleAccess.BindingFactory() - .addTypeToType(IFormatter.typeRef, new TypeReference(FormatGeneratorUtil::getFormatterName(grammar, ""))) - .addTypeToType(INodeModelFormatter.typeRef, RegionNodeModelFormatter.typeRef) - .addTypeToType(INodeModelStreamer.typeRef, DirectNodeModelStreamer.typeRef) - .contributeTo(language.runtimeGenModule) - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest.requiredBundles += "org.eclipse.emf.ecore" - projectConfig.runtime.manifest.requiredBundles += RUNTIME_PLUGIN - } - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.eclipsePlugin.manifest.requiredBundles += RUNTIME_PLUGIN - } - - if (projectConfig.genericIde.manifest !== null) { - projectConfig.genericIde.manifest.requiredBundles+= RUNTIME_PLUGIN - } - - FormatStandaloneSetup.doSetup() - doGenerateStubFiles() - } - - protected def doGenerateStubFiles() { - if(!isGenerateStub) { - return - } - if (isGenerateXtendStub) { - val xtendFile = doGetXtendStubFile - xtendFile?.writeTo(projectConfig.runtime.src) - } else { - val javaFile = doGetJavaStubFile - javaFile?.writeTo(projectConfig.runtime.src) - } - if (isGenerateFormatStub) { - val formatFile = doGetFormatStubFile - formatFile?.writeTo(projectConfig.runtime.src) - } - } - - protected def doGetXtendStubFile() { - val xtendFile = fileAccessFactory.createXtendFile(grammar.formatterStub) - xtendFile.resourceSet = language.resourceSet - - xtendFile.content = ''' - import com.avaloq.tools.ddk.xtext.formatting.ExtendedLineEntry - import java.util.List - - /** - * This class contains custom formatting declarations. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting - * on how and when to use it. - * - * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example - */ - class «grammar.formatterStub.simpleName» extends «FormatGeneratorUtil::getFormatterName(grammar, "Abstract")» { - - @«Inject» extension «grammar.grammarAccess» - - override executeCustomPostFormatAction(ExtendedLineEntry lineEntry, List previousEntries) { - // TODO Auto-generated method stub - return null - } - - override protected getMLCommentRule() { - // TODO Auto-generated method stub - return ML_COMMENTRule - } - - override protected getSLCommentRule() { - // TODO Auto-generated method stub - return SL_COMMENTRule - } - - override protected isUnformattedContent(String content) { - // TODO Auto-generated method stub - return false - } - - } - ''' - return xtendFile - } - - protected def doGetJavaStubFile() { - val javaFile = fileAccessFactory.createJavaFile(grammar.formatterStub) - javaFile.resourceSet = language.resourceSet - - javaFile.content = ''' - import com.avaloq.tools.ddk.xtext.formatting.ExtendedLineEntry; - import java.util.List; - - import org.eclipse.xtext.TerminalRule; - - /** - * This class contains custom formatting declarations. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting - * on how and when to use it. - * - * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example - */ - public class «grammar.formatterStub.simpleName» extends «FormatGeneratorUtil::getFormatterName(grammar, "Abstract")» { - - @«Inject» «grammar.grammarAccess» grammarAccess; - - @Override - protected boolean isUnformattedContent(String content) { - // TODO Auto-generated method stub - return false; - } - - @Override - protected TerminalRule getSLCommentRule() { - // TODO Auto-generated method stub - return grammarAccess.getSL_COMMENTRule(); - } - - @Override - protected TerminalRule getMLCommentRule() { - // TODO Auto-generated method stub - return grammarAccess.getML_COMMENTRule(); - } - - @Override - public String executeCustomPostFormatAction(ExtendedLineEntry lineEntry, List previousEntries) { - // TODO Auto-generated method stub - return null; - } - - } - ''' - return javaFile - } - - protected def doGetFormatStubFile() { - val formatFile = fileAccessFactory.createTextFile(grammar.formatStub) - - formatFile.content = ''' - formatter for «grammar.name» - - ''' - return formatFile - } -} diff --git a/com.avaloq.tools.ddk.xtext.format.generator/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.format.generator/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.format.ide/.classpath b/com.avaloq.tools.ddk.xtext.format.ide/.classpath index 66b4755301..dee17f20f7 100644 --- a/com.avaloq.tools.ddk.xtext.format.ide/.classpath +++ b/com.avaloq.tools.ddk.xtext.format.ide/.classpath @@ -7,7 +7,6 @@ - diff --git a/com.avaloq.tools.ddk.xtext.format.ide/.project b/com.avaloq.tools.ddk.xtext.format.ide/.project index 98bb967ba7..2d78e4bef1 100644 --- a/com.avaloq.tools.ddk.xtext.format.ide/.project +++ b/com.avaloq.tools.ddk.xtext.format.ide/.project @@ -65,35 +65,5 @@ 1 PARENT-1-PROJECT_LOC/ddk-configuration/.pmd - - .settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - - - .settings/org.eclipse.core.resources.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.resources.prefs - - - .settings/org.eclipse.core.runtime.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.runtime.prefs - - - .settings/org.eclipse.jdt.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.core.prefs - - - .settings/org.eclipse.jdt.ui.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.ui.prefs - - - .settings/org.eclipse.pde.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.pde.core.prefs - diff --git a/com.avaloq.tools.ddk.xtext.format.ide/build.properties b/com.avaloq.tools.ddk.xtext.format.ide/build.properties index bef28d3d93..ec65859a4c 100644 --- a/com.avaloq.tools.ddk.xtext.format.ide/build.properties +++ b/com.avaloq.tools.ddk.xtext.format.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeModule.java b/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeModule.java new file mode 100644 index 0000000000..857ceeac50 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeModule.java @@ -0,0 +1,11 @@ +/* + * generated by Xtext 2.26 + */ +package com.avaloq.tools.ddk.xtext.format.ide; + + +/** + * Use this class to register ide components. + */ +public class FormatIdeModule extends AbstractFormatIdeModule { +} diff --git a/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeModule.xtend b/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeModule.xtend deleted file mode 100644 index e64de3e6d2..0000000000 --- a/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeModule.xtend +++ /dev/null @@ -1,11 +0,0 @@ -/* - * generated by Xtext 2.26 - */ -package com.avaloq.tools.ddk.xtext.format.ide - - -/** - * Use this class to register ide components. - */ -class FormatIdeModule extends AbstractFormatIdeModule { -} diff --git a/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeSetup.java b/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeSetup.java new file mode 100644 index 0000000000..d62207f5cd --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeSetup.java @@ -0,0 +1,21 @@ +/* + * generated by Xtext 2.25.0 + */ +package com.avaloq.tools.ddk.xtext.format.ide; + +import com.avaloq.tools.ddk.xtext.format.FormatRuntimeModule; +import com.avaloq.tools.ddk.xtext.format.FormatStandaloneSetup; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.eclipse.xtext.util.Modules2; + +/** + * Initialization support for running Xtext languages as language servers. + */ +public class FormatIdeSetup extends FormatStandaloneSetup { + + @Override + public Injector createInjector() { + return Guice.createInjector(Modules2.mixin(new FormatRuntimeModule(), new FormatIdeModule())); + } +} diff --git a/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeSetup.xtend b/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeSetup.xtend deleted file mode 100644 index 97f5bdd664..0000000000 --- a/com.avaloq.tools.ddk.xtext.format.ide/src/com/avaloq/tools/ddk/xtext/format/ide/FormatIdeSetup.xtend +++ /dev/null @@ -1,20 +0,0 @@ -/* - * generated by Xtext 2.25.0 - */ -package com.avaloq.tools.ddk.xtext.format.ide - -import com.avaloq.tools.ddk.xtext.format.FormatRuntimeModule -import com.avaloq.tools.ddk.xtext.format.FormatStandaloneSetup -import com.google.inject.Guice -import org.eclipse.xtext.util.Modules2 - -/** - * Initialization support for running Xtext languages as language servers. - */ -class FormatIdeSetup extends FormatStandaloneSetup { - - override createInjector() { - Guice.createInjector(Modules2.mixin(new FormatRuntimeModule, new FormatIdeModule)) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.format.ide/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.format.ide/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.format.test/.classpath b/com.avaloq.tools.ddk.xtext.format.test/.classpath index bc8caf1683..9209effae2 100644 --- a/com.avaloq.tools.ddk.xtext.format.test/.classpath +++ b/com.avaloq.tools.ddk.xtext.format.test/.classpath @@ -3,7 +3,6 @@ - diff --git a/com.avaloq.tools.ddk.xtext.format.test/build.properties b/com.avaloq.tools.ddk.xtext.format.test/build.properties index 39b83bd4a8..4404a080c8 100644 --- a/com.avaloq.tools.ddk.xtext.format.test/build.properties +++ b/com.avaloq.tools.ddk.xtext.format.test/build.properties @@ -1,7 +1,6 @@ source.. = src/,\ src-gen/,\ - resource/,\ - xtend-gen/ + resource/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/FormatParsingTest.java b/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/FormatParsingTest.java new file mode 100644 index 0000000000..2c153c05de --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/FormatParsingTest.java @@ -0,0 +1,44 @@ +/* + * generated by Xtext 2.14.0 + */ +package com.avaloq.tools.ddk.xtext.format; + +import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration; +import com.google.inject.Inject; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@ExtendWith(InjectionExtension.class) +@InjectWith(FormatInjectorProvider.class) +@SuppressWarnings("nls") +public class FormatParsingTest { + + @Inject + private ParseHelper parseHelper; + + @Test + public void loadModel() throws Exception { + String input = """ + formatter for MyDsl + + const String SOME_STRING = ""; + const int SOME_INT = 2; + + Person { + } + """; + final FormatConfiguration result = parseHelper.parse(input); + assertNotNull(result); + boolean hasSyntaxErrors = ((XtextResource) result.eResource()).getParseResult().hasSyntaxErrors(); + assertFalse(hasSyntaxErrors, + "Unexpected errors: " + String.join(", ", + result.eResource().getErrors().stream().map(Object::toString).toList())); + } +} diff --git a/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/FormatParsingTest.xtend b/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/FormatParsingTest.xtend deleted file mode 100644 index e061f64b2e..0000000000 --- a/com.avaloq.tools.ddk.xtext.format.test/src/com/avaloq/tools/ddk/xtext/format/FormatParsingTest.xtend +++ /dev/null @@ -1,40 +0,0 @@ -/* - * generated by Xtext 2.14.0 - */ -package com.avaloq.tools.ddk.xtext.format - -import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration -import com.google.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.util.ParseHelper - -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertNotNull -import static org.junit.jupiter.api.Assertions.assertFalse -import org.eclipse.xtext.resource.XtextResource - -@ExtendWith(InjectionExtension) -@InjectWith(FormatInjectorProvider) -class FormatParsingTest { - @Inject - ParseHelper parseHelper - - @Test - def void loadModel() { - val result = parseHelper.parse(''' - formatter for MyDsl - - const String SOME_STRING = ""; - const int SOME_INT = 2; - - Person { - } ''' - ) - assertNotNull(result) - assertFalse( - ((result.eResource) as XtextResource).getParseResult. - hasSyntaxErrors, '''Unexpected errors: «result.eResource.errors.join(", ")»''') - } -} diff --git a/com.avaloq.tools.ddk.xtext.format.test/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.format.test/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.format.ui/.classpath b/com.avaloq.tools.ddk.xtext.format.ui/.classpath index 9e1b018817..72be310083 100644 --- a/com.avaloq.tools.ddk.xtext.format.ui/.classpath +++ b/com.avaloq.tools.ddk.xtext.format.ui/.classpath @@ -8,7 +8,6 @@ - diff --git a/com.avaloq.tools.ddk.xtext.format.ui/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.format.ui/META-INF/MANIFEST.MF index 3fbf9573bd..1b0046432a 100644 --- a/com.avaloq.tools.ddk.xtext.format.ui/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.xtext.format.ui/META-INF/MANIFEST.MF @@ -25,8 +25,7 @@ Require-Bundle: com.avaloq.tools.ddk.xtext.format;visibility:=reexport, org.eclipse.xtext.xbase.ui, org.eclipse.jdt.debug.ui, org.eclipse.xtext.xbase.lib, - org.eclipse.xtext.ui.codetemplates.ui, - org.eclipse.xtend.lib;resolution:=optional + org.eclipse.xtext.ui.codetemplates.ui Import-Package: org.apache.log4j Bundle-RequiredExecutionEnvironment: JavaSE-21 Export-Package: com.avaloq.tools.ddk.xtext.format.ui.builder, diff --git a/com.avaloq.tools.ddk.xtext.format.ui/build.properties b/com.avaloq.tools.ddk.xtext.format.ui/build.properties index 31255ed05b..e10dcceb6a 100644 --- a/com.avaloq.tools.ddk.xtext.format.ui/build.properties +++ b/com.avaloq.tools.ddk.xtext.format.ui/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ .,\ plugin.xml \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.format.ui/src/com/avaloq/tools/ddk/xtext/format/ui/FormatUiModule.java b/com.avaloq.tools.ddk.xtext.format.ui/src/com/avaloq/tools/ddk/xtext/format/ui/FormatUiModule.java new file mode 100644 index 0000000000..ec4bec973b --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format.ui/src/com/avaloq/tools/ddk/xtext/format/ui/FormatUiModule.java @@ -0,0 +1,55 @@ +/* + * generated by Xtext 2.14.0 + */ +package com.avaloq.tools.ddk.xtext.format.ui; + +import com.avaloq.tools.ddk.xtext.format.ui.builder.FormatBuilderParticipant; +import com.avaloq.tools.ddk.xtext.format.ui.hyperlinking.FormatHyperlinkHelper; +import com.avaloq.tools.ddk.xtext.ui.templates.KeywordAwareCrossReferenceTemplateVariableResolver; +import com.google.inject.Binder; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.eclipse.xtext.builder.IXtextBuilderParticipant; +import org.eclipse.xtext.ui.editor.hyperlinking.IHyperlinkHelper; +import org.eclipse.xtext.ui.editor.templates.CrossReferenceTemplateVariableResolver; +import org.eclipse.xtext.xtext.generator.model.project.IXtextProjectConfig; +import org.eclipse.xtext.xtext.generator.model.project.XtextProjectConfig; + +/** + * Use this class to register components to be used within the Eclipse IDE. + */ +public class FormatUiModule extends AbstractFormatUiModule { + + public FormatUiModule(final AbstractUIPlugin plugin) { + super(plugin); + } + + /** + * Binds a {@link CrossReferenceTemplateVariableResolver} which prefixes keywords with escape characters. + * + * @return {@link KeywordAwareCrossReferenceTemplateVariableResolver} + */ + public Class bindCrossReferenceTemplateVariableResolver() { + return KeywordAwareCrossReferenceTemplateVariableResolver.class; + } + + /** + * Bind hyperlink helper to provide hyperlinking from "override" keyword to extended rule. + * + * @return FormatHyperlinkHelper.class + */ + @Override + public Class bindIHyperlinkHelper() { + return FormatHyperlinkHelper.class; + } + + @Override + public Class bindIXtextBuilderParticipant() { + return FormatBuilderParticipant.class; + } + + @Override + public void configure(final Binder binder) { + super.configure(binder); + binder.bind(IXtextProjectConfig.class).to(XtextProjectConfig.class); + } +} diff --git a/com.avaloq.tools.ddk.xtext.format.ui/src/com/avaloq/tools/ddk/xtext/format/ui/FormatUiModule.xtend b/com.avaloq.tools.ddk.xtext.format.ui/src/com/avaloq/tools/ddk/xtext/format/ui/FormatUiModule.xtend deleted file mode 100644 index e9ff58f92c..0000000000 --- a/com.avaloq.tools.ddk.xtext.format.ui/src/com/avaloq/tools/ddk/xtext/format/ui/FormatUiModule.xtend +++ /dev/null @@ -1,47 +0,0 @@ -/* - * generated by Xtext 2.14.0 - */ -package com.avaloq.tools.ddk.xtext.format.ui - -import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor -import org.eclipse.xtext.ui.editor.templates.CrossReferenceTemplateVariableResolver -import com.avaloq.tools.ddk.xtext.ui.templates.KeywordAwareCrossReferenceTemplateVariableResolver -import com.avaloq.tools.ddk.xtext.format.ui.hyperlinking.FormatHyperlinkHelper -import com.avaloq.tools.ddk.xtext.format.ui.builder.FormatBuilderParticipant -import org.eclipse.xtext.xtext.generator.model.project.IXtextProjectConfig -import org.eclipse.xtext.xtext.generator.model.project.XtextProjectConfig -import com.google.inject.Binder - -/** - * Use this class to register components to be used within the Eclipse IDE. - */ -@FinalFieldsConstructor -class FormatUiModule extends AbstractFormatUiModule { - - /** - * Binds a {@link CrossReferenceTemplateVariableResolver} which prefixes keywords with escape characters. - * - * @return {@link KeywordAwareCrossReferenceTemplateVariableResolver} - */ - def Class bindCrossReferenceTemplateVariableResolver() { - return KeywordAwareCrossReferenceTemplateVariableResolver - } - - /** - * Bind hyperlink helper to provide hyperlinking from "override" keyword to extended rule. - * - * @return FormatHyperlinkHelper.class - */ - override bindIHyperlinkHelper() { - return FormatHyperlinkHelper - } - - override bindIXtextBuilderParticipant() { - return FormatBuilderParticipant - } - - override configure(Binder binder) { - super.configure(binder); - binder.bind(IXtextProjectConfig).to(XtextProjectConfig); - } -} diff --git a/com.avaloq.tools.ddk.xtext.format.ui/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.format.ui/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.format/.classpath b/com.avaloq.tools.ddk.xtext.format/.classpath index 7e674d9ee4..0ddb998ae0 100644 --- a/com.avaloq.tools.ddk.xtext.format/.classpath +++ b/com.avaloq.tools.ddk.xtext.format/.classpath @@ -13,10 +13,5 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.format/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.format/META-INF/MANIFEST.MF index b92f22f79e..4059039553 100644 --- a/com.avaloq.tools.ddk.xtext.format/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.xtext.format/META-INF/MANIFEST.MF @@ -21,7 +21,6 @@ Require-Bundle: org.eclipse.xtext, org.eclipse.osgi, org.eclipse.core.runtime, org.eclipse.core.resources, - org.eclipse.xtend.lib, org.eclipse.xtext.common.types, org.eclipse.xtext.xbase;visibility:=reexport, org.eclipse.xtext.xbase.lib, diff --git a/com.avaloq.tools.ddk.xtext.format/build.properties b/com.avaloq.tools.ddk.xtext.format/build.properties index 93152c92f5..04a0b862e0 100644 --- a/com.avaloq.tools.ddk.xtext.format/build.properties +++ b/com.avaloq.tools.ddk.xtext.format/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = model/generated/,\ META-INF/,\ .,\ diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java new file mode 100644 index 0000000000..89602f2d1a --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.format; + +import com.avaloq.tools.ddk.xtext.format.conversion.FormatValueConverterService; +import com.avaloq.tools.ddk.xtext.format.generator.FormatOutputConfigurationProvider; +import com.avaloq.tools.ddk.xtext.format.naming.FormatQualifiedNameConverter; +import com.avaloq.tools.ddk.xtext.format.naming.FormatQualifiedNameProvider; +import com.avaloq.tools.ddk.xtext.format.resource.FormatResource; +import com.avaloq.tools.ddk.xtext.format.resource.FormatResourceDescriptionStrategy; +import com.avaloq.tools.ddk.xtext.format.scoping.FormatLinkingService; +import com.avaloq.tools.ddk.xtext.format.scoping.FormatScopeProvider; +import com.google.inject.Binder; +import com.google.inject.name.Names; +import org.eclipse.xtext.conversion.IValueConverterService; +import org.eclipse.xtext.generator.IOutputConfigurationProvider; +import org.eclipse.xtext.linking.ILinkingService; +import org.eclipse.xtext.linking.LinkingScopeProviderBinding; +import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.naming.IQualifiedNameProvider; +import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.containers.IAllContainersState; +import org.eclipse.xtext.resource.containers.ResourceSetBasedAllContainersStateProvider; +import org.eclipse.xtext.scoping.IScopeProvider; +import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; +import org.eclipse.xtext.xbase.scoping.XImportSectionNamespaceScopeProvider; +import org.eclipse.xtext.xtext.generator.model.project.IXtextProjectConfig; +import org.eclipse.xtext.xtext.generator.model.project.XtextProjectConfig; + +/** + * Use this class to register components to be used at runtime / without the Equinox extension registry. + */ +public class FormatRuntimeModule extends AbstractFormatRuntimeModule { + + @Override + public Class bindXtextResource() { + return FormatResource.class; + } + + /** + * Binds a class loader required by the resource manager. + * + * @return bound class loader instance + */ + @Override + public ClassLoader bindClassLoaderToInstance() { + return getClass().getClassLoader(); + } + + @Override + public Class bindIValueConverterService() { + return FormatValueConverterService.class; + } + + /** + * Binds custom qualified name converter. + * + * @return the implementation + */ + @Override + public Class bindIQualifiedNameConverter() { + return FormatQualifiedNameConverter.class; + } + + /** {@inheritDoc} */ + @Override + public Class bindIQualifiedNameProvider() { + return FormatQualifiedNameProvider.class; + } + + /** {@inheritDoc} */ + @Override + public Class bindIScopeProvider() { + return FormatScopeProvider.class; + } + + /** {@inheritDoc} */ + @Override + public void configureLinkingIScopeProvider(final Binder binder) { + binder.bind(IScopeProvider.class).annotatedWith(LinkingScopeProviderBinding.class).to(FormatScopeProvider.class); + } + + /** {@inheritDoc} */ + @Override + @SuppressWarnings("PMD.AvoidDollarSigns") + // CHECKSTYLE:OFF + public Class bindIAllContainersState$Provider() { + // CHECKSTYLE:ON + return ResourceSetBasedAllContainersStateProvider.class; + } + + /** {@inheritDoc} */ + @Override + public Class bindILinkingService() { + return FormatLinkingService.class; + } + + /** {@inheritDoc} */ + @Override + public Class bindIDefaultResourceDescriptionStrategy() { + return FormatResourceDescriptionStrategy.class; + } + + /** + * Binds a custom output configuration provider. + * + * @return the format output configuration provider + */ + public Class bindIOutputConfigurationProvider() { + return FormatOutputConfigurationProvider.class; + } + + /** {@inheritDoc} */ + @Override + public void configureIScopeProviderDelegate(final Binder binder) { + binder.bind(IScopeProvider.class).annotatedWith(Names.named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE)).to(XImportSectionNamespaceScopeProvider.class); + } + + @Override + public void configure(final Binder binder) { + super.configure(binder); + binder.bind(IXtextProjectConfig.class).to(XtextProjectConfig.class); + } +} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.xtend b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.xtend deleted file mode 100644 index 1d10e8b1de..0000000000 --- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatRuntimeModule.xtend +++ /dev/null @@ -1,115 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.format - -import com.avaloq.tools.ddk.xtext.format.conversion.FormatValueConverterService -import com.avaloq.tools.ddk.xtext.format.generator.FormatOutputConfigurationProvider -import com.avaloq.tools.ddk.xtext.format.naming.FormatQualifiedNameConverter -import com.avaloq.tools.ddk.xtext.format.naming.FormatQualifiedNameProvider -import com.avaloq.tools.ddk.xtext.format.resource.FormatResource -import com.avaloq.tools.ddk.xtext.format.resource.FormatResourceDescriptionStrategy -import com.avaloq.tools.ddk.xtext.format.scoping.FormatLinkingService -import com.avaloq.tools.ddk.xtext.format.scoping.FormatScopeProvider -import com.google.inject.Binder -import com.google.inject.name.Names -import org.eclipse.xtext.generator.IOutputConfigurationProvider -import org.eclipse.xtext.linking.LinkingScopeProviderBinding -import org.eclipse.xtext.resource.containers.ResourceSetBasedAllContainersStateProvider -import org.eclipse.xtext.scoping.IScopeProvider -import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider -import org.eclipse.xtext.xbase.scoping.XImportSectionNamespaceScopeProvider -import org.eclipse.xtext.xtext.generator.model.project.IXtextProjectConfig -import org.eclipse.xtext.xtext.generator.model.project.XtextProjectConfig - -/** - * Use this class to register components to be used at runtime / without the Equinox extension registry. - */ -class FormatRuntimeModule extends AbstractFormatRuntimeModule { - - override bindXtextResource() { - return FormatResource - } - - /** - * Binds a class loader required by the resource manager. - * - * @return bound class loader instance - */ - override bindClassLoaderToInstance() { - return getClass().getClassLoader() - } - - override bindIValueConverterService() { - return FormatValueConverterService - } - - /** - * Binds custom qualified name converter. - * - * @return the implementation - */ - override bindIQualifiedNameConverter() { - return FormatQualifiedNameConverter - } - - /** {@inheritDoc} */ - override bindIQualifiedNameProvider() { - return FormatQualifiedNameProvider - } - - /** {@inheritDoc} */ - override bindIScopeProvider() { - return FormatScopeProvider - } - - /** {@inheritDoc} */ - override configureLinkingIScopeProvider(Binder binder) { - binder.bind(IScopeProvider).annotatedWith(LinkingScopeProviderBinding).to(FormatScopeProvider) - } - - /** {@inheritDoc} */ - override - // CHECKSTYLE:OFF - bindIAllContainersState$Provider() { - // CHECKSTYLE:ON - return ResourceSetBasedAllContainersStateProvider - } - - /** {@inheritDoc} */ - override bindILinkingService() { - return FormatLinkingService - } - - /** {@inheritDoc} */ - override bindIDefaultResourceDescriptionStrategy() { - return FormatResourceDescriptionStrategy - } - - /** - * Binds a custom output configuration provider. - * - * @return the format output configuration provider - */ - def Class bindIOutputConfigurationProvider() { - return FormatOutputConfigurationProvider - } - - /** {@inheritDoc} */ - override configureIScopeProviderDelegate(Binder binder) { - binder.bind(IScopeProvider).annotatedWith(Names.named(AbstractDeclarativeScopeProvider.NAMED_DELEGATE)).to(XImportSectionNamespaceScopeProvider) - } - - override configure(Binder binder) { - super.configure(binder); - binder.bind(IXtextProjectConfig).to(XtextProjectConfig); - } -} - diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatStandaloneSetup.xtend b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatStandaloneSetup.java similarity index 54% rename from com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatStandaloneSetup.xtend rename to com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatStandaloneSetup.java index 352e2d59f3..f24c0d5ebe 100644 --- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatStandaloneSetup.xtend +++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/FormatStandaloneSetup.java @@ -1,15 +1,15 @@ /* * generated by Xtext 2.14.0 */ -package com.avaloq.tools.ddk.xtext.format +package com.avaloq.tools.ddk.xtext.format; /** * Initialization support for running Xtext languages without Equinox extension registry. */ -class FormatStandaloneSetup extends FormatStandaloneSetupGenerated { +public class FormatStandaloneSetup extends FormatStandaloneSetupGenerated { - def static void doSetup() { - new FormatStandaloneSetup().createInjectorAndDoEMFRegistration() + public static void doSetup() { + new FormatStandaloneSetup().createInjectorAndDoEMFRegistration(); } } diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/generator/FormatGenerator.java b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/generator/FormatGenerator.java new file mode 100644 index 0000000000..83b086cf3b --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/generator/FormatGenerator.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.format.generator; + +import static com.avaloq.tools.ddk.xtext.format.generator.FormatGeneratorUtil.getFormatterName; + +import com.avaloq.tools.ddk.xtext.format.FormatConstants; +import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.common.types.JvmConstructor; +import org.eclipse.xtext.common.types.JvmDeclaredType; +import org.eclipse.xtext.common.types.JvmField; +import org.eclipse.xtext.common.types.JvmMember; +import org.eclipse.xtext.common.types.JvmOperation; +import org.eclipse.xtext.generator.IFileSystemAccess; +import org.eclipse.xtext.util.Strings; +import org.eclipse.xtext.xbase.compiler.DocumentationAdapter; +import org.eclipse.xtext.xbase.compiler.ErrorSafeExtensions; +import org.eclipse.xtext.xbase.compiler.GeneratorConfig; +import org.eclipse.xtext.xbase.compiler.JvmModelGenerator; +import org.eclipse.xtext.xbase.compiler.TreeAppendableUtil; +import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.IteratorExtensions; + +/** + * Generates code from your model files on save. + * + * Currently this generator is not used at all. + * This is a side effect of the usage of the org.eclipse.xtext.generator.generator.GeneratorFragment.GeneratorFragment, + * which generates this file besides necessary contents for org.eclipse.xtext.builder.BuilderParticipant. + * + * see http://www.eclipse.org/Xtext/documentation.html#TutorialCodeGeneration + */ +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class FormatGenerator extends JvmModelGenerator { + + @Inject + private TreeAppendableUtil treeAppendableUtil; + + @Inject + private ErrorSafeExtensions errorSafeExtensions; + + public String getSingleCommentDocumentation(final EObject it, final ITreeAppendable appendable, final GeneratorConfig config) { + final DocumentationAdapter adapter = IterableExtensions.head(Iterables.filter(it.eAdapters(), DocumentationAdapter.class)); + String documentation = null; + if (adapter != null) { + documentation = adapter.getDocumentation(); + } + boolean isNullOrEmpty = documentation == null || documentation.isEmpty(); + if (!isNullOrEmpty) { + return adapter.getDocumentation(); + } + return null; + } + + @Override + protected ITreeAppendable _generateMember(final JvmField it, final ITreeAppendable appendable, final GeneratorConfig config) { + appendable.newLine(); + final ITreeAppendable tracedAppendable = appendable.trace(it); + generateAnnotations(it.getAnnotations(), tracedAppendable, true, config); + generateModifier(it, tracedAppendable, config); + errorSafeExtensions.serializeSafely(it.getType(), "Object", tracedAppendable); + tracedAppendable.append(" "); + treeAppendableUtil.traceSignificant(tracedAppendable, it).append(it.getSimpleName()); + generateInitialization(it, tracedAppendable, config); + tracedAppendable.append(";"); + String documentation = getSingleCommentDocumentation(it, appendable, config); + if (documentation != null && documentation.endsWith("\r\n")) { + documentation = documentation.substring(0, documentation.length() - 2); + } + if (documentation != null) { + tracedAppendable.append(" // "); + tracedAppendable.append(documentation); + } + return tracedAppendable; + } + + @Override + public void doGenerate(final Resource resource, final IFileSystemAccess fsa) { + super.doGenerate(resource, fsa); // Generate the abstract formatter from inferred Jvm models. + + for (final FormatConfiguration model : Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), FormatConfiguration.class)) { + String path = getFormatterName(model, "").replace(".", "/") + ".java"; + fsa.generateFile(path, FormatConstants.FORMATTER, generateSrc(model)); + } + } + + public CharSequence generateSrc(final FormatConfiguration model) { + String packageName = Strings.skipLastToken(getFormatterName(model, ""), "."); + String grammarName = Strings.lastToken(model.getTargetGrammar().getName(), "."); + String className = Strings.lastToken(getFormatterName(model, ""), "."); + String baseClassName = Strings.lastToken(getFormatterName(model, "Abstract"), "."); + return String.format(""" + package %s; + + /** + * The formatting configuration for %s. + */ + public class %s extends %s { + // TODO: Provide a correct implementation of getSLCommentRule() and getMLCommentRule() in this class + } + """, packageName, grammarName, className, baseClassName); + } + + @Override + public ITreeAppendable generateMember(final JvmMember it, final ITreeAppendable appendable, final GeneratorConfig config) { + if (it instanceof JvmConstructor) { + return _generateMember((JvmConstructor) it, appendable, config); + } else if (it instanceof JvmOperation) { + return _generateMember((JvmOperation) it, appendable, config); + } else if (it instanceof JvmField jvmField) { + return _generateMember(jvmField, appendable, config); + } else if (it instanceof JvmDeclaredType) { + return _generateMember((JvmDeclaredType) it, appendable, config); + } else if (it != null) { + return _generateMember(it, appendable, config); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + java.util.Arrays.asList(it, appendable, config).toString()); + } + } +} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/generator/FormatGenerator.xtend b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/generator/FormatGenerator.xtend deleted file mode 100644 index 0ecfb6910a..0000000000 --- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/generator/FormatGenerator.xtend +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.format.generator - -import com.google.inject.Inject -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.common.types.JvmField -import org.eclipse.xtext.generator.IFileSystemAccess -import org.eclipse.xtext.xbase.compiler.DocumentationAdapter -import org.eclipse.xtext.xbase.compiler.ErrorSafeExtensions -import org.eclipse.xtext.xbase.compiler.GeneratorConfig -import org.eclipse.xtext.xbase.compiler.JvmModelGenerator -import org.eclipse.xtext.xbase.compiler.TreeAppendableUtil -import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable - -import static com.avaloq.tools.ddk.xtext.format.generator.FormatGeneratorUtil.* -import static org.eclipse.xtext.xbase.lib.IteratorExtensions.* -import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration -import com.avaloq.tools.ddk.xtext.format.FormatConstants -import org.eclipse.xtext.util.Strings - -/** - * Generates code from your model files on save. - * - * Currently this generator is not used at all. - * This is a side effect of the usage of the org.eclipse.xtext.generator.generator.GeneratorFragment.GeneratorFragment, - * which generates this file besides necessary contents for org.eclipse.xtext.builder.BuilderParticipant. - * - * see http://www.eclipse.org/Xtext/documentation.html#TutorialCodeGeneration - */ -class FormatGenerator extends JvmModelGenerator { - - @Inject extension TreeAppendableUtil - @Inject extension ErrorSafeExtensions - - def getSingleCommentDocumentation(EObject it, ITreeAppendable appendable, GeneratorConfig config) { - val adapter = it.eAdapters.filter(DocumentationAdapter).head - if (!adapter?.documentation.nullOrEmpty) { - return adapter.documentation - } - return null - } - - override dispatch generateMember(JvmField it, ITreeAppendable appendable, GeneratorConfig config) { - appendable.newLine - val tracedAppendable = appendable.trace(it) - annotations.generateAnnotations(tracedAppendable, true, config) - generateModifier(tracedAppendable, config) - type.serializeSafely("Object", tracedAppendable) - tracedAppendable.append(" ") - tracedAppendable.traceSignificant(it).append(simpleName) - generateInitialization(tracedAppendable, config) - tracedAppendable.append(";") - var documentation = getSingleCommentDocumentation(appendable, config) - if (documentation !== null && documentation.endsWith("\r\n")) { - documentation = documentation.substring(0, documentation.length - 2) - } - if (documentation !== null) { - tracedAppendable.append(" // ") - tracedAppendable.append(documentation) - } - } - - override void doGenerate(Resource resource, IFileSystemAccess fsa) { - super.doGenerate(resource, fsa); // Generate the abstract formatter from inferred Jvm models. - - for (model : toIterable(resource.allContents).filter(typeof(FormatConfiguration))) { - fsa.generateFile(getFormatterName(model, "").replace('.', '/') + ".java", FormatConstants.FORMATTER, - model.generateSrc) - } - } - - def generateSrc(FormatConfiguration model) ''' - package «Strings.skipLastToken(getFormatterName(model, ""), ".")»; - - /** - * The formatting configuration for «Strings.lastToken(model.targetGrammar.name,".")». - */ - public class «Strings.lastToken(getFormatterName(model, ""),".")» extends «Strings.lastToken(getFormatterName(model, "Abstract"),".")» { - // TODO: Provide a correct implementation of getSLCommentRule() and getMLCommentRule() in this class - } - ''' - -} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.java b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.java new file mode 100644 index 0000000000..bf218c46d2 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.java @@ -0,0 +1,1286 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.format.jvmmodel; + +import static com.avaloq.tools.ddk.xtext.util.EObjectUtil.getFileLocation; +import static org.eclipse.xtext.GrammarUtil.getGrammar; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + +import org.eclipse.emf.common.util.BasicEList; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractMetamodelDeclaration; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.EnumRule; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.TypeRef; +import org.eclipse.xtext.common.types.JvmAnnotationReference; +import org.eclipse.xtext.common.types.JvmAnnotationType; +import org.eclipse.xtext.common.types.JvmDeclaredType; +import org.eclipse.xtext.common.types.JvmFormalParameter; +import org.eclipse.xtext.common.types.JvmGenericType; +import org.eclipse.xtext.common.types.JvmMember; +import org.eclipse.xtext.common.types.JvmOperation; +import org.eclipse.xtext.common.types.JvmType; +import org.eclipse.xtext.common.types.JvmTypeReference; +import org.eclipse.xtext.common.types.JvmVisibility; +import org.eclipse.xtext.common.types.TypesFactory; +import org.eclipse.xtext.common.types.util.TypeReferences; +import org.eclipse.xtext.util.Strings; +import org.eclipse.xtext.xbase.XExpression; +import org.eclipse.xtext.xbase.compiler.XbaseCompiler; +import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable; +import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer; +import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor; +import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder; +import org.eclipse.xtext.xbase.lib.CollectionLiterals; +import org.eclipse.xtext.xbase.lib.Conversions; +import org.eclipse.xtext.xbase.lib.ExclusiveRange; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ListExtensions; +import org.eclipse.xtext.xbase.lib.Pair; +import org.eclipse.xtext.xbase.lib.StringExtensions; +import org.eclipse.xtext.xtext.RuleNames; +import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions; + +import com.avaloq.tools.ddk.xtext.format.format.ColumnLocator; +import com.avaloq.tools.ddk.xtext.format.format.Constant; +import com.avaloq.tools.ddk.xtext.format.format.ContextFreeDirective; +import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration; +import com.avaloq.tools.ddk.xtext.format.format.GrammarElementLookup; +import com.avaloq.tools.ddk.xtext.format.format.GrammarElementReference; +import com.avaloq.tools.ddk.xtext.format.format.GrammarRule; +import com.avaloq.tools.ddk.xtext.format.format.GrammarRuleDirective; +import com.avaloq.tools.ddk.xtext.format.format.GroupBlock; +import com.avaloq.tools.ddk.xtext.format.format.IndentLocator; +import com.avaloq.tools.ddk.xtext.format.format.IntValue; +import com.avaloq.tools.ddk.xtext.format.format.KeywordPair; +import com.avaloq.tools.ddk.xtext.format.format.LinewrapLocator; +import com.avaloq.tools.ddk.xtext.format.format.Locator; +import com.avaloq.tools.ddk.xtext.format.format.Matcher; +import com.avaloq.tools.ddk.xtext.format.format.MatcherList; +import com.avaloq.tools.ddk.xtext.format.format.MatcherType; +import com.avaloq.tools.ddk.xtext.format.format.NoFormatLocator; +import com.avaloq.tools.ddk.xtext.format.format.OffsetLocator; +import com.avaloq.tools.ddk.xtext.format.format.RightPaddingLocator; +import com.avaloq.tools.ddk.xtext.format.format.Rule; +import com.avaloq.tools.ddk.xtext.format.format.SpaceLocator; +import com.avaloq.tools.ddk.xtext.format.format.SpecificDirective; +import com.avaloq.tools.ddk.xtext.format.format.StringValue; +import com.avaloq.tools.ddk.xtext.format.format.WildcardRule; +import com.avaloq.tools.ddk.xtext.format.format.WildcardRuleDirective; +import com.avaloq.tools.ddk.xtext.format.generator.FormatGeneratorUtil; +import com.avaloq.tools.ddk.xtext.formatting.AbstractExtendedFormatter; +import com.avaloq.tools.ddk.xtext.formatting.ExtendedFormattingConfig; +import com.avaloq.tools.ddk.xtext.formatting.locators.LocatorActivator; +import com.avaloq.tools.ddk.xtext.formatting.locators.LocatorParameterCalculator; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + + +/** + *

+ * Infers a JVM model from the source model. + *

+ *

+ * The JVM model should contain all elements that would appear in the Java code + * which is generated from the source model. Other models link against the JVM model rather than the source model. + *

+ */ +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class FormatJvmModelInferrer extends AbstractModelInferrer { + + @Inject + private JvmTypesBuilder jvmTypesBuilder; + + @Inject + private TypeReferences typeReferences; + + @Inject + private GrammarAccessExtensions grammarAccess; + + @Inject + private TypesFactory typesFactory; + + @Inject + private XbaseCompiler xbaseCompiler; + + private static final String BASE_FORMATTER_CLASS_NAME = AbstractExtendedFormatter.class.getName(); + + private static final String BASE_FORMAT_CONFIG = ExtendedFormattingConfig.class.getName(); + + private static final String METHOD_ACTIVATE = "activate"; + + private static final String METHOD_CALCULATE = "calculateParameter"; + + private static final String PARAMETER_CONFIG = "config"; + + private static final String PARAMETER_ELEMENTS = "elements"; + + private static final String PARAMETER_RULE = "rule"; + + private static final String PARAMETER_GRAMMAR_ACCESS = "grammarAccess"; + + private static final String PARAMETER_CONTEXT = "context"; + + private static final String PARAMETER_COLUMN = "currentColumn"; + + private static final int DEFAULT_BUFFER_CAPACITY = 256; + + private static final String DOT = "."; + + private static final String IMPL_SUFFIX = "Impl"; + + private static final String THE_FORMAT_CONFIGURATION = "the format configuration"; + + /** + * The dispatch method {@code infer} is called for each instance of the + * given element's type that is contained in a resource. + * + * @param format + * the model to create one or more {@link JvmDeclaredType declared types} from. + * @param acceptor + * each created {@link JvmDeclaredType type} without a container should be passed to the acceptor in order + * get attached to the current resource. The acceptor's {@link IJvmDeclaredTypeAcceptor#accept(JvmDeclaredType, + * org.eclipse.xtext.xbase.lib.Procedures.Procedure1)} method takes the constructed empty type for the + * pre-indexing phase. This one is further initialized in the indexing phase using the passed closure. + * @param isPreIndexingPhase + * whether the method is called in a pre-indexing phase, i.e. when the global index is not yet fully updated. You must not + * rely on linking using the index if isPreIndexingPhase is {@code true}. + */ + protected void _infer(final FormatConfiguration format, final IJvmDeclaredTypeAcceptor acceptor, final boolean isPreIndexingPhase) { + if (isPreIndexingPhase) { + return; + } + final Grammar context = format.getTargetGrammar(); + if (EcoreUtil.getAdapter(context.eAdapters(), RuleNames.class) == null) { + final List allRules = GrammarUtil.allRules(context); + for (final AbstractRule rule : allRules) { + final Object adpt = EcoreUtil.getAdapter(rule.eAdapters(), RuleNames.class); + if (adpt != null) { + rule.eAdapters().remove(adpt); + } + } + RuleNames.getRuleNames(context, true); + } + acceptor. accept(jvmTypesBuilder.toClass(format, Strings.lastToken(FormatGeneratorUtil.getFormatterName(format, "Abstract"), DOT)), (final JvmGenericType it) -> { + inferClass(format, it); + inferConstants(format, it); + inferGetGrammarAccess(format, it); + inferConfigureAcsFormatting(format, it); + inferInit(format, it); + inferRules(format, it); + inferLocatorActivators(format, it); + }); + } + + public void inferClass(final FormatConfiguration format, final JvmGenericType it) { + Grammar targetGrammar = format.getTargetGrammar(); + String targetGrammarNameRaw = targetGrammar != null ? targetGrammar.getName() : null; + String targetGrammarName = Strings.emptyIfNull(targetGrammarNameRaw); + jvmTypesBuilder.setDocumentation(it, "The abstract formatting configuration for " + Strings.skipLastToken(targetGrammarName, DOT) + DOT + + Strings.lastToken(targetGrammarName, DOT) + " as declared in " + Strings.lastToken(targetGrammarName, DOT) + ".format."); + if (format.getFormatterBaseClass() != null) { + jvmTypesBuilder. operator_add(it.getSuperTypes(), _typeReferenceBuilder.typeRef(format.getFormatterBaseClass().getPackageName() + DOT + + format.getFormatterBaseClass().getSimpleName())); + } else { + jvmTypesBuilder. operator_add(it.getSuperTypes(), _typeReferenceBuilder.typeRef(BASE_FORMATTER_CLASS_NAME)); + } + it.setPackageName(Strings.skipLastToken(FormatGeneratorUtil.getFormatterName(format, ""), DOT)); + it.setAbstract(true); + } + + public boolean inferConstants(final FormatConfiguration format, final JvmGenericType it) { + return !FormatGeneratorUtil.getAllConstants(format).isEmpty() + && jvmTypesBuilder. operator_add(it.getMembers(), ListExtensions. map(FormatGeneratorUtil.getAllConstants(format), (final Constant c) -> createConstant(format, c))); + } + + public String getFullyQualifiedName(final Grammar g) { + return GrammarUtil.getNamespace(g) + ".services." + GrammarUtil.getSimpleName(g) + "GrammarAccess"; + } + + public boolean inferGetGrammarAccess(final FormatConfiguration format, final JvmGenericType it) { + JvmOperation method = jvmTypesBuilder.toMethod(format, "getGrammarAccess", typeReferences.getTypeForName(getFullyQualifiedName(format.getTargetGrammar()), format.getTargetGrammar()), (final JvmOperation op) -> { + op.setVisibility(JvmVisibility.PROTECTED); + final JvmAnnotationReference overrideAnnotation = createOverrideAnnotation(format); + if (overrideAnnotation != null) { + jvmTypesBuilder. operator_add(op.getAnnotations(), overrideAnnotation); + } + jvmTypesBuilder.setBody(op, (final ITreeAppendable body) -> { + body.append("return (" + GrammarUtil.getSimpleName(format.getTargetGrammar()) + "GrammarAccess) super.getGrammarAccess();"); + }); + }); + return jvmTypesBuilder. operator_add(it.getMembers(), method); + } + + public boolean inferConfigureAcsFormatting(final FormatConfiguration format, final JvmGenericType it) { + JvmOperation method = jvmTypesBuilder.toMethod(format, "configureAcsFormatting", _typeReferenceBuilder.typeRef("void"), (final JvmOperation op) -> { + op.setVisibility(JvmVisibility.PROTECTED); + final JvmAnnotationReference overrideAnnotation = createOverrideAnnotation(format); + if (overrideAnnotation != null) { + jvmTypesBuilder. operator_add(op.getAnnotations(), overrideAnnotation); + } + jvmTypesBuilder. operator_add(op.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_CONFIG, _typeReferenceBuilder.typeRef(BASE_FORMAT_CONFIG))); + jvmTypesBuilder.setBody(op, (final ITreeAppendable body) -> { + body.append("init(config, getGrammarAccess());"); + }); + }); + return jvmTypesBuilder. operator_add(it.getMembers(), method); + } + + public boolean inferInit(final FormatConfiguration format, final JvmGenericType it) { + JvmOperation method = jvmTypesBuilder.toMethod(format, "init", _typeReferenceBuilder.typeRef("void"), (final JvmOperation op) -> initializeInitMethod(format, op)); + return jvmTypesBuilder. operator_add(it.getMembers(), method); + } + + private void initializeInitMethod(final FormatConfiguration format, final JvmOperation op) { + Pair mappedTo = Pair. of(PARAMETER_CONFIG, THE_FORMAT_CONFIGURATION); + Pair mapped = Pair. of(PARAMETER_GRAMMAR_ACCESS, "the grammar access for the grammar"); + jvmTypesBuilder.setDocumentation(op, generateJavaDoc("Calls all configXyz methods declared in this class.", CollectionLiterals. newLinkedHashMap(mappedTo, mapped))); + op.setVisibility(JvmVisibility.PROTECTED); + jvmTypesBuilder. operator_add(op.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_CONFIG, _typeReferenceBuilder.typeRef(BASE_FORMAT_CONFIG))); + jvmTypesBuilder. operator_add(op.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_GRAMMAR_ACCESS, _typeReferenceBuilder.typeRef(getFullyQualifiedName(format.getTargetGrammar())))); + jvmTypesBuilder.setBody(op, (final ITreeAppendable body) -> { + final List rules = listConfigRules(format); + int length = ((Object[]) Conversions.unwrapArray(rules, Object.class)).length; + ExclusiveRange range = new ExclusiveRange(0, length, true); + for (final Integer i : range) { + if (i != 0) { + body.newLine(); + } + body.append(rules.get(i)); + } + }); + } + + public boolean inferRules(final FormatConfiguration format, final JvmGenericType it) { + jvmTypesBuilder. operator_add(it.getMembers(), Iterables. concat(ListExtensions.> map(FormatGeneratorUtil.getParserRules(format), (final GrammarRule c) -> createRule(format, c)))); + jvmTypesBuilder. operator_add(it.getMembers(), Iterables. concat(ListExtensions.> map(FormatGeneratorUtil.getEnumRules(format), (final GrammarRule c) -> createRule(format, c)))); + jvmTypesBuilder. operator_add(it.getMembers(), Iterables. concat(ListExtensions.> map(FormatGeneratorUtil.getTerminalRules(format), (final GrammarRule c) -> createRule(format, c)))); + boolean result = false; + if (FormatGeneratorUtil.getWildcardRule(format) != null) { + JvmOperation method = jvmTypesBuilder.toMethod(format, "configFindElements", _typeReferenceBuilder.typeRef("void"), (final JvmOperation op) -> { + Pair mappedTo = Pair. of(PARAMETER_CONFIG, THE_FORMAT_CONFIGURATION); + Pair mapped = Pair. of(PARAMETER_ELEMENTS, "the grammar access for the grammar"); + jvmTypesBuilder.setDocumentation(op, generateJavaDoc("Configuration for IGrammarAccess.findXyz() methods.", CollectionLiterals. newLinkedHashMap(mappedTo, mapped))); + op.setVisibility(JvmVisibility.PROTECTED); + jvmTypesBuilder. operator_add(op.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_CONFIG, _typeReferenceBuilder.typeRef(BASE_FORMAT_CONFIG))); + jvmTypesBuilder. operator_add(op.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_ELEMENTS, _typeReferenceBuilder.typeRef(getFullyQualifiedName(getGrammar(format.getTargetGrammar()))))); + jvmTypesBuilder.setBody(op, (final ITreeAppendable body) -> { + final List directives = ListExtensions. map(FormatGeneratorUtil.getWildcardRule(format).getDirectives(), (final WildcardRuleDirective d) -> directive(d, getRuleName(FormatGeneratorUtil.getWildcardRule(format))).toString()); + body.append(fixLastLine(IterableExtensions.join(directives))); + }); + }); + result = jvmTypesBuilder. operator_add(it.getMembers(), method); + } + return result; + } + + public void inferLocatorActivators(final FormatConfiguration format, final JvmGenericType it) { + List rules = new LinkedList<>(); + Iterables. addAll(rules, Iterables. concat(Iterables. concat(FormatGeneratorUtil.getParserRules(format), FormatGeneratorUtil.getTerminalRules(format)), FormatGeneratorUtil.getEnumRules(format))); + rules.add(FormatGeneratorUtil.getWildcardRule(format)); + for (final Rule rule : rules) { + List directives = new LinkedList<>(); + if (rule instanceof GrammarRule grammarRule) { + Iterables. addAll(directives, grammarRule.getDirectives()); + } else if (rule instanceof WildcardRule wildcardRule) { + Iterables. addAll(directives, wildcardRule.getDirectives()); + } + for (final EObject directive : IterableExtensions. filterNull(directives)) { + for (final Matcher matcher : collectMatchers(directive)) { + if ((matcher.getLocator() instanceof ColumnLocator) && (((ColumnLocator) matcher.getLocator()).getParameter() != null)) { + jvmTypesBuilder. operator_add(it.getMembers(), createParameterCalculatorInnerClass(format, rule, directive, matcher, ((ColumnLocator) matcher.getLocator()).getParameter(), it)); + } + if ((matcher.getLocator() instanceof IndentLocator) && (((IndentLocator) matcher.getLocator()).getParameter() != null)) { + jvmTypesBuilder. operator_add(it.getMembers(), createParameterCalculatorInnerClass(format, rule, directive, matcher, ((IndentLocator) matcher.getLocator()).getParameter(), it)); + } + if (matcher.getCondition() != null) { + jvmTypesBuilder. operator_add(it.getMembers(), createLocatorActivatorInnerClass(format, rule, directive, matcher, it)); + } + } + } + } + } + + public JvmGenericType createLocatorActivatorInnerClass(final FormatConfiguration format, final Rule rule, final EObject directive, final Matcher matcher, final JvmGenericType type) { + return jvmTypesBuilder.toClass(format, getLocatorActivatorName(rule, directive, matcher), (final JvmGenericType it) -> { + it.setStatic(true); + it.setFinal(true); + it.setVisibility(JvmVisibility.PROTECTED); + jvmTypesBuilder. operator_add(it.getSuperTypes(), getLocatorActivatorSuperType(format, rule)); + JvmOperation method = jvmTypesBuilder.toMethod(matcher, METHOD_ACTIVATE, getLocatorActivatorReturnType(format), (final JvmOperation op) -> { + jvmTypesBuilder. operator_add(op.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_CONTEXT, typeReferences.getTypeForName(getGrammarElementNameFromSelf(rule), format))); + jvmTypesBuilder. operator_add(op.getParameters(), createCurrenctColumnParameter()); + if (!Objects.equals(format.eResource(), matcher.eResource())) { + jvmTypesBuilder.setBody(op, (final ITreeAppendable body) -> { + xbaseCompiler.compile(matcher.getCondition(), body, getLocatorActivatorReturnType(format), null); + }); + } else { + jvmTypesBuilder.setBody(op, matcher.getCondition()); + } + }); + jvmTypesBuilder. operator_add(it.getMembers(), method); + }); + } + + public JvmFormalParameter createCurrenctColumnParameter() { + JvmFormalParameter result = typesFactory.createJvmFormalParameter(); + result.setName(PARAMETER_COLUMN); + result.setParameterType(_typeReferenceBuilder.typeRef(Integer.class)); + return result; + } + + public JvmGenericType createParameterCalculatorInnerClass(final FormatConfiguration format, final Rule rule, final EObject directive, final Matcher matcher, final XExpression parameterCalculation, final JvmGenericType type) { + return jvmTypesBuilder.toClass(format, getParameterCalculatorName(rule, directive, matcher), (final JvmGenericType it) -> { + it.setStatic(true); + it.setFinal(true); + it.setVisibility(JvmVisibility.PROTECTED); + jvmTypesBuilder. operator_add(it.getSuperTypes(), getParameterCalculatorSuperType(format, rule)); + JvmOperation method = jvmTypesBuilder.toMethod(matcher, METHOD_CALCULATE, getParameterCalculatorReturnType(format), (final JvmOperation op) -> { + jvmTypesBuilder. operator_add(op.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_CONTEXT, typeReferences.getTypeForName(getGrammarElementNameFromSelf(rule), format))); + jvmTypesBuilder. operator_add(op.getParameters(), createCurrenctColumnParameter()); + if (!Objects.equals(format.eResource(), matcher.eResource())) { + jvmTypesBuilder.setBody(op, (final ITreeAppendable body) -> { + xbaseCompiler.compile(parameterCalculation, body, getParameterCalculatorReturnType(format), null); + }); + } else { + jvmTypesBuilder.setBody(op, parameterCalculation); + } + }); + jvmTypesBuilder. operator_add(it.getMembers(), method); + }); + } + + public List listConfigRules(final FormatConfiguration format) { + final List configRules = CollectionLiterals. newArrayList(); + if (FormatGeneratorUtil.getWildcardRule(format) != null) { + configRules.add("configFindElements(config, grammarAccess);"); + } + for (final GrammarRule rule : FormatGeneratorUtil.getParserRules(format)) { + configRules.add("config" + rule.getTargetRule().getName() + "(config, grammarAccess." + grammarAccess.gaElementsAccessor(rule.getTargetRule()) + ");"); + } + for (final GrammarRule rule : FormatGeneratorUtil.getEnumRules(format)) { + configRules.add("config" + rule.getTargetRule().getName() + "(config, grammarAccess." + grammarAccess.gaRuleAccessor(rule.getTargetRule()) + ");"); + } + for (final GrammarRule rule : FormatGeneratorUtil.getTerminalRules(format)) { + configRules.add("config" + rule.getTargetRule().getName() + "(config, grammarAccess." + grammarAccess.gaRuleAccessor(rule.getTargetRule()) + ");"); + } + return configRules; + } + + public String generateJavaDoc(final String description, final Map parameters) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append(description).append('\n'); + sb.append('\n'); + for (final Map.Entry parameter : parameters.entrySet()) { + sb.append("@param ").append(parameter.getKey()).append('\n'); + sb.append(" - ").append(parameter.getValue()).append('\n'); + } + return sb.toString(); + } + + public JvmAnnotationReference createOverrideAnnotation(final FormatConfiguration format) { + final JvmTypeReference annotationTypeRef = _typeReferenceBuilder.typeRef(Override.class); + JvmAnnotationReference overrideAnnotation = null; + if (annotationTypeRef != null) { + final JvmType annotationType = annotationTypeRef.getType(); + overrideAnnotation = typesFactory.createJvmAnnotationReference(); + overrideAnnotation.setAnnotation((JvmAnnotationType) annotationType); + } + return overrideAnnotation; + } + + public JvmMember createConstant(final FormatConfiguration configuration, final Constant constant) { + if (constant.getStringValue() != null) { + return jvmTypesBuilder.toField(constant, constant.getName(), typeReferences.getTypeForName("String", constant), (it) -> { + jvmTypesBuilder.setDocumentation(it, locatorString(constant)); + it.setStatic(true); + it.setFinal(true); + it.setVisibility(JvmVisibility.PROTECTED); + jvmTypesBuilder.setInitializer(it, (final ITreeAppendable op) -> { + op.append("\"" + constant.getStringValue() + "\""); + }); + }); + } else if (constant.getIntValue() != null) { + return jvmTypesBuilder.toField(constant, constant.getName(), typeReferences.getTypeForName("int", constant), (it) -> { + jvmTypesBuilder.setDocumentation(it, locatorString(constant)); + it.setStatic(true); + it.setFinal(true); + it.setVisibility(JvmVisibility.PROTECTED); + jvmTypesBuilder.setInitializer(it, (final ITreeAppendable op) -> { + op.append(constant.getIntValue().toString()); + }); + }); + } + return null; + } + + public List collectMatchers(final EObject directive) { + List matchers = new LinkedList<>(); + if (directive instanceof GroupBlock groupBlock) { + if (groupBlock.getMatcherList() != null) { + Iterables. addAll(matchers, groupBlock.getMatcherList().getMatchers()); + } + } else if (directive instanceof SpecificDirective specificDirective) { + if (specificDirective.getMatcherList() != null) { + Iterables. addAll(matchers, specificDirective.getMatcherList().getMatchers()); + } + } else if (directive instanceof ContextFreeDirective contextFreeDirective) { + if (contextFreeDirective.getMatcherList() != null) { + Iterables. addAll(matchers, contextFreeDirective.getMatcherList().getMatchers()); + } + } else if (directive instanceof KeywordPair keywordPair) { + if (keywordPair.getLeftMatchers() != null) { + Iterables. addAll(matchers, keywordPair.getLeftMatchers()); + } + if (keywordPair.getRightMatchers() != null) { + Iterables. addAll(matchers, keywordPair.getRightMatchers()); + } + } + return matchers; + } + + public JvmTypeReference getLocatorActivatorReturnType(final FormatConfiguration formatConfiguration) { + return _typeReferenceBuilder.typeRef(boolean.class); + } + + public JvmTypeReference getParameterCalculatorReturnType(final FormatConfiguration formatConfiguration) { + return _typeReferenceBuilder.typeRef(int.class); + } + + // getLocatorActivatorSuperType dispatch + protected JvmTypeReference _getLocatorActivatorSuperType(final FormatConfiguration formatConfiguration, final GrammarRule rule) { + return _typeReferenceBuilder.typeRef(LocatorActivator.class, typeReferences.getTypeForName(getGrammarElementNameFromSelf(rule), formatConfiguration)); + } + + protected JvmTypeReference _getLocatorActivatorSuperType(final FormatConfiguration formatConfiguration, final WildcardRule rule) { + return _typeReferenceBuilder.typeRef(LocatorActivator.class, typeReferences.getTypeForName(getGrammarElementNameFromSelf(rule), formatConfiguration)); + } + + public JvmTypeReference getLocatorActivatorSuperType(final FormatConfiguration formatConfiguration, final Rule rule) { + if (rule instanceof GrammarRule grammarRule) { + return _getLocatorActivatorSuperType(formatConfiguration, grammarRule); + } else if (rule instanceof WildcardRule wildcardRule) { + return _getLocatorActivatorSuperType(formatConfiguration, wildcardRule); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(formatConfiguration, rule).toString()); + } + } + + // getParameterCalculatorSuperType dispatch + protected JvmTypeReference _getParameterCalculatorSuperType(final FormatConfiguration formatConfiguration, final GrammarRule rule) { + return _typeReferenceBuilder.typeRef(LocatorParameterCalculator.class, typeReferences.getTypeForName(getGrammarElementNameFromSelf(rule), formatConfiguration)); + } + + protected JvmTypeReference _getParameterCalculatorSuperType(final FormatConfiguration formatConfiguration, final WildcardRule rule) { + return _typeReferenceBuilder.typeRef(LocatorParameterCalculator.class, typeReferences.getTypeForName(getGrammarElementNameFromSelf(rule), formatConfiguration)); + } + + public JvmTypeReference getParameterCalculatorSuperType(final FormatConfiguration formatConfiguration, final Rule rule) { + if (rule instanceof GrammarRule grammarRule) { + return _getParameterCalculatorSuperType(formatConfiguration, grammarRule); + } else if (rule instanceof WildcardRule wildcardRule) { + return _getParameterCalculatorSuperType(formatConfiguration, wildcardRule); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(formatConfiguration, rule).toString()); + } + } + + // getGrammarElementNameFromSelf dispatch + protected String _getGrammarElementNameFromSelf(final GrammarRule rule) { + final String originalRuleName = getRuleName(rule); + String actualRuleName = originalRuleName; + if (rule.getTargetRule() == null || rule.getTargetRule().getType() == null || rule.getTargetRule().getType().getClassifier() == null) { + return actualRuleName; + } else { + AbstractRule targetRule = rule.getTargetRule(); + TypeRef type = targetRule != null ? targetRule.getType() : null; + EClassifier classifier = type != null ? type.getClassifier() : null; + String name = classifier != null ? classifier.getName() : null; + if (!Objects.equals(actualRuleName, name)) { + actualRuleName = rule.getTargetRule().getType().getClassifier().getName(); + } + } + AbstractRule targetRule = rule.getTargetRule(); + TypeRef type = targetRule != null ? targetRule.getType() : null; + AbstractMetamodelDeclaration metamodel = type != null ? type.getMetamodel() : null; + if (metamodel == null) { + return actualRuleName; + } else { + if (!Objects.equals(actualRuleName, originalRuleName)) { + boolean anyMatch = metamodel.getEPackage().getEClassifiers().stream().anyMatch((final EClassifier it) -> (it instanceof EClass) + && it.getName().equalsIgnoreCase(originalRuleName)); + if (anyMatch) { + actualRuleName = originalRuleName; + } + } + URI uri = EcoreUtil2.getURI(metamodel.getEPackage()); + String segment = uri != null ? uri.segment(1) : null; + final String metamodelPackage = segment; + if (metamodelPackage == null) { + return actualRuleName; + } + EPackage ePackage = metamodel.getEPackage(); + String ePackageName = ePackage != null ? ePackage.getName() : null; + return metamodelPackage.substring(0, metamodelPackage.lastIndexOf(".core")) + DOT + ePackageName + DOT + actualRuleName; + } + } + + protected String _getGrammarElementNameFromSelf(final WildcardRule rule) { + return EObject.class.getName(); + } + + public String getGrammarElementNameFromSelf(final Rule rule) { + if (rule instanceof GrammarRule grammarRule) { + return _getGrammarElementNameFromSelf(grammarRule); + } else if (rule instanceof WildcardRule wildcardRule) { + return _getGrammarElementNameFromSelf(wildcardRule); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(rule).toString()); + } + } + + public int getMatcherIndex(final Matcher matcher) { + final MatcherList matcherList = EcoreUtil2. getContainerOfType(matcher, MatcherList.class); + return matcherList.getMatchers().indexOf(matcher); + } + + public String getLocatorActivatorName(final EObject rule, final EObject directive, final Matcher matcher) { + return ("ActivatorFor" + getRuleName(rule) + getMatcherName(matcher, directive)).replace(IMPL_SUFFIX, ""); + } + + public String getLocatorActivatorName(final String partialName, final Matcher matcher) { + return ("ActivatorFor" + partialName + getMatcherIndex(matcher) + getLocatorName(matcher.getLocator()) + + StringExtensions.toFirstUpper(matcher.getType().name().toLowerCase())).replace(IMPL_SUFFIX, ""); + } + + public String getParameterCalculatorName(final EObject rule, final EObject directive, final Matcher matcher) { + return ("ParameterCalculatorFor" + getRuleName(rule) + getMatcherName(matcher, directive)).replace(IMPL_SUFFIX, ""); + } + + public String getParameterCalculatorName(final String partialName, final Matcher matcher) { + return ("ParameterCalculatorFor" + partialName + getMatcherIndex(matcher) + getLocatorName(matcher.getLocator()) + + StringExtensions.toFirstUpper(matcher.getType().name().toLowerCase())).replace(IMPL_SUFFIX, ""); + } + + // getRuleName dispatch + protected String _getRuleName(final GrammarRule rule) { + AbstractRule targetRule = rule.getTargetRule(); + return targetRule != null ? targetRule.getName() : null; + } + + protected String _getRuleName(final WildcardRule rule) { + return "Wildcard"; + } + + protected String _getRuleName(final EObject rule) { + return EObject.class.getSimpleName(); + } + + public String getRuleName(final EObject rule) { + if (rule instanceof GrammarRule grammarRule) { + return _getRuleName(grammarRule); + } else if (rule instanceof WildcardRule wildcardRule) { + return _getRuleName(wildcardRule); + } else if (rule != null) { + return _getRuleName(rule); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(rule).toString()); + } + } + + public String getMatcherName(final Matcher matcher, final EObject directive) { + return getDirectiveName(directive) + getMatcherIndex(matcher) + getLocatorName(matcher.getLocator()) + + StringExtensions.toFirstUpper(matcher.getType().name().toLowerCase()); + } + + public String getLocatorName(final EObject locator) { + Class clazz = locator != null ? locator.getClass() : null; + String simpleName = clazz != null ? clazz.getSimpleName() : null; + return simpleName != null ? simpleName : ""; + } + + public String convertNonAlphaNumeric(final String str) { + final Pattern pattern = Pattern.compile("[\\W]"); + final java.util.regex.Matcher matcher = pattern.matcher(str); + final StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(sb, Integer.toHexString(matcher.group().hashCode())); + } + matcher.appendTail(sb); + return sb.toString(); + } + + // getDirectiveName dispatch + @SuppressWarnings("PMD.CollectionTypeMismatch") + protected String _getDirectiveName(final GroupBlock directive) { + final GrammarRule grammarRule = EcoreUtil2. getContainerOfType(directive, GrammarRule.class); + final List> directives = CollectionLiterals.> newArrayList(Iterables. filter(grammarRule.getDirectives(), GroupBlock.class)); + return "Group" + (directives.indexOf(directive) + 1); + } + + protected String _getDirectiveName(final SpecificDirective directive) { + StringBuilder directiveName = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + for (final GrammarElementReference grammarElementReference : directive.getGrammarElements()) { + if (grammarElementReference.getAssignment() != null) { + directiveName.append(grammarAccess.gaElementAccessMethodName(grammarElementReference.getAssignment()).replaceFirst("get", "").replaceFirst("(?s)(.*)Assignment", "$1")); + } + if (grammarElementReference.getRuleCall() != null) { + directiveName.append(StringExtensions.toFirstUpper(grammarElementReference.getRuleCall().getRule().getName())); + } + if (grammarElementReference.getRule() != null) { + directiveName.append(StringExtensions.toFirstUpper(grammarElementReference.getRule().getName())); + } + if (grammarElementReference.getKeyword() != null) { + directiveName.append(StringExtensions.toFirstUpper(convertNonAlphaNumeric(grammarElementReference.getKeyword().getValue()))); + } + if (grammarElementReference.getSelf() != null) { + directiveName.append("Self"); + } + } + return directiveName.toString(); + } + + protected String _getDirectiveName(final ContextFreeDirective directive) { + StringBuilder directiveName = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + for (final GrammarElementLookup grammarElementLookup : directive.getGrammarElements()) { + if (grammarElementLookup.getRule() != null) { + directiveName.append(StringExtensions.toFirstUpper(grammarElementLookup.getRule().getName())); + } + if (grammarElementLookup.getKeyword() != null) { + directiveName.append(StringExtensions.toFirstUpper(convertNonAlphaNumeric(grammarElementLookup.getKeyword()))); + } + } + return directiveName.toString(); + } + + protected String _getDirectiveName(final KeywordPair directive) { + return convertNonAlphaNumeric(directive.getLeft()) + convertNonAlphaNumeric(directive.getRight()); + } + + protected String _getDirectiveName(final EObject directive) { + return String.valueOf(directive.hashCode()); + } + + public String getDirectiveName(final EObject directive) { + if (directive instanceof ContextFreeDirective contextFreeDirective) { + return _getDirectiveName(contextFreeDirective); + } else if (directive instanceof KeywordPair keywordPair) { + return _getDirectiveName(keywordPair); + } else if (directive instanceof SpecificDirective specificDirective) { + return _getDirectiveName(specificDirective); + } else if (directive instanceof GroupBlock groupBlock) { + return _getDirectiveName(groupBlock); + } else if (directive != null) { + return _getDirectiveName(directive); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(directive).toString()); + } + } + + public Iterable createRule(final FormatConfiguration format, final GrammarRule rule) { + final List members = CollectionLiterals. newArrayList(); + JvmOperation method = jvmTypesBuilder.toMethod(format, "config" + + rule.getTargetRule().getName(), _typeReferenceBuilder.typeRef("void"), (final JvmOperation it) -> initializeRuleMethod(format, rule, it)); + members.add(method); + return members; + } + + private void initializeRuleMethod(final FormatConfiguration format, final GrammarRule rule, final JvmOperation it) { + it.setFinal(false); + it.setVisibility(JvmVisibility.PROTECTED); + jvmTypesBuilder. operator_add(it.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_CONFIG, _typeReferenceBuilder.typeRef(BASE_FORMAT_CONFIG))); + AbstractRule targetRule = rule.getTargetRule(); + if (targetRule instanceof ParserRule) { + final String ruleName = getFullyQualifiedName(getGrammar(rule.getTargetRule())) + "$" + grammarAccess.gaRuleAccessorClassName(rule.getTargetRule()); + jvmTypesBuilder. operator_add(it.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_ELEMENTS, typeReferences.getTypeForName(ruleName, rule.getTargetRule()))); + jvmTypesBuilder.setDocumentation(it, generateJavaDoc("Configuration for " + rule.getTargetRule().getName() + + ".", CollectionLiterals. newLinkedHashMap(Pair. of(PARAMETER_CONFIG, THE_FORMAT_CONFIGURATION), Pair. of(PARAMETER_ELEMENTS, "the grammar access for " + + rule.getTargetRule().getName() + " elements")))); + } else if (targetRule instanceof EnumRule) { + jvmTypesBuilder. operator_add(it.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_RULE, typeReferences.getTypeForName(EnumRule.class.getName(), rule.getTargetRule()))); + jvmTypesBuilder.setDocumentation(it, generateJavaDoc("Configuration for " + rule.getTargetRule().getName() + + ".", CollectionLiterals. newLinkedHashMap(Pair. of(PARAMETER_CONFIG, THE_FORMAT_CONFIGURATION), Pair. of(PARAMETER_RULE, "the enum rule for " + + rule.getTargetRule().getName())))); + } else if (targetRule instanceof TerminalRule) { + jvmTypesBuilder. operator_add(it.getParameters(), jvmTypesBuilder.toParameter(format, PARAMETER_RULE, typeReferences.getTypeForName(TerminalRule.class.getName(), rule.getTargetRule()))); + jvmTypesBuilder.setDocumentation(it, generateJavaDoc("Configuration for " + rule.getTargetRule().getName() + + ".", CollectionLiterals. newLinkedHashMap(Pair. of(PARAMETER_CONFIG, THE_FORMAT_CONFIGURATION), Pair. of(PARAMETER_RULE, "the terminal rule for " + + rule.getTargetRule().getName())))); + } + jvmTypesBuilder.setBody(it, (final ITreeAppendable op) -> { + final List directives = ListExtensions. map(rule.getDirectives(), (final EObject d) -> directive(d, getRuleName(rule)).toString()); + op.append(fixLastLine(IterableExtensions.join(directives))); + }); + } + + public String fixLastLine(final String content) { + if (content.endsWith("\r\n")) { + return content.substring(0, content.length() - 2); + } else { + return content; + } + } + + // CHECKSTYLE:CONSTANTS-OFF + // directive dispatch + protected CharSequence _directive(final SpecificDirective dir, final String partialName) { + return matchReference(dir.getMatcherList(), dir.getGrammarElements(), partialName + getDirectiveName(dir)); + } + + protected CharSequence _directive(final ContextFreeDirective dir, final String partialName) { + return matchLookup(dir.getMatcherList(), dir.getGrammarElements(), partialName + getDirectiveName(dir)); + } + + protected CharSequence _directive(final GroupBlock dir, final String partialName) { + if (dir.getMatcherList() != null) { + return matchReference(dir.getMatcherList(), new BasicEList<>(Collections.singletonList(dir.getGrammarElement())), partialName + getDirectiveName(dir)); + } else if (dir.getSubGroup() != null) { + return directive(dir.getSubGroup(), partialName + getDirectiveName(dir)); + } else { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + for (final GrammarRuleDirective d : dir.getDirectives()) { + sb.append(directive(d, partialName + getDirectiveName(dir))); + } + return sb; + } + } + + protected CharSequence _directive(final KeywordPair dir, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("// ").append(locatorString(dir)).append('\n'); + sb.append("for (final org.eclipse.xtext.util.Pair pair : elements.findKeywordPairs(\"").append(dir.getLeft()).append("\", \"").append(dir.getRight()).append("\")) {\n"); + for (final Matcher matcher : dir.getLeftMatchers()) { + sb.append(matchLookupPartial(matcher.getLocator(), matcher, "pair.getFirst()", partialName + getDirectiveName(dir))); + sb.append('\n'); + } + for (final Matcher matcher : dir.getRightMatchers()) { + sb.append(matchLookupPartial(matcher.getLocator(), matcher, "pair.getSecond()", partialName + getDirectiveName(dir))); + sb.append('\n'); + } + sb.append("}\n"); + return sb; + } + + protected CharSequence _directive(final Object dir, final String partialName) { + throw new UnsupportedOperationException("Unknown directive " + dir.getClass().getName()); + } + + public CharSequence directive(final Object dir, final String partialName) { + if (dir instanceof ContextFreeDirective contextFreeDirective) { + return _directive(contextFreeDirective, partialName); + } else if (dir instanceof KeywordPair keywordPair) { + return _directive(keywordPair, partialName); + } else if (dir instanceof SpecificDirective specificDirective) { + return _directive(specificDirective, partialName); + } else if (dir instanceof GroupBlock groupBlock) { + return _directive(groupBlock, partialName); + } else if (dir != null) { + return _directive(dir, partialName); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(dir, partialName).toString()); + } + } + + public CharSequence matchLookup(final MatcherList matcherList, final EList elements, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if (!elements.isEmpty()) { + boolean hasRuleElements = elements.stream().anyMatch((final GrammarElementLookup e) -> e.getRule() != null); + if (hasRuleElements) { + sb.append("// ").append(locatorString(matcherList)).append('\n'); + sb.append("for (org.eclipse.xtext.RuleCall ruleCall : elements.findRuleCalls("); + boolean first = true; + for (final GrammarElementLookup element : elements.stream().filter((final GrammarElementLookup e) -> e.getRule() != null).toList()) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append("elements.").append(grammarAccess.gaRuleAccessor(element.getRule())); + } + sb.append(")) {\n"); + for (final Matcher matcher : matcherList.getMatchers()) { + sb.append(" ").append(matchLookupPartial(matcher.getLocator(), matcher, "ruleCall", partialName)).append('\n'); + } + sb.append("}\n"); + } + boolean hasKeywordElements = elements.stream().anyMatch((final GrammarElementLookup e) -> e.getKeyword() != null); + if (hasKeywordElements) { + sb.append("// ").append(locatorString(matcherList)).append('\n'); + sb.append("for (org.eclipse.xtext.Keyword keyword : elements.findKeywords("); + boolean first2 = true; + for (final GrammarElementLookup element : elements.stream().filter((final GrammarElementLookup e) -> e.getKeyword() != null).toList()) { + if (!first2) { + sb.append(", "); + } + first2 = false; + sb.append('"').append(element.getKeyword()).append('"'); + } + sb.append(")) {\n"); + for (final Matcher matcher : matcherList.getMatchers()) { + sb.append(" ").append(matchLookupPartial(matcher.getLocator(), matcher, "keyword", partialName)).append('\n'); + } + sb.append("}\n"); + } + } + return sb; + } + + // matchLookupPartial dispatch + protected CharSequence _matchLookupPartial(final ColumnLocator columnLocator, final Matcher matcher, final String eobjectTypeName, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if ("before".equals(matcher.getType().getLiteral())) { + sb.append("config.setColumn(").append(getValueOrConstant(columnLocator.getValue())).append(", ").append(columnLocator.isFixed()).append(", ").append(columnLocator.isRelative()).append(", ").append(columnLocator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").before(").append(eobjectTypeName).append("); // ").append(locatorString(columnLocator)).append('\n'); + sb.append("config.setColumn(").append(getValueOrConstant(columnLocator.getValue())).append(", ").append(columnLocator.isFixed()).append(", ").append(columnLocator.isRelative()).append(", ").append(columnLocator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").after(").append(eobjectTypeName).append("); // ").append(locatorString(columnLocator)); + } else { + sb.append("config.setColumn(").append(getValueOrConstant(columnLocator.getValue())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").").append(matcherType(matcher.getType())).append('(').append(eobjectTypeName).append("); // ").append(locatorString(columnLocator)); + } + return sb; + } + + protected CharSequence _matchLookupPartial(final OffsetLocator offsetLocator, final Matcher matcher, final String eobjectTypeName, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if ("before".equals(matcher.getType().getLiteral())) { + sb.append("config.setColumn(").append(getValueOrConstant(offsetLocator.getValue())).append(", ").append(offsetLocator.isFixed()).append(", true, ").append(offsetLocator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").before(").append(eobjectTypeName).append("); // ").append(locatorString(offsetLocator)).append('\n'); + sb.append("config.setColumn(").append(getValueOrConstant(offsetLocator.getValue())).append(", ").append(offsetLocator.isFixed()).append(", true, ").append(offsetLocator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").after(").append(eobjectTypeName).append("); // ").append(locatorString(offsetLocator)); + } else { + sb.append("config.setOffset(").append(getValueOrConstant(offsetLocator.getValue())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").").append(matcherType(matcher.getType())).append('(').append(eobjectTypeName).append("); // ").append(locatorString(offsetLocator)); + } + return sb; + } + + protected CharSequence _matchLookupPartial(final EObject locator, final Matcher matcher, final String eobjectTypeName, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("config.").append(locator(matcher, matcher.getLocator(), partialName)).append('.').append(matcherType(matcher.getType())).append('(').append(eobjectTypeName).append(");"); + return sb; + } + + public CharSequence matchLookupPartial(final EObject columnLocator, final Matcher matcher, final String eobjectTypeName, final String partialName) { + if (columnLocator instanceof ColumnLocator cl) { + return _matchLookupPartial(cl, matcher, eobjectTypeName, partialName); + } else if (columnLocator instanceof OffsetLocator ol) { + return _matchLookupPartial(ol, matcher, eobjectTypeName, partialName); + } else if (columnLocator != null) { + return _matchLookupPartial(columnLocator, matcher, eobjectTypeName, partialName); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays. asList(columnLocator, matcher, eobjectTypeName, partialName).toString()); + } + } + + public CharSequence matchReference(final MatcherList matcherList, final EList elements, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if (!elements.isEmpty()) { + for (final Matcher matcher : matcherList.getMatchers()) { + if (FormatGeneratorUtil.isTwoArgumentMatcherType(matcher.getType())) { + sb.append(match(matcher, elements.get(0), elements.get(1), matcher.getLocator(), partialName)); + } else { + for (final EObject e : elements) { + sb.append(match(matcher, e, matcher.getLocator(), partialName)); + } + } + } + } + return sb; + } + + public CharSequence match(final Matcher matcher, final EObject element1, final EObject element2, final EObject locator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("config.").append(locator(matcher, matcher.getLocator(), partialName)).append('.').append(matcherType(matcher.getType())).append('(').append(elementAccess(element1)).append(", ").append(elementAccess(element2)).append("); // ").append(locatorString(matcher)).append('\n'); + return sb; + } + + // match dispatch + protected CharSequence _match(final Matcher matcher, final EObject element, final Locator locator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("config.").append(locator(matcher, matcher.getLocator(), partialName)).append('.').append(matcherType(matcher.getType())).append('(').append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + return sb; + } + + protected CharSequence _match(final Matcher matcher, final EObject element, final NoFormatLocator locator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("config.").append(locator(matcher, matcher.getLocator(), partialName)).append('.').append(matcherType(matcher.getType())).append('(').append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + return sb; + } + + protected CharSequence _match(final Matcher matcher, final EObject element, final ColumnLocator locator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if ("before".equals(matcher.getType().getLiteral())) { + if (locator.getParameter() != null) { + sb.append("config.setColumn(").append(locator.isFixed()).append(", ").append(locator.isRelative()).append(", ").append(locator.isNobreak()).append(", new ").append(getParameterCalculatorName(partialName, matcher)).append("()"); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").before(").append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + sb.append("config.setColumn(").append(locator.isFixed()).append(", ").append(locator.isRelative()).append(", ").append(locator.isNobreak()).append(", new ").append(getParameterCalculatorName(partialName, matcher)).append("()"); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").after(").append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + } else { + sb.append("config.setColumn(").append(getValueOrConstant(locator.getValue())).append(", ").append(locator.isFixed()).append(", ").append(locator.isRelative()).append(", ").append(locator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").before(").append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + sb.append("config.setColumn(").append(getValueOrConstant(locator.getValue())).append(", ").append(locator.isFixed()).append(", ").append(locator.isRelative()).append(", ").append(locator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").after(").append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + } + } else { + if (locator.getParameter() != null) { + sb.append("config.setColumn(new ").append(getParameterCalculatorName(partialName, matcher)).append("()"); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").").append(matcherType(matcher.getType())).append('(').append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + } else { + sb.append("config.setColumn(").append(getValueOrConstant(locator.getValue())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").").append(matcherType(matcher.getType())).append('(').append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + } + } + return sb; + } + + protected CharSequence _match(final Matcher matcher, final EObject element, final OffsetLocator locator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if ("before".equals(matcher.getType().getLiteral())) { + sb.append("config.setColumn(").append(getValueOrConstant(locator.getValue())).append(", ").append(locator.isFixed()).append(", true, ").append(locator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").before(").append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + sb.append("config.setColumn(").append(getValueOrConstant(locator.getValue())).append(", ").append(locator.isFixed()).append(", true, ").append(locator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").after(").append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + } else { + sb.append("config.setOffset(").append(getValueOrConstant(locator.getValue())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(").").append(matcherType(matcher.getType())).append('(').append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + } + return sb; + } + + protected CharSequence _match(final Matcher matcher, final EObject element, final IndentLocator locator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("config.").append(locator(matcher, matcher.getLocator(), partialName)).append('.').append(matcherType(matcher.getType())).append('(').append(elementAccess(element)).append("); // ").append(locatorString(matcher)).append('\n'); + return sb; + } + + public CharSequence match(final Matcher matcher, final EObject element, final Locator locator, final String partialName) { + if (locator instanceof ColumnLocator cl) { + return _match(matcher, element, cl, partialName); + } else if (locator instanceof IndentLocator il) { + return _match(matcher, element, il, partialName); + } else if (locator instanceof NoFormatLocator nfl) { + return _match(matcher, element, nfl, partialName); + } else if (locator instanceof OffsetLocator ol) { + return _match(matcher, element, ol, partialName); + } else if (locator != null) { + return _match(matcher, element, locator, partialName); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(matcher, element, locator, partialName).toString()); + } + } + + public String matcherType(final MatcherType matcherType) { + return matcherType.getLiteral(); + } + + // elementAccess dispatch + protected CharSequence _elementAccess(final GrammarElementLookup grammarElementLookup) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if (grammarElementLookup.getRule() != null) { + sb.append("elements.findRuleCalls(").append(grammarAccess.gaElementsAccessor(grammarElementLookup.getRule())).append(')'); + } else if (grammarElementLookup.getKeyword() != null) { + sb.append("elements.findKeywords(\"").append(grammarElementLookup.getKeyword()).append("\")"); + } + return sb; + } + + protected CharSequence _elementAccess(final GrammarElementReference grammarElementReference) { + if (grammarElementReference.getRuleCall() != null) { + return elementAccess(grammarElementReference.getRuleCall()); + } else if (grammarElementReference.getKeyword() != null) { + return elementAccess(grammarElementReference.getKeyword()); + } else if (grammarElementReference.getAssignment() != null) { + return elementAccess(grammarElementReference.getAssignment()); + } else if (grammarElementReference.getSelf() != null) { + if (FormatGeneratorUtil.containedByParserRule(grammarElementReference)) { + return "elements.getRule()"; + } else { + return "rule"; + } + } else if (grammarElementReference.getRule() != null) { + return elementAccess(grammarElementReference.getRule()); + } + return null; + } + + protected CharSequence _elementAccess(final AbstractRule abstractRule) { + return "getGrammarAccess()." + grammarAccess.gaRuleAccessor(abstractRule); + } + + protected CharSequence _elementAccess(final AbstractElement abstractElement) { + return "elements." + grammarAccess.gaElementAccessor(abstractElement); + } + + protected CharSequence _elementAccess(final Object object) { + throw new UnsupportedOperationException("Unknown Xtext element " + object.getClass().getName()); + } + + public CharSequence elementAccess(final Object grammarElementLookup) { + if (grammarElementLookup instanceof GrammarElementLookup gel) { + return _elementAccess(gel); + } else if (grammarElementLookup instanceof GrammarElementReference ger) { + return _elementAccess(ger); + } else if (grammarElementLookup instanceof AbstractElement ae) { + return _elementAccess(ae); + } else if (grammarElementLookup instanceof AbstractRule ar) { + return _elementAccess(ar); + } else if (grammarElementLookup != null) { + return _elementAccess(grammarElementLookup); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(grammarElementLookup).toString()); + } + } + + // locator dispatch + protected CharSequence _locator(final Matcher matcher, final SpaceLocator spaceLocator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if (spaceLocator.isNoSpace()) { + sb.append("setNoSpace("); + if (matcher.getCondition() != null) { + sb.append("new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + } else { + sb.append("setSpace(").append(getValueOrConstant(spaceLocator.getValue())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + } + return sb; + } + + protected CharSequence _locator(final Matcher matcher, final RightPaddingLocator rightPaddingLocator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("setRightPadding(").append(getValueOrConstant(rightPaddingLocator.getValue())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + return sb; + } + + protected CharSequence _locator(final Matcher matcher, final LinewrapLocator linewrapLocator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if (linewrapLocator.isNoLinewrap()) { + sb.append("setNoLinewrap("); + if (matcher.getCondition() != null) { + sb.append("new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + } else { + sb.append("setLinewrap("); + if (linewrapLocator.getValue() != null) { + sb.append(getValueOrConstant(linewrapLocator.getValue())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + } else if (linewrapLocator.getMinimum() != null) { + sb.append(getValueOrConstant(linewrapLocator.getMinimum())).append(", ").append(getValueOrConstant(linewrapLocator.getDefault())).append(", ").append(getValueOrConstant(linewrapLocator.getMaximum())); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + } else { + if (matcher.getCondition() != null) { + sb.append("new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + } + sb.append(')'); + } + return sb; + } + + protected CharSequence _locator(final Matcher matcher, final ColumnLocator columnLocator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("setColumn(").append(getValueOrConstant(columnLocator.getValue())).append(", ").append(columnLocator.isFixed()).append(", ").append(columnLocator.isRelative()).append(", ").append(columnLocator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + return sb; + } + + protected CharSequence _locator(final Matcher matcher, final OffsetLocator offsetLocator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("setColumn(").append(getValueOrConstant(offsetLocator.getValue())).append(", ").append(offsetLocator.isFixed()).append(", true, ").append(offsetLocator.isNobreak()); + if (matcher.getCondition() != null) { + sb.append(", new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + return sb; + } + + protected CharSequence _locator(final Matcher matcher, final IndentLocator indentLocator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + if (indentLocator.isIncrement()) { + sb.append("setIndentationIncrement("); + } else { + sb.append("setIndentationDecrement("); + } + if (indentLocator.getValue() != null && (indentLocator.getValue().getReference() != null || indentLocator.getValue().getLiteral() >= 1)) { + sb.append(getValueOrConstant(indentLocator.getValue())); + } else if (indentLocator.getParameter() != null) { + sb.append("new ").append(getParameterCalculatorName(partialName, matcher)).append("()"); + } + if (matcher.getCondition() != null) { + if (indentLocator.getValue() != null || indentLocator.getParameter() != null) { + sb.append(','); + } + sb.append(" new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + return sb; + } + + protected CharSequence _locator(final Matcher matcher, final NoFormatLocator noFormatLocator, final String partialName) { + final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_CAPACITY); + sb.append("setNoFormat("); + if (matcher.getCondition() != null) { + sb.append("new ").append(getLocatorActivatorName(partialName, matcher)).append("()"); + } + sb.append(')'); + return sb; + } + + protected CharSequence _locator(final Matcher matcher, final Locator locator, final String partialName) { + throw new UnsupportedOperationException("Unknown locator " + locator.getClass().getName()); + } + + public CharSequence locator(final Matcher matcher, final Locator columnLocator, final String partialName) { + if (columnLocator instanceof ColumnLocator cl) { + return _locator(matcher, cl, partialName); + } else if (columnLocator instanceof IndentLocator il) { + return _locator(matcher, il, partialName); + } else if (columnLocator instanceof LinewrapLocator ll) { + return _locator(matcher, ll, partialName); + } else if (columnLocator instanceof NoFormatLocator nfl) { + return _locator(matcher, nfl, partialName); + } else if (columnLocator instanceof OffsetLocator ol) { + return _locator(matcher, ol, partialName); + } else if (columnLocator instanceof RightPaddingLocator rpl) { + return _locator(matcher, rpl, partialName); + } else if (columnLocator instanceof SpaceLocator sl) { + return _locator(matcher, sl, partialName); + } else if (columnLocator != null) { + return _locator(matcher, columnLocator, partialName); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(matcher, columnLocator, partialName).toString()); + } + } + + // CHECKSTYLE:CONSTANTS-ON + + // getValueOrConstant dispatch + protected String _getValueOrConstant(final StringValue stringValue) { + if (stringValue.getLiteral() == null) { + return stringValue.getReference().getName(); + } else { + return "\"" + stringValue.getLiteral() + "\""; + } + } + + protected String _getValueOrConstant(final IntValue intValue) { + if (intValue.getLiteral() == null) { + return intValue.getReference().getName(); + } else { + return intValue.getLiteral().toString(); + } + } + + public String getValueOrConstant(final EObject intValue) { + if (intValue instanceof IntValue iv) { + return _getValueOrConstant(iv); + } else if (intValue instanceof StringValue sv) { + return _getValueOrConstant(sv); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(intValue).toString()); + } + } + + public String locatorString(final EObject object) { + return IterableExtensions. lastOrNull((Iterable) Conversions.doWrapArray(getFileLocation(object).split("/"))); + } + + @Override + public void infer(final EObject format, final IJvmDeclaredTypeAcceptor acceptor, final boolean isPreIndexingPhase) { + if (format instanceof FormatConfiguration formatConfiguration) { + _infer(formatConfiguration, acceptor, isPreIndexingPhase); + } else if (format != null) { + _infer(format, acceptor, isPreIndexingPhase); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(format, acceptor, isPreIndexingPhase).toString()); + } + } +} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.xtend b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.xtend deleted file mode 100644 index 3db5521ec7..0000000000 --- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/jvmmodel/FormatJvmModelInferrer.xtend +++ /dev/null @@ -1,766 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.format.jvmmodel - -import com.avaloq.tools.ddk.xtext.format.format.ColumnLocator -import com.avaloq.tools.ddk.xtext.format.format.Constant -import com.avaloq.tools.ddk.xtext.format.format.ContextFreeDirective -import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration -import com.avaloq.tools.ddk.xtext.format.format.GrammarElementLookup -import com.avaloq.tools.ddk.xtext.format.format.GrammarElementReference -import com.avaloq.tools.ddk.xtext.format.format.GrammarRule -import com.avaloq.tools.ddk.xtext.format.format.GroupBlock -import com.avaloq.tools.ddk.xtext.format.format.IndentLocator -import com.avaloq.tools.ddk.xtext.format.format.IntValue -import com.avaloq.tools.ddk.xtext.format.format.KeywordPair -import com.avaloq.tools.ddk.xtext.format.format.LinewrapLocator -import com.avaloq.tools.ddk.xtext.format.format.Locator -import com.avaloq.tools.ddk.xtext.format.format.Matcher -import com.avaloq.tools.ddk.xtext.format.format.MatcherList -import com.avaloq.tools.ddk.xtext.format.format.MatcherType -import com.avaloq.tools.ddk.xtext.format.format.NoFormatLocator -import com.avaloq.tools.ddk.xtext.format.format.OffsetLocator -import com.avaloq.tools.ddk.xtext.format.format.RightPaddingLocator -import com.avaloq.tools.ddk.xtext.format.format.Rule -import com.avaloq.tools.ddk.xtext.format.format.SpaceLocator -import com.avaloq.tools.ddk.xtext.format.format.SpecificDirective -import com.avaloq.tools.ddk.xtext.format.format.StringValue -import com.avaloq.tools.ddk.xtext.format.format.WildcardRule -import com.avaloq.tools.ddk.xtext.formatting.AbstractExtendedFormatter -import com.avaloq.tools.ddk.xtext.formatting.ExtendedFormattingConfig -import com.avaloq.tools.ddk.xtext.formatting.locators.LocatorActivator -import com.avaloq.tools.ddk.xtext.formatting.locators.LocatorParameterCalculator -import com.google.common.collect.Iterables -import com.google.inject.Inject -import java.util.List -import java.util.Map -import org.eclipse.emf.common.util.BasicEList -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtext.AbstractElement -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.EnumRule -import org.eclipse.xtext.ParserRule -import org.eclipse.xtext.TerminalRule -import org.eclipse.xtext.util.Strings -import org.eclipse.xtext.common.types.JvmAnnotationReference -import org.eclipse.xtext.common.types.JvmAnnotationType -import org.eclipse.xtext.common.types.JvmDeclaredType -import org.eclipse.xtext.common.types.JvmGenericType -import org.eclipse.xtext.common.types.JvmMember -import org.eclipse.xtext.common.types.JvmVisibility -import org.eclipse.xtext.common.types.TypesFactory -import org.eclipse.xtext.common.types.util.TypeReferences -import org.eclipse.xtext.xbase.XExpression -import org.eclipse.xtext.xbase.compiler.XbaseCompiler -import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer -import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor -import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder - -import static com.avaloq.tools.ddk.xtext.util.EObjectUtil.getFileLocation -import static org.eclipse.xtext.GrammarUtil.* - -import static extension com.avaloq.tools.ddk.xtext.format.generator.FormatGeneratorUtil.* -import org.eclipse.emf.ecore.EClass -import java.util.regex.Pattern -import org.eclipse.xtext.xtext.RuleNames -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.xtext.GrammarUtil -import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions -import org.eclipse.xtext.Grammar - -/** - *

Infers a JVM model from the source model.

- * - *

The JVM model should contain all elements that would appear in the Java code - * which is generated from the source model. Other models link against the JVM model rather than the source model.

- */ -class FormatJvmModelInferrer extends AbstractModelInferrer { - @Inject extension JvmTypesBuilder - @Inject extension TypeReferences - @Inject extension GrammarAccessExtensions grammarAccess - @Inject TypesFactory typesFactory - @Inject XbaseCompiler xbaseCompiler - - static final String BASE_FORMATTER_CLASS_NAME = AbstractExtendedFormatter.name - static final String BASE_FORMAT_CONFIG = ExtendedFormattingConfig.name - - static final String METHOD_ACTIVATE= 'activate' - static final String METHOD_CALCULATE= 'calculateParameter' - - static final String PARAMETER_CONFIG = 'config' - static final String PARAMETER_ELEMENTS = 'elements' - static final String PARAMETER_RULE = 'rule' - static final String PARAMETER_GRAMMAR_ACCESS = 'grammarAccess' - static final String PARAMETER_CONTEXT = 'context' - static final String PARAMETER_COLUMN = 'currentColumn' - - /** - * The dispatch method {@code infer} is called for each instance of the - * given element's type that is contained in a resource. - * - * @param element - * the model to create one or more {@link JvmDeclaredType declared types} from. - * @param acceptor - * each created {@link JvmDeclaredType type} without a container should be passed to the acceptor in order - * get attached to the current resource. The acceptor's {@link IJvmDeclaredTypeAcceptor#accept(JvmDeclaredType, - * org.eclipse.xtext.xbase.lib.Procedures.Procedure1)} method takes the constructed empty type for the - * pre-indexing phase. This one is further initialized in the indexing phase using the passed closure. - * @param isPreIndexingPhase - * whether the method is called in a pre-indexing phase, i.e. when the global index is not yet fully updated. You must not - * rely on linking using the index if isPreIndexingPhase is {@code true}. - */ - def dispatch void infer(FormatConfiguration format, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) { - if (isPreIndexingPhase) return - val context = format.targetGrammar - if (EcoreUtil.getAdapter(context.eAdapters(), typeof(RuleNames)) === null) { - val allRules = GrammarUtil.allRules(context); - for(AbstractRule rule: allRules) { - val adpt =EcoreUtil.getAdapter(rule.eAdapters(), typeof(RuleNames)); - if(adpt!==null) rule.eAdapters().remove(adpt) - } - RuleNames.getRuleNames(context, true); - } - acceptor.accept( - format.toClass(Strings.lastToken(getFormatterName(format, "Abstract"),".")), [ - inferClass(format, it) - inferConstants(format, it) - inferGetGrammarAccess(format, it) - inferConfigureAcsFormatting(format, it) - inferInit(format, it) - inferRules(format, it) - inferLocatorActivators(format, it) - ] - ) - } - - def inferClass(FormatConfiguration format, JvmGenericType it) { - var targetGrammarName = Strings.emptyIfNull(format.targetGrammar?.name) - documentation = '''The abstract formatting configuration for «Strings.skipLastToken(targetGrammarName, ".")».«Strings.lastToken(targetGrammarName,".")» as declared in «Strings.lastToken(targetGrammarName,".")».format.''' - if(format.formatterBaseClass !== null) { - superTypes += typeRef(format.formatterBaseClass.packageName + '.' + format.formatterBaseClass.simpleName) - } else { - superTypes += typeRef(BASE_FORMATTER_CLASS_NAME) - } - packageName = Strings.skipLastToken(getFormatterName(format, ""), ".") - abstract = true - } - - def inferConstants(FormatConfiguration format, JvmGenericType it) { - if(!format.allConstants.isEmpty) { - members += format.allConstants.map(c|createConstant(format, c)); - } - } - - def getFullyQualifiedName(Grammar g) { - GrammarUtil.getNamespace(g) + ".services." + GrammarUtil.getSimpleName(g) + "GrammarAccess"; - } - - def inferGetGrammarAccess(FormatConfiguration format, JvmGenericType it) { - members += format.toMethod('getGrammarAccess', format.targetGrammar.getFullyQualifiedName.getTypeForName(format.targetGrammar)) [ - visibility = JvmVisibility::PROTECTED - val JvmAnnotationReference overrideAnnotation = createOverrideAnnotation(format) - if(overrideAnnotation !== null) { - annotations += overrideAnnotation; - } - body = [append('''return («GrammarUtil.getSimpleName(format.targetGrammar) + "GrammarAccess"») super.getGrammarAccess();''')] - ] - } - - def inferConfigureAcsFormatting(FormatConfiguration format, JvmGenericType it) { - members += format.toMethod('configureAcsFormatting', typeRef('void')) [ - visibility = JvmVisibility::PROTECTED - val JvmAnnotationReference overrideAnnotation = createOverrideAnnotation(format) - if(overrideAnnotation !== null) { - annotations += overrideAnnotation; - } - parameters += format.toParameter(PARAMETER_CONFIG, typeRef(BASE_FORMAT_CONFIG)) - body = [append('''init(config, getGrammarAccess());''')] - ] - } - - def inferInit(FormatConfiguration format, JvmGenericType it) { - members += format.toMethod('init', typeRef('void')) [ - documentation = generateJavaDoc('Calls all configXyz methods declared in this class.', newLinkedHashMap( - PARAMETER_CONFIG -> 'the format configuration', - PARAMETER_GRAMMAR_ACCESS -> 'the grammar access for the grammar' - )) - visibility = JvmVisibility::PROTECTED - parameters += format.toParameter(PARAMETER_CONFIG, typeRef(BASE_FORMAT_CONFIG)) - parameters += format.toParameter(PARAMETER_GRAMMAR_ACCESS, typeRef(format.targetGrammar.getFullyQualifiedName)) - body = [ - val rules = listConfigRules(format) - for (i : 0 ..< rules.length()) { - if(i != 0) newLine - append(rules.get(i)) - } - ] - ] - } - - def inferRules(FormatConfiguration format, JvmGenericType it) { - members += format.parserRules.map(c|createRule(format, c)).flatten; - members += format.enumRules.map(c|createRule(format, c)).flatten; - members += format.terminalRules.map(c|createRule(format, c)).flatten; - if(format.wildcardRule !== null) { - members += format.toMethod('configFindElements', typeRef('void')) [ - documentation = generateJavaDoc('Configuration for IGrammarAccess.findXyz() methods.', newLinkedHashMap( - PARAMETER_CONFIG -> 'the format configuration', - PARAMETER_ELEMENTS -> 'the grammar access for the grammar' - )) - visibility = JvmVisibility::PROTECTED - parameters += format.toParameter(PARAMETER_CONFIG, typeRef(BASE_FORMAT_CONFIG)) - parameters += format.toParameter(PARAMETER_ELEMENTS, typeRef(getGrammar(format.targetGrammar).getFullyQualifiedName)) - body = [ - val directives = format.wildcardRule.directives.map(d|directive(d, format.wildcardRule.getRuleName).toString) - append(fixLastLine(directives.join)) - ] - ] - } - } - - def inferLocatorActivators(FormatConfiguration format, JvmGenericType it) { - var List rules = newLinkedList - rules += format.parserRules + format.terminalRules + format.enumRules - rules += format.wildcardRule - for (rule : rules) { - var List directives = newLinkedList - switch rule { - GrammarRule: directives += rule.directives - WildcardRule: directives += rule.directives - } - for (directive : directives.filterNull) { - for (matcher : collectMatchers(directive)) { - if (matcher.locator instanceof ColumnLocator && (matcher.locator as ColumnLocator).parameter !== null) { - members += createParameterCalculatorInnerClass(format, rule, directive, matcher, (matcher.locator as ColumnLocator).parameter, it) - } - if (matcher.locator instanceof IndentLocator && (matcher.locator as IndentLocator).parameter !== null) { - members += createParameterCalculatorInnerClass(format, rule, directive, matcher, (matcher.locator as IndentLocator).parameter, it) - } - if (matcher.condition !== null) { - members += createLocatorActivatorInnerClass(format, rule, directive, matcher, it) - } - } - } - } - } - - def createLocatorActivatorInnerClass(FormatConfiguration format, Rule rule, EObject directive, Matcher matcher, JvmGenericType type) { - format.toClass(rule.getLocatorActivatorName(directive, matcher)) [ - static = true - final = true - visibility = JvmVisibility::PROTECTED - superTypes += format.getLocatorActivatorSuperType(rule) - members += matcher.toMethod(METHOD_ACTIVATE, format.getLocatorActivatorReturnType) [ - parameters += format.toParameter(PARAMETER_CONTEXT, getGrammarElementNameFromSelf(rule).getTypeForName(format)) - parameters += createCurrenctColumnParameter - if(format.eResource != matcher.eResource) { - body = [xbaseCompiler.compile(matcher.condition, it, format.getLocatorActivatorReturnType, null)] - } else { - body = matcher.condition - } - ] - ] - } - - def createCurrenctColumnParameter(){ - var result = typesFactory.createJvmFormalParameter() - result.setName(PARAMETER_COLUMN) - result.setParameterType(typeRef(typeof(Integer))) - return result - } - - def createParameterCalculatorInnerClass(FormatConfiguration format, Rule rule, EObject directive, Matcher matcher, XExpression parameterCalculation, JvmGenericType type) { - format.toClass(rule.getParameterCalculatorName(directive, matcher)) [ - static = true - final = true - visibility = JvmVisibility::PROTECTED - superTypes += format.getParameterCalculatorSuperType(rule) - members += matcher.toMethod(METHOD_CALCULATE, format.parameterCalculatorReturnType) [ - parameters += format.toParameter(PARAMETER_CONTEXT, getGrammarElementNameFromSelf(rule).getTypeForName(format)) - parameters += createCurrenctColumnParameter - if(format.eResource != matcher.eResource) { - body = [xbaseCompiler.compile(parameterCalculation, it, format.parameterCalculatorReturnType, null)] - } else { - body = parameterCalculation - } - ] - ] - } - - def listConfigRules(FormatConfiguration format) { - val configRules = newArrayList; - if(format.wildcardRule !== null) { - configRules += '''configFindElements(config, grammarAccess);''' - } - for (rule : format.parserRules) { - configRules += '''config«rule.targetRule.name»(config, grammarAccess.«rule.targetRule.gaElementsAccessor»);''' - } - for (rule : format.enumRules) { - configRules += '''config«rule.targetRule.name»(config, grammarAccess.«rule.targetRule.gaRuleAccessor»);''' - } - for (rule : format.terminalRules) { - configRules += '''config«rule.targetRule.name»(config, grammarAccess.«rule.targetRule.gaRuleAccessor»);''' - } - configRules - } - - def generateJavaDoc(String description, Map parameters) { - ''' - «description» - - «FOR parameter : parameters.entrySet()» - @param «parameter.key» - - «parameter.value» - «ENDFOR» - '''.toString - } - - def JvmAnnotationReference createOverrideAnnotation(FormatConfiguration format) { - val annotationTypeRef = typeRef(typeof(Override)); - var JvmAnnotationReference overrideAnnotation = null - if(annotationTypeRef !== null) { - val annotationType = annotationTypeRef.type; - overrideAnnotation = typesFactory.createJvmAnnotationReference(); - overrideAnnotation.annotation = annotationType as JvmAnnotationType; - } - overrideAnnotation - } - - def JvmMember createConstant(FormatConfiguration configuration, Constant constant) { - switch constant { - case constant.stringValue !== null: - constant.toField(constant.name, "String".getTypeForName(constant)) [ - documentation = locatorString(constant) - static = true - final = true - visibility = JvmVisibility::PROTECTED - initializer = [append('"' + constant.stringValue + '"')] - ] - case constant.intValue !== null: - constant.toField(constant.name, "int".getTypeForName(constant)) [ - documentation = locatorString(constant) - static = true - final = true - visibility = JvmVisibility::PROTECTED - initializer = [append(constant.intValue.toString)] - ] - } - } - - def collectMatchers(EObject directive) { - var List matchers = newLinkedList - switch directive { - GroupBlock: if(directive.matcherList !== null) matchers += directive.matcherList.matchers - SpecificDirective: if(directive.matcherList !== null) matchers += directive.matcherList.matchers - ContextFreeDirective: if(directive.matcherList !== null) matchers += directive.matcherList.matchers - KeywordPair: { - if(directive.leftMatchers !== null) matchers += directive.leftMatchers - if(directive.rightMatchers !== null) matchers += directive.rightMatchers - } - } - matchers - } - - def getLocatorActivatorReturnType(FormatConfiguration formatConfiguration) { - typeRef(typeof(boolean)) - } - - def getParameterCalculatorReturnType(FormatConfiguration formatConfiguration) { - typeRef(typeof(int)) - } - - // getLocatorActivatorSuperType dispatch - def dispatch getLocatorActivatorSuperType(FormatConfiguration formatConfiguration, GrammarRule rule) { - typeRef(typeof(LocatorActivator), getGrammarElementNameFromSelf(rule).getTypeForName(formatConfiguration)) - } - def dispatch getLocatorActivatorSuperType(FormatConfiguration formatConfiguration, WildcardRule rule) { - typeRef(typeof(LocatorActivator), getGrammarElementNameFromSelf(rule).getTypeForName(formatConfiguration)) - } - - def dispatch getParameterCalculatorSuperType(FormatConfiguration formatConfiguration, GrammarRule rule) { - typeRef(typeof(LocatorParameterCalculator), getGrammarElementNameFromSelf(rule).getTypeForName(formatConfiguration)) - } - def dispatch getParameterCalculatorSuperType(FormatConfiguration formatConfiguration, WildcardRule rule) { - typeRef(typeof(LocatorParameterCalculator), getGrammarElementNameFromSelf(rule).getTypeForName(formatConfiguration)) - } - - // getGrammarElementNameFromSelf dispatch - def dispatch String getGrammarElementNameFromSelf(GrammarRule rule) { - val originalRuleName = rule.ruleName - var actualRuleName = originalRuleName - if (rule.targetRule === null || rule.targetRule.type === null || rule.targetRule.type.classifier === null) { - return actualRuleName - } else if (actualRuleName != rule.targetRule?.type?.classifier?.name) { - actualRuleName = rule.targetRule.type.classifier.name - } - var metamodel = rule.targetRule?.type?.metamodel - if (metamodel === null) { - return actualRuleName - } else { - if (actualRuleName != originalRuleName) { - if (metamodel.EPackage.EClassifiers.stream.anyMatch[(it instanceof EClass) && (it.name.equalsIgnoreCase(originalRuleName))]){ - actualRuleName = originalRuleName - } - } - val metamodelPackage = EcoreUtil2::getURI(metamodel.EPackage)?.segment(1) - if (metamodelPackage === null) { - return actualRuleName - } - return metamodelPackage.substring(0,metamodelPackage.lastIndexOf('.core')) + '.' + metamodel.EPackage?.name + '.' + actualRuleName - } - } - def dispatch String getGrammarElementNameFromSelf(WildcardRule rule) { - EObject.name - } - - def int getMatcherIndex(Matcher matcher){ - val MatcherList matcherList=EcoreUtil2::getContainerOfType(matcher, typeof(MatcherList)) - return matcherList.matchers.indexOf(matcher); - } - - def String getLocatorActivatorName(EObject rule, EObject directive, Matcher matcher) { - ('ActivatorFor' + rule.getRuleName + matcher.getMatcherName(directive)).replace("Impl", "") - } - - def String getLocatorActivatorName(String partialName, Matcher matcher) { - ('ActivatorFor' + partialName + getMatcherIndex(matcher) + getLocatorName(matcher.locator) + matcher.type.name().toLowerCase.toFirstUpper).replace("Impl", "") - } - - def String getParameterCalculatorName(EObject rule, EObject directive, Matcher matcher) { - ('ParameterCalculatorFor' + rule.getRuleName + matcher.getMatcherName(directive)).replace("Impl", "") - } - - - def String getParameterCalculatorName(String partialName, Matcher matcher) { - ('ParameterCalculatorFor' + partialName + getMatcherIndex(matcher) + getLocatorName(matcher.locator) + matcher.type.name().toLowerCase.toFirstUpper).replace("Impl", "") - } - - // getRuleName dispatch - def dispatch String getRuleName(GrammarRule rule) { rule.targetRule?.name } - def dispatch String getRuleName(WildcardRule rule) { "Wildcard" } - def dispatch String getRuleName(EObject rule) { EObject.simpleName} - - def String getMatcherName(Matcher matcher, EObject directive) { - getDirectiveName(directive) + getMatcherIndex(matcher) + getLocatorName(matcher.locator) + matcher.type.name().toLowerCase.toFirstUpper - } - - def String getLocatorName(EObject locator) { - locator?.class?.simpleName ?: "" - } - - def convertNonAlphaNumeric(String str) { - val pattern = Pattern.compile("[\\W]"); - val matcher = pattern.matcher(str); - val sb = new StringBuffer(); - while(matcher.find) { - matcher.appendReplacement(sb, String.valueOf(Integer.toHexString(matcher.group.hashCode))) - } - matcher.appendTail(sb); - sb.toString; - } - - // getDirectiveName dispatch - def dispatch String getDirectiveName(GroupBlock directive) { - val GrammarRule grammarRule = EcoreUtil2::getContainerOfType(directive, typeof(GrammarRule)) - val directives = newArrayList(Iterables.filter(grammarRule.directives, GroupBlock)); - "Group" + String.valueOf(directives.indexOf(directive) + 1) - } - def dispatch String getDirectiveName(SpecificDirective directive) { - var directiveName = '' - for (grammarElementReference : directive.grammarElements) { - if(grammarElementReference.assignment !== null) { - directiveName = directiveName + grammarElementReference.assignment.gaElementAccessMethodName.replaceFirst("get","").replaceFirst("(?s)(.*)" + "Assignment","$1" + "") - } - if(grammarElementReference.ruleCall !== null) { - directiveName = directiveName + grammarElementReference.ruleCall.rule.name.toFirstUpper - } - if(grammarElementReference.rule !== null) { - directiveName = directiveName + grammarElementReference.rule.name.toFirstUpper - } - if(grammarElementReference.keyword !== null) { - directiveName = directiveName + grammarElementReference.keyword.value.convertNonAlphaNumeric.toFirstUpper - } - if(grammarElementReference.self !== null) { - directiveName = directiveName + "Self" - } - } - directiveName - } - def dispatch String getDirectiveName(ContextFreeDirective directive) { - var directiveName = '' - for (grammarElementLookup : directive.grammarElements) { - if(grammarElementLookup.rule !== null) { - directiveName = directiveName + grammarElementLookup.rule.name.toFirstUpper - } - if(grammarElementLookup.keyword !== null) { - directiveName = directiveName + grammarElementLookup.keyword.convertNonAlphaNumeric.toFirstUpper - } - } - directiveName - } - def dispatch String getDirectiveName(KeywordPair directive) { - directive.left.convertNonAlphaNumeric + directive.right.convertNonAlphaNumeric - } - def dispatch String getDirectiveName(EObject directive) { - String.valueOf(directive.hashCode) - } - - def Iterable createRule(FormatConfiguration format, GrammarRule rule) { - val List members = newArrayList - members += format.toMethod('config' + rule.targetRule.name, typeRef('void')) [ - final = false - visibility = JvmVisibility::PROTECTED - parameters += format.toParameter(PARAMETER_CONFIG, typeRef(BASE_FORMAT_CONFIG)) - switch rule.targetRule { - ParserRule: { - val ruleName = (getGrammar(rule.targetRule).getFullyQualifiedName + "$" + rule.targetRule.gaRuleAccessorClassName) - parameters += format.toParameter(PARAMETER_ELEMENTS, ruleName.getTypeForName(rule.targetRule)) - documentation = generateJavaDoc('''Configuration for «rule.targetRule.name».''', newLinkedHashMap( - PARAMETER_CONFIG -> 'the format configuration', - PARAMETER_ELEMENTS -> '''the grammar access for «rule.targetRule.name» elements''' - )) - } - EnumRule: { - parameters += format.toParameter(PARAMETER_RULE, EnumRule.name.getTypeForName(rule.targetRule)) - documentation = generateJavaDoc('''Configuration for «rule.targetRule.name».''', newLinkedHashMap( - PARAMETER_CONFIG -> 'the format configuration', - PARAMETER_RULE -> '''the enum rule for «rule.targetRule.name»''' - )) - } - TerminalRule: { - parameters += format.toParameter(PARAMETER_RULE, TerminalRule.name.getTypeForName(rule.targetRule)) - documentation = generateJavaDoc('''Configuration for «rule.targetRule.name».''', newLinkedHashMap( - PARAMETER_CONFIG -> 'the format configuration', - PARAMETER_RULE -> '''the terminal rule for «rule.targetRule.name»''' - )) - } - } - body = [ - val directives = rule.directives.map(d|directive(d, rule.getRuleName).toString) - append(fixLastLine(directives.join)) - ] - ] - return members; - } - - def fixLastLine(String content) { - if(content.endsWith("\r\n")) { - return content.substring(0, content.length - 2) - } else { - return content - } - } - - // directive dispatch - def dispatch directive(SpecificDirective dir, String partialName) { matchReference(dir.matcherList, dir.grammarElements, partialName + getDirectiveName(dir)) } - def dispatch directive(ContextFreeDirective dir, String partialName) { matchLookup(dir.matcherList, dir.grammarElements, partialName + getDirectiveName(dir)) } - def dispatch CharSequence directive(GroupBlock dir, String partialName) { - if(dir.matcherList !== null) { - matchReference(dir.matcherList, new BasicEList(#[dir.grammarElement]), partialName + getDirectiveName(dir)) - } else if (dir.subGroup !== null) { - directive(dir.subGroup, partialName+ getDirectiveName(dir)) - } else { - '''«FOR d : dir.directives»«directive(d, partialName + getDirectiveName(dir))»«ENDFOR»''' - } - } - def dispatch directive(KeywordPair dir, String partialName) ''' - // «locatorString(dir)» - for (final org.eclipse.xtext.util.Pair pair : elements.findKeywordPairs("«dir.left»", "«dir.right»")) { - «FOR matcher : dir.leftMatchers» - «matchLookupPartial(matcher.locator, matcher, "pair.getFirst()", partialName + getDirectiveName(dir))» - «ENDFOR» - «FOR matcher : dir.rightMatchers» - «matchLookupPartial(matcher.locator, matcher, "pair.getSecond()", partialName + getDirectiveName(dir))» - «ENDFOR» - } - ''' - def dispatch directive(Object dir, String partialName) { - throw new UnsupportedOperationException("Unknown directive " + dir.class.name) - } - - def matchLookup(MatcherList matcherList, EList elements, String partialName) ''' - «IF !elements.isEmpty» - «IF !elements.filter[e|e.rule !== null].isEmpty» - // «locatorString(matcherList)» - for (org.eclipse.xtext.RuleCall ruleCall : elements.findRuleCalls(«FOR element : elements.filter(e|e.rule !== null) SEPARATOR ', '»elements.«element.rule.gaRuleAccessor»«ENDFOR»)) { - «FOR matcher : matcherList.matchers» - «matchLookupPartial(matcher.locator, matcher, "ruleCall", partialName)» - «ENDFOR» - } - «ENDIF» - «IF !elements.filter(e|e.keyword !== null).isEmpty» - // «locatorString(matcherList)» - for (org.eclipse.xtext.Keyword keyword : elements.findKeywords(«FOR element : elements.filter(e|e.keyword !== null) SEPARATOR ', '»"«element.keyword»"«ENDFOR»)) { - «FOR matcher : matcherList.matchers» - «matchLookupPartial(matcher.locator, matcher, "keyword", partialName)» - «ENDFOR» - } - «ENDIF» - «ENDIF» - ''' - - // matchLookupPartial dispatch - def dispatch matchLookupPartial(ColumnLocator columnLocator, Matcher matcher, String eobjectTypeName, String partialName) ''' - «IF matcher.type.literal.compareTo("before") == 0» - config.setColumn(«columnLocator.value.getValueOrConstant», «columnLocator.fixed», «columnLocator.relative», «columnLocator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).before(«eobjectTypeName»); // «locatorString(columnLocator)» - config.setColumn(«columnLocator.value.getValueOrConstant», «columnLocator.fixed», «columnLocator.relative», «columnLocator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).after(«eobjectTypeName»); // «locatorString(columnLocator)» - «ELSE» - config.setColumn(«columnLocator.value.getValueOrConstant»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).«matcherType(matcher.type)»(«eobjectTypeName»); // «locatorString(columnLocator)» - «ENDIF» - ''' - def dispatch matchLookupPartial(OffsetLocator offsetLocator, Matcher matcher, String eobjectTypeName, String partialName) ''' - «IF matcher.type.literal.compareTo("before") == 0» - config.setColumn(«offsetLocator.value.getValueOrConstant», «offsetLocator.fixed», true, «offsetLocator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).before(«eobjectTypeName»); // «locatorString(offsetLocator)» - config.setColumn(«offsetLocator.value.getValueOrConstant», «offsetLocator.fixed», true, «offsetLocator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).after(«eobjectTypeName»); // «locatorString(offsetLocator)» - «ELSE» - config.setOffset(«offsetLocator.value.getValueOrConstant»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).«matcherType(matcher.type)»(«eobjectTypeName»); // «locatorString(offsetLocator)» - «ENDIF» - ''' - def dispatch matchLookupPartial(EObject locator, Matcher matcher, String eobjectTypeName, String partialName) ''' - config.«locator(matcher, matcher.locator, partialName)».«matcherType(matcher.type)»(«eobjectTypeName»);''' - - def matchReference(MatcherList matcherList, EList elements, String partialName) ''' - «IF !elements.isEmpty» - «FOR matcher : matcherList.matchers» - «IF matcher.type.isTwoArgumentMatcherType» - «match(matcher, elements.get(0), elements.get(1), matcher.locator, partialName)» - «ELSE» - «FOR e : elements»«match(matcher, e, matcher.locator, partialName)»«ENDFOR» - «ENDIF» - «ENDFOR» - «ENDIF» - ''' - - def match(Matcher matcher, EObject element1, EObject element2, EObject locator, String partialName) ''' - config.«locator(matcher, matcher.locator, partialName)».«matcherType(matcher.type)»(«elementAccess(element1)», «elementAccess(element2)»); // «locatorString(matcher)» - ''' - - // match dispatch - def dispatch match(Matcher matcher, EObject element, Locator locator, String partialName) ''' - config.«locator(matcher, matcher.locator, partialName)».«matcherType(matcher.type)»(«elementAccess(element)»); // «locatorString(matcher)» - ''' - def dispatch match(Matcher matcher, EObject element, NoFormatLocator locator, String partialName) ''' - config.«locator(matcher, matcher.locator, partialName)».«matcherType(matcher.type)»(«elementAccess(element)»); // «locatorString(matcher)» - ''' - def dispatch match(Matcher matcher, EObject element, ColumnLocator locator, String partialName) ''' - «IF matcher.type.literal.compareTo("before") == 0» - «IF locator.parameter !== null» - config.setColumn(«locator.fixed», «locator.relative», «locator.nobreak», new «getParameterCalculatorName(partialName, matcher)»()«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).before(«elementAccess(element)»); // «locatorString(matcher)» - config.setColumn(«locator.fixed», «locator.relative», «locator.nobreak», new «getParameterCalculatorName(partialName, matcher)»()«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).after(«elementAccess(element)»); // «locatorString(matcher)» - «ELSE» - config.setColumn(«locator.value.getValueOrConstant», «locator.fixed», «locator.relative», «locator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).before(«elementAccess(element)»); // «locatorString(matcher)» - config.setColumn(«locator.value.getValueOrConstant», «locator.fixed», «locator.relative», «locator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).after(«elementAccess(element)»); // «locatorString(matcher)» - «ENDIF» - «ELSE» - «IF locator.parameter !== null» - config.setColumn(new «getParameterCalculatorName(partialName, matcher)»()«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).«matcherType(matcher.type)»(«elementAccess(element)»); // «locatorString(matcher)» - «ELSE» - config.setColumn(«locator.value.getValueOrConstant»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).«matcherType(matcher.type)»(«elementAccess(element)»); // «locatorString(matcher)» - «ENDIF» - «ENDIF» - ''' - def dispatch match(Matcher matcher, EObject element, OffsetLocator locator, String partialName) ''' - «IF matcher.type.literal.compareTo("before") == 0» - config.setColumn(«locator.value.getValueOrConstant», «locator.fixed», true, «locator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).before(«elementAccess(element)»); // «locatorString(matcher)» - config.setColumn(«locator.value.getValueOrConstant», «locator.fixed», true, «locator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).after(«elementAccess(element)»); // «locatorString(matcher)» - «ELSE» - config.setOffset(«locator.value.getValueOrConstant»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»).«matcherType(matcher.type)»(«elementAccess(element)»); // «locatorString(matcher)» - «ENDIF» - ''' - def dispatch match(Matcher matcher, EObject element, IndentLocator locator, String partialName) ''' - config.«locator(matcher, matcher.locator, partialName)».«matcherType(matcher.type)»(«elementAccess(element)»); // «locatorString(matcher)» - ''' - - def matcherType(MatcherType matcherType) { matcherType.literal } - - // elementAccess dispatch - def dispatch elementAccess(GrammarElementLookup grammarElementLookup) ''' - «IF grammarElementLookup.rule !== null»elements.findRuleCalls(«grammarElementLookup.rule.gaElementsAccessor»)«ELSEIF grammarElementLookup.keyword !== null»elements.findKeywords("«grammarElementLookup.keyword»")«ENDIF»''' - def dispatch CharSequence elementAccess(GrammarElementReference grammarElementReference) { - if(grammarElementReference.ruleCall !== null) { - elementAccess(grammarElementReference.ruleCall) - } - else if(grammarElementReference.keyword !== null) { - elementAccess(grammarElementReference.keyword) - } - else if(grammarElementReference.assignment !== null) { - elementAccess(grammarElementReference.assignment) - } - else if(grammarElementReference.self !== null) { - if(grammarElementReference.containedByParserRule) { - 'elements.getRule()' - } - else { - 'rule' - } - } else if(grammarElementReference.rule !== null) { - elementAccess(grammarElementReference.rule) - } - } - - def dispatch elementAccess(AbstractRule abstractRule) ''' - getGrammarAccess().«abstractRule.gaRuleAccessor»''' - def dispatch elementAccess(AbstractElement abstractElement) ''' - elements.«abstractElement.gaElementAccessor»''' - def dispatch elementAccess(Object object) { - throw new UnsupportedOperationException("Unknown Xtext element " + object.class.name) - } - - // locator dispatch - def dispatch locator(Matcher matcher, SpaceLocator spaceLocator, String partialName) ''' - «IF spaceLocator.noSpace»setNoSpace(«IF matcher.condition !== null»new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)«ELSE»setSpace(«spaceLocator.value.getValueOrConstant»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)«ENDIF»''' - def dispatch locator(Matcher matcher, RightPaddingLocator rightPaddingLocator, String partialName) ''' - setRightPadding(«rightPaddingLocator.value.getValueOrConstant»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)''' - def dispatch locator(Matcher matcher, LinewrapLocator linewrapLocator, String partialName) ''' - «IF linewrapLocator.noLinewrap»setNoLinewrap(«IF matcher.condition !== null»new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)«ELSE»setLinewrap(«IF linewrapLocator.value !== null»«linewrapLocator.value.getValueOrConstant»«IF matcher.condition !== null», new «getLocatorActivatorName( - partialName, matcher)»()«ENDIF»«ELSEIF linewrapLocator.minimum !== null»«linewrapLocator.minimum.getValueOrConstant», «linewrapLocator.^default.getValueOrConstant», «linewrapLocator.maximum.getValueOrConstant()»«IF matcher.condition!==null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»«ELSE»«IF matcher. - condition !== null»new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»«ENDIF»)«ENDIF»''' - def dispatch locator(Matcher matcher, ColumnLocator columnLocator, String partialName) ''' - setColumn(«columnLocator.value.getValueOrConstant», «columnLocator.fixed», «columnLocator.relative», «columnLocator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)''' - def dispatch locator(Matcher matcher, OffsetLocator offsetLocator, String partialName) ''' - setColumn(«offsetLocator.value.getValueOrConstant», «offsetLocator.fixed», true, «offsetLocator.nobreak»«IF matcher.condition !== null», new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)''' - def dispatch locator(Matcher matcher, IndentLocator indentLocator, String partialName) ''' - «IF indentLocator.increment»setIndentationIncrement(«ELSE»setIndentationDecrement(« - ENDIF»« - IF indentLocator.value !== null && (indentLocator.value.reference !== null || indentLocator.value.literal >= 1)»«indentLocator.value.getValueOrConstant»« - ELSEIF indentLocator.parameter !== null»new «getParameterCalculatorName(partialName, matcher)»()« - ENDIF»« - IF matcher.condition !== null»«IF indentLocator.value !== null || indentLocator.parameter !== null»,«ENDIF» new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)''' - - def dispatch locator(Matcher matcher, NoFormatLocator noFormatLocator, String partialName) ''' - setNoFormat(«IF matcher.condition !== null»new «getLocatorActivatorName(partialName, matcher)»()«ENDIF»)''' - def dispatch locator(Matcher matcher, Locator locator, String partialName) { - throw new UnsupportedOperationException("Unknown locator " + locator.class.name) - } - - // getValueOrConstant dispatch - def dispatch getValueOrConstant(StringValue stringValue) { - if(stringValue.literal === null) { - stringValue.reference.name - } else { - '"' + stringValue.literal + '"' - } - } - def dispatch getValueOrConstant(IntValue intValue) { - if(intValue.literal === null) { - intValue.reference.name - } else { - intValue.literal.toString() - } - } - - def locatorString(EObject object) { - getFileLocation(object).split('/').lastOrNull() - } - -} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/scoping/FormatScopeProvider.java b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/scoping/FormatScopeProvider.java new file mode 100644 index 0000000000..076bc85d47 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/scoping/FormatScopeProvider.java @@ -0,0 +1,344 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.format.scoping; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.CompoundElement; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.EObjectDescription; +import org.eclipse.xtext.resource.IEObjectDescription; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.impl.MapBasedScope; +import org.eclipse.xtext.scoping.impl.SimpleScope; + +import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration; +import com.avaloq.tools.ddk.xtext.format.format.FormatPackage; +import com.avaloq.tools.ddk.xtext.format.format.GrammarElementReference; +import com.avaloq.tools.ddk.xtext.format.format.GrammarRule; +import com.avaloq.tools.ddk.xtext.format.format.GroupBlock; +import com.avaloq.tools.ddk.xtext.format.naming.FormatScopeNameProvider; +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.inject.Inject; + +/** + * The scope provider for the Format language. + */ +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class FormatScopeProvider extends AbstractFormatScopeProvider { + + @Inject + private FormatScopeUtil scopeUtil; + + @Inject + private FormatScopeNameProvider nameProvider; + + /** + * Provides a scope for given context and reference. + * If there is no specific scoping method or if there is such a method but it cannot return a scope, the super class method is called. + * + * @param context + * the context object + * @param reference + * the reference + * @return the scope for the given context and reference + */ + @Override + public IScope getScope(final EObject context, final EReference reference) { + final IScope result = scope(context, reference); + if (result != null) { + return result; + } + return super.getScope(context, reference); + } + + /** + * For a given grammar returns the grammar on which it is based, and transitively all base grammars for that grammar. + * + * @param context + * the grammar + * @return all used grammars including the given one + */ + public Iterable getUsedGrammar(final Grammar context) { + final List grammars = new LinkedList<>(); + grammars.add(context); + final EList usedGrammars = EcoreUtil2.getContainerOfType(context, Grammar.class).getUsedGrammars(); + if (usedGrammars != null && !usedGrammars.isEmpty()) { + Iterables.addAll(grammars, Iterables.concat(Iterables.transform(usedGrammars, (Grammar g) -> getUsedGrammar(g)))); + } + return grammars; + } + + /** + * For a given formatter returns the grammar on which it is based, and transitively all base grammars for that grammar. + * + * @param context + * the context object + * @return the collection of grammars + */ + public Collection getGrammars(final EObject context) { + final List grammars = new LinkedList<>(); + final FormatConfiguration format = EcoreUtil2.getContainerOfType(context, FormatConfiguration.class); + if (format != null && format.getTargetGrammar() != null) { + grammars.add(format.getTargetGrammar()); + Iterables.addAll(grammars, getUsedGrammar(format.getTargetGrammar())); + } + return grammars; + } + + /** + * For a given {@link FormatConfiguration} returns transitively all extending format configurations. + * Usage of LinkedList for {@code formats} does not prevent against duplication of grammars, but a HashSet cannot be used here as there won't be possible to recover the overriding order. + * + * @param context + * the format configuration + * @return all format configurations including the given one and its extensions + */ + public Collection getFormats(final FormatConfiguration context) { + final List formats = new LinkedList<>(); + final FormatConfiguration format = context; + if (format != null) { + formats.add(format); + if (format.getExtendedFormatConfiguration() != null) { + formats.addAll(getFormats(format.getExtendedFormatConfiguration())); + } + } + return formats; + } + + /** + * In order to ensure the correct path of inheritance/overriding the list of the grammars has to be reversed. + * + * @param context + * the context object + * @return the list of grammars in hierarchy order + */ + public List getHierarchyOrderedGrammars(final EObject context) { + return Lists.reverse(Lists.newArrayList(getGrammars(context))); + } + + /** + * In order to ensure the correct path of inheritance/overriding the list of the format configurations has to be reversed. + * + * @param context + * the context object + * @return the list of format configurations in hierarchy order + */ + public List getHierarchyOrderedFormats(final EObject context) { + return Lists.reverse(Lists.newArrayList(getFormats(EcoreUtil2.getContainerOfType(context, FormatConfiguration.class)))); + } + + /** + * Creates scopes for a given list of rules for each grammar. Returned scopes are chained (parental relationships). + * + * @param parent + * the parent scope, may be {@code null} + * @param rulesForGrammars + * the rules for each grammar + * @return the chained scope + */ + public IScope createScopeForAbstractRules(final IScope parent, final Iterable> rulesForGrammars) { + if (parent == null) { + return createScopeForAbstractRules( + MapBasedScope.createScope(IScope.NULLSCOPE, createDescriptions(Iterables.getFirst(rulesForGrammars, null))), + Iterables.skip(rulesForGrammars, 1)); + } else { + if (Iterables.isEmpty(rulesForGrammars)) { + return parent; + } else { + return createScopeForAbstractRules( + MapBasedScope.createScope(parent, createDescriptions(Iterables.getFirst(rulesForGrammars, null))), + Iterables.skip(rulesForGrammars, 1)); + } + } + } + + /** + * Creates a scope for a given list of elements. + * + * @param + * the element type + * @param elements + * the list of elements + * @return the scope + */ + public IScope createScopeForEObjects(final List elements) { + return MapBasedScope.createScope(IScope.NULLSCOPE, createDescriptions(elements)); + } + + /** + * Creates a simple scope for a given object description. + * + * @param description + * the object description + * @return the simple scope + */ + public SimpleScope createSimpleScope(final IEObjectDescription description) { + return new SimpleScope(IScope.NULLSCOPE, ImmutableList.of(description)); + } + + /** + * Creates a scope for a given list of compound elements. + * + * @param compoundElements + * the list of compound elements + * @return the scope + */ + public IScope createScopeForCompoundElements(final List compoundElements) { + return MapBasedScope.createScope(IScope.NULLSCOPE, createDescriptionsForCompoundElements(compoundElements)); + } + + protected IScope _scope(final GrammarRule context, final EReference reference) { + if (reference == FormatPackage.Literals.GRAMMAR_RULE__TARGET_RULE) { + final List grammars = getHierarchyOrderedGrammars(context); + final Iterable> rulesForGrammars = Iterables.transform(grammars, (Grammar g) -> g.getRules()); + return createScopeForAbstractRules(null, rulesForGrammars); + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__ASSIGNMENT) { + return createScopeForEObjects(scopeUtil.getAssignments(context.getTargetRule())); + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__KEYWORD) { + return createScopeForEObjects(scopeUtil.getKeywords(context.getTargetRule())); + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__RULE_CALL) { + return createScopeForEObjects(scopeUtil.getRuleCalls(context.getTargetRule())); + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__SELF) { + final IEObjectDescription selfDescription = EObjectDescription.create( + nameProvider.getConstantNameFunction("rule").apply(context.getTargetRule()), context.getTargetRule()); + return createSimpleScope(selfDescription); + } + return IScope.NULLSCOPE; + } + + protected IScope _scope(final GroupBlock context, final EReference reference) { + if (reference == FormatPackage.Literals.GROUP_BLOCK__GRAMMAR_ELEMENT) { + final GrammarRule grammarRule = EcoreUtil2.getContainerOfType(context, GrammarRule.class); + final GroupBlock superGroup = EcoreUtil2.getContainerOfType(context.eContainer(), GroupBlock.class); + if (superGroup == null) { + return createScopeForCompoundElements(scopeUtil.getCompoundElements(grammarRule.getTargetRule(), CompoundElement.class)); + } else { + return createScopeForCompoundElements(scopeUtil.getCompoundElements(superGroup.getGrammarElement(), CompoundElement.class)); + } + } + return IScope.NULLSCOPE; + } + + protected IScope _scope(final GrammarElementReference context, final EReference reference) { + final GrammarRule grammarRule = EcoreUtil2.getContainerOfType(context, GrammarRule.class); + final GroupBlock groupBlock = EcoreUtil2.getContainerOfType(context, GroupBlock.class); + if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__KEYWORD) { + if (groupBlock != null) { + return createScopeForEObjects(scopeUtil.getKeywords(groupBlock.getGrammarElement())); + } + return createScopeForEObjects(scopeUtil.getKeywords(grammarRule.getTargetRule())); + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__ASSIGNMENT) { + if (groupBlock != null) { + return createScopeForEObjects(scopeUtil.getAssignments(groupBlock.getGrammarElement())); + } + return createScopeForEObjects(scopeUtil.getAssignments(grammarRule.getTargetRule())); + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__RULE_CALL) { + if (groupBlock != null) { + return createScopeForEObjects(scopeUtil.getRuleCalls(groupBlock.getGrammarElement())); + } + return createScopeForEObjects(scopeUtil.getRuleCalls(grammarRule.getTargetRule())); + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__SELF) { + if (groupBlock != null) { + final IEObjectDescription selfDescription = EObjectDescription.create( + nameProvider.getConstantNameFunction("rule").apply(groupBlock.getGrammarElement()), + groupBlock.getGrammarElement()); + return createSimpleScope(selfDescription); + } else { + final IEObjectDescription selfDescription = EObjectDescription.create( + nameProvider.getConstantNameFunction("rule").apply(grammarRule.getTargetRule()), grammarRule.getTargetRule()); + return createSimpleScope(selfDescription); + } + } else if (reference == FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__RULE) { + final Iterable> rulesForGrammars = Iterables.transform(getHierarchyOrderedGrammars(grammarRule), (Grammar c) -> c.getRules()); + return createScopeForAbstractRules(null, rulesForGrammars); + } + return IScope.NULLSCOPE; + } + + // default implementation will throw an illegal argument exception + protected IScope _scope(final EObject context, final EReference reference) { + return null; + } + + /** + * Dispatch method for scope resolution based on runtime type of context. + * + * @param context + * the context object + * @param reference + * the reference + * @return the scope, or {@code null} if no specific scope applies + * @throws IllegalArgumentException if the context type is not handled + */ + public IScope scope(final EObject context, final EReference reference) { + if (context instanceof GrammarRule grammarRule) { + return _scope(grammarRule, reference); + } else if (context instanceof GrammarElementReference grammarElementReference) { + return _scope(grammarElementReference, reference); + } else if (context instanceof GroupBlock groupBlock) { + return _scope(groupBlock, reference); + } else if (context != null) { + return _scope(context, reference); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(context, reference).toString()); + } + } + + /** + * Creates object descriptions for a given list of {@link FormatConfiguration}. + * + * @param elements + * the list of format configurations + * @return the collection of object descriptions + */ + public Collection createDescriptionsFormats(final List elements) { + return Collections2.transform(elements, (FormatConfiguration e) -> EObjectDescription.create(scopeUtil.findTargetGrammar(e).getName(), e)); + } + + /** + * Creates descriptions for a given list of objects. + * + * @param elements + * the list of objects + * @return the collection of object descriptions + */ + public Collection createDescriptions(final List elements) { + final Function namingFunction = nameProvider.getIndexParameterNameFunction(elements); + return Collections2.transform(elements, (EObject e) -> EObjectDescription.create(namingFunction.apply(e), e)); + } + + /** + * Creates object descriptions for a list of CompundElements ({@code group X} items). Groups are referenced using numbers which are used in formatter and at the same time corresponds to indexes in the list. + * + * @param elements + * the list of compound elements + * @return the collection of object descriptions + */ + public Collection createDescriptionsForCompoundElements(final List elements) { + final Function namingFunction = nameProvider.getIndexNameFunction(elements); + return Collections2.transform(elements, (CompoundElement e) -> EObjectDescription.create(namingFunction.apply(e), e)); + } +} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/scoping/FormatScopeProvider.xtend b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/scoping/FormatScopeProvider.xtend deleted file mode 100644 index fe5270c5ce..0000000000 --- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/scoping/FormatScopeProvider.xtend +++ /dev/null @@ -1,258 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.format.scoping - -import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration -import com.avaloq.tools.ddk.xtext.format.format.FormatPackage -import com.avaloq.tools.ddk.xtext.format.format.GrammarElementReference -import com.avaloq.tools.ddk.xtext.format.format.GrammarRule -import com.avaloq.tools.ddk.xtext.format.format.GroupBlock -import com.avaloq.tools.ddk.xtext.format.naming.FormatScopeNameProvider -import com.google.common.collect.Collections2 -import com.google.common.collect.ImmutableList -import com.google.common.collect.Iterables -import com.google.common.collect.Lists -import com.google.inject.Inject -import java.util.Collection -import java.util.LinkedList -import java.util.List -import org.eclipse.emf.common.util.EList -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EReference -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.CompoundElement -import org.eclipse.xtext.EcoreUtil2 -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.resource.EObjectDescription -import org.eclipse.xtext.resource.IEObjectDescription -import org.eclipse.xtext.scoping.IScope -import org.eclipse.xtext.scoping.impl.MapBasedScope -import org.eclipse.xtext.scoping.impl.SimpleScope - -/** - * The scope provider for the Format language. - */ -class FormatScopeProvider extends AbstractFormatScopeProvider { - - @Inject - FormatScopeUtil scopeUtil; - @Inject - FormatScopeNameProvider nameProvider; - - /** - * Provides a scope for given context and reference. - * If there is no specific scoping method or if there is such a method but it cannot return a scope, the super class method is called. - */ - override IScope getScope(EObject context, EReference reference) { - val result = scope(context, reference) - if (result !== null) { - return result - } - return super.getScope(context, reference) - } - - /** - * For a given grammar returns the grammar on which it is based, and transitively all base grammars for that grammar. - * - */ - def Iterable getUsedGrammar(Grammar context){ - val LinkedList grammars = newLinkedList() - grammars.add(context) - val usedGrammars = EcoreUtil2::getContainerOfType(context, typeof(Grammar)).usedGrammars - if (usedGrammars!==null && !usedGrammars.empty){ - grammars.addAll( Iterables::concat( Iterables::transform( usedGrammars, [g|getUsedGrammar(g)] ))) - } - return grammars - } - - /** - * For a given formatter returns the grammar on which it is based, and transitively all base grammars for that grammar. - * - */ - def Collection getGrammars(EObject context) { - val LinkedList grammars = newLinkedList() - val format = EcoreUtil2::getContainerOfType(context, typeof(FormatConfiguration)) - if (format !== null && format.targetGrammar !== null) { - grammars.add(format.targetGrammar); - grammars.addAll(getUsedGrammar(format.targetGrammar)) - } - return grammars - } - - /** - * For a given {@link FormatConfiguration} returns transitively all extending format configurations. - * Usage of LinkedList for {@code formats} does not prevent against duplication of grammars, but a HashSet cannot be used here as there won't be possible to recover the overriding order. - */ - def Collection getFormats(FormatConfiguration context) { - val formats = newLinkedList() - val format = context - if (format !== null) { - formats.add(format); - if (format.extendedFormatConfiguration !== null) { - formats.addAll(getFormats(format.extendedFormatConfiguration)) - } - } - return formats - } - - /** - * In order to ensure the correct path of inheritance/overriding the list of the grammars has to be reversed. - */ - def List getHierarchyOrderedGrammars(EObject context) { - return Lists.newArrayList(getGrammars(context)).reverse() - } - - /** - * In order to ensure the correct path of inheritance/overriding the list of the format configurations has to be reversed. - */ - def List getHierarchyOrderedFormats(EObject context) { - return Lists.newArrayList(getFormats(EcoreUtil2::getContainerOfType(context, typeof(FormatConfiguration)))). - reverse() - } - - /** - * Creates scopes for a given list of rules for each grammar. Returned scopes are chained (parental relationships). - */ - def IScope createScopeForAbstractRules(IScope parent, Iterable> rulesForGrammars) { - if (parent === null) { - return createScopeForAbstractRules( - MapBasedScope::createScope(IScope::NULLSCOPE, createDescriptions(rulesForGrammars.head)), - rulesForGrammars.tail); - } else { - if (rulesForGrammars.empty) { - return parent; - } else { - return createScopeForAbstractRules( - MapBasedScope::createScope(parent, createDescriptions(rulesForGrammars.head)), rulesForGrammars.tail); - } - } - } - - /** - * Creates a scope for a given list of elements. - */ - def IScope createScopeForEObjects(List elements) { - return MapBasedScope::createScope(IScope::NULLSCOPE, createDescriptions(elements)); - } - - /** - * Creates a simple scope for a given object description. - */ - def createSimpleScope(IEObjectDescription description) { - return new SimpleScope(IScope::NULLSCOPE, ImmutableList::of(description)); - } - - /** - * Creates a scope for a given list of compound elements. - */ - def IScope createScopeForCompoundElements(List compoundElements){ - return MapBasedScope::createScope(IScope::NULLSCOPE, createDescriptionsForCompoundElements(compoundElements)) - } - - - def dispatch IScope scope(GrammarRule context, EReference reference) { - if (reference == FormatPackage.Literals::GRAMMAR_RULE__TARGET_RULE) { - val grammars = getHierarchyOrderedGrammars(context) - val rulesForGrammars = Iterables::transform(grammars, [g|g.rules]) - return createScopeForAbstractRules(null, rulesForGrammars); - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__ASSIGNMENT) { - return createScopeForEObjects(scopeUtil.getAssignments(context.targetRule)); - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__KEYWORD) { - return createScopeForEObjects(scopeUtil.getKeywords(context.targetRule)); - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__RULE_CALL) { - return createScopeForEObjects(scopeUtil.getRuleCalls(context.targetRule)); - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__SELF) { - val selfDescription = EObjectDescription::create( - nameProvider.getConstantNameFunction("rule").apply(context.targetRule), context.targetRule); - return createSimpleScope(selfDescription) - } - return IScope::NULLSCOPE; - } - - def dispatch IScope scope(GroupBlock context, EReference reference) { - if (reference == FormatPackage.Literals::GROUP_BLOCK__GRAMMAR_ELEMENT) { - val grammarRule = EcoreUtil2::getContainerOfType(context, typeof(GrammarRule)) - val superGroup = EcoreUtil2::getContainerOfType(context.eContainer(), typeof(GroupBlock)) - if (superGroup === null){ - return createScopeForCompoundElements(scopeUtil.getCompoundElements(grammarRule.targetRule, typeof(CompoundElement))) - } - else{ - return createScopeForCompoundElements(scopeUtil.getCompoundElements(superGroup.grammarElement, typeof(CompoundElement))) - } - } - return IScope::NULLSCOPE; - } - - def dispatch IScope scope(GrammarElementReference context, EReference reference) { - val grammarRule = EcoreUtil2::getContainerOfType(context, typeof(GrammarRule)) - val groupBlock = EcoreUtil2::getContainerOfType(context, typeof(GroupBlock)) - if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__KEYWORD) { - if (groupBlock !== null) { - return createScopeForEObjects(scopeUtil.getKeywords(groupBlock.grammarElement)); - } - return createScopeForEObjects(scopeUtil.getKeywords(grammarRule.targetRule)); - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__ASSIGNMENT) { - if (groupBlock !== null) { - return createScopeForEObjects(scopeUtil.getAssignments(groupBlock.grammarElement)); - } - return createScopeForEObjects(scopeUtil.getAssignments(grammarRule.targetRule)); - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__RULE_CALL) { - if (groupBlock !== null) { - return createScopeForEObjects(scopeUtil.getRuleCalls(groupBlock.grammarElement)); - } - return createScopeForEObjects(scopeUtil.getRuleCalls(grammarRule.targetRule)); - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__SELF) { - if (groupBlock !== null) { - val selfDescription = EObjectDescription::create( - nameProvider.getConstantNameFunction("rule").apply(groupBlock.grammarElement), - groupBlock.grammarElement) - return createSimpleScope(selfDescription) - } else { - val selfDescription = EObjectDescription::create( - nameProvider.getConstantNameFunction("rule").apply(grammarRule.targetRule), grammarRule.targetRule); - return createSimpleScope(selfDescription) - } - } else if (reference == FormatPackage.Literals::GRAMMAR_ELEMENT_REFERENCE__RULE) { - val rulesForGrammars = Iterables::transform(getHierarchyOrderedGrammars(grammarRule), [c|c.rules]) - return createScopeForAbstractRules(null, rulesForGrammars); - } - return IScope::NULLSCOPE; - } - - // default implementation will throw an illegal argument exception - def dispatch IScope scope(EObject context, EReference reference) { - return null - } - - /** - * Creates object descriptions for a given list of {@link FormatConfiguration}. - */ - def Collection createDescriptionsFormats(List elements) { - Collections2::transform(elements, [e|EObjectDescription::create(scopeUtil.findTargetGrammar(e).name, e)]) - } - - /** - * Creates descriptions for a given list of objects. - */ - def Collection createDescriptions(List elements) { - val namingFunction = nameProvider.getIndexParameterNameFunction(elements) - Collections2::transform(elements, [e|EObjectDescription::create(namingFunction.apply(e), e)]) - } - - /** - * Creates object descriptions for a list of CompundElements ({@code group X} items). Groups are referenced using numbers which are used in formatter and at the same time corresponds to indexes in the list. - */ - def Collection createDescriptionsForCompoundElements(List elements) { - val namingFunction = nameProvider.getIndexNameFunction(elements) - Collections2::transform(elements, [e|EObjectDescription::create(namingFunction.apply(e), e)]) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/validation/FormatValidator.java b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/validation/FormatValidator.java new file mode 100644 index 0000000000..fbaec9518c --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/validation/FormatValidator.java @@ -0,0 +1,396 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.format.validation; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.osgi.util.NLS; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.EnumRule; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.validation.Check; + +import com.avaloq.tools.ddk.xtext.format.format.Constant; +import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration; +import com.avaloq.tools.ddk.xtext.format.format.FormatPackage; +import com.avaloq.tools.ddk.xtext.format.format.GrammarElementLookup; +import com.avaloq.tools.ddk.xtext.format.format.GrammarElementReference; +import com.avaloq.tools.ddk.xtext.format.format.GrammarRule; +import com.avaloq.tools.ddk.xtext.format.format.IntValue; +import com.avaloq.tools.ddk.xtext.format.format.KeywordPair; +import com.avaloq.tools.ddk.xtext.format.format.Matcher; +import com.avaloq.tools.ddk.xtext.format.format.MatcherType; +import com.avaloq.tools.ddk.xtext.format.format.Rule; +import com.avaloq.tools.ddk.xtext.format.format.SpecificDirective; +import com.avaloq.tools.ddk.xtext.format.format.StringValue; +import com.avaloq.tools.ddk.xtext.format.format.WildcardRule; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + + +/** + * This class contains custom validation rules. + * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation + */ +@SuppressWarnings("nls") +public class FormatValidator extends AbstractFormatValidator { + + /** + * Validation code for illegal format directives for Terminal or Datatype rules. + */ + public static final String ILLEGAL_DIRECTIVE_CODE = "com.avaloq.tools.ddk.xtext.format.validation.IllegalDirective"; + + /** + * Validation code for override missing. + */ + public static final String OVERRIDE_MISSING_CODE = "com.avaloq.tools.ddk.xtext.format.validation.MissingOverride"; + + /** + * Validation code for illegal override. + */ + public static final String OVERRIDE_ILLEGAL_CODE = "com.avaloq.tools.ddk.xtext.format.validation.IllegalOverride"; + + /** + * Validation code for incompatible grammar of extended format. + */ + public static final String EXTENDED_GRAMMAR_INCOMPATIBLE_CODE = "com.avaloq.tools.ddk.xtext.format.validation.ExtendedGrammarIncompatible"; + + /** + * Validation code for required grammar rule missing. + */ + public static final String GRAMMAR_RULE_MISSING_CODE = "com.avaloq.tools.ddk.xtext.format.validation.GrammarRuleMissing"; + + private static final String OVERRIDE_MISSING_MESSAGE = "Override missing"; + + private static final String OVERRIDE_ILLEGAL_MESSAGE = "Override illegal"; + + private static final Predicate IS_OVERRIDE = new Predicate() { + @Override + public boolean apply(final Rule input) { + return input.isOverride(); + } + }; + + private static final Function TARGET_RULE = new Function() { + @Override + public AbstractRule apply(final GrammarRule from) { + return from.getTargetRule(); + } + }; + + /** + * Verify that only rule self directives are used for terminal, enum and data type rules. + * + * @param model + * the GrammarRule + */ + @Check + public void checkDataTypeOrEnumRule(final GrammarRule model) { + if (model.getTargetRule() instanceof TerminalRule || model.getTargetRule() instanceof EnumRule + || (model.getTargetRule() instanceof ParserRule && GrammarUtil.isDatatypeRule((ParserRule) model.getTargetRule()))) { + final Iterator grammarElementAccessors = collectGrammarElementAccessors(model); + final boolean selfAccessOnly = Iterators.all(grammarElementAccessors, new Predicate() { + @Override + public boolean apply(final EObject input) { + return input instanceof GrammarElementReference && ((GrammarElementReference) input).getSelf() != null; + } + }); + if (!selfAccessOnly) { + error(NLS.bind("For data type, enum or terminal rule {0} only ''rule'' directive may be used", model.getTargetRule().getName()), FormatPackage.Literals.GRAMMAR_RULE__DIRECTIVES, ILLEGAL_DIRECTIVE_CODE); + } + } + } + + /** + * Verify that there are exactly two grammar elements if "between" is used. + * + * @param matcher + * the Matcher + */ + @Check + public void checkBetweenArguments(final Matcher matcher) { + checkTwoArgumentMatcherType(matcher, MatcherType.BETWEEN); + } + + /** + * Verify that there are exactly two grammar elements if "range" is used. + * + * @param matcher + * the Matcher + */ + @Check + public void checkRangeArguments(final Matcher matcher) { + checkTwoArgumentMatcherType(matcher, MatcherType.RANGE); + } + + /** + * Verify that references to other grammar rules at most occur once and that "between" or "range" is used. + * + * @param elementReference + * reference to a grammar element + */ + @Check + public void checkRuleReference(final GrammarElementReference elementReference) { + if (elementReference.getRule() == null) { + return; + } + final SpecificDirective directive = EcoreUtil2.getContainerOfType(elementReference, SpecificDirective.class); + if (directive.getMatcherList() != null) { + final boolean twoArgumentMatcherTypesOnly = Iterables.all(directive.getMatcherList().getMatchers(), new Predicate() { + @Override + public boolean apply(final Matcher input) { + return Objects.equals(input.getType(), MatcherType.BETWEEN) || Objects.equals(input.getType(), MatcherType.RANGE); + } + }); + if (!twoArgumentMatcherTypesOnly) { + error(NLS.bind("Grammar rules may only be used with \"{0}\" or \"{1}\"", MatcherType.RANGE.getName(), MatcherType.BETWEEN.getName()), FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__RULE); + } + final Iterable ruleGrammarElements = Iterables.filter(directive.getGrammarElements(), new Predicate() { + @Override + public boolean apply(final GrammarElementReference input) { + return input.getRule() != null; + } + }); + if (Iterables.size(ruleGrammarElements) != 1) { + error(NLS.bind("Only one grammar rule may be referenced with \"{0}\" and \"{1}\"", MatcherType.RANGE.getName(), MatcherType.BETWEEN.getName()), FormatPackage.Literals.GRAMMAR_ELEMENT_REFERENCE__RULE); + } + } + } + + /** + * Checks that rules declare overrides when there is a corresponding inherited rule. + * + * @param model + * the model + */ + @Check + public void checkOverrideMissing(final FormatConfiguration model) { + final FormatConfiguration extendedModel = model.getExtendedFormatConfiguration(); + if (extendedModel == null || extendedModel.eIsProxy()) { + return; + } + final Iterable nonOverrideRules = Iterables.filter(model.getRules(), Predicates.not(IS_OVERRIDE)); + final Iterable overriddenRules = collectRules(extendedModel); + final Map localAbstractRuleMap = Maps.newHashMap(); + for (final GrammarRule rule : Iterables.filter(nonOverrideRules, GrammarRule.class)) { + localAbstractRuleMap.put(TARGET_RULE.apply(rule), rule); + } + // Check GrammarRules + for (final GrammarRule overriddenRule : Iterables.filter(overriddenRules, GrammarRule.class)) { + if (localAbstractRuleMap.containsKey(TARGET_RULE.apply(overriddenRule))) { + final GrammarRule localRule = localAbstractRuleMap.get(TARGET_RULE.apply(overriddenRule)); + error(OVERRIDE_MISSING_MESSAGE, localRule, FormatPackage.Literals.GRAMMAR_RULE__TARGET_RULE, OVERRIDE_MISSING_CODE); + } + } + // Check WildcardRule + if (!Iterables.isEmpty(Iterables.filter(nonOverrideRules, WildcardRule.class)) + && !Iterables.isEmpty(Iterables.filter(overriddenRules, WildcardRule.class))) { + error(OVERRIDE_MISSING_MESSAGE, Iterables.filter(nonOverrideRules, WildcardRule.class).iterator().next(), null, OVERRIDE_MISSING_CODE); + } + } + + /** + * Checks that no rule declares override when there is no corresponding inherited rule. + * + * @param model + * the model + */ + @Check + public void checkIllegalOverride(final FormatConfiguration model) { + final Iterable overrideRules = Iterables.filter(model.getRules(), IS_OVERRIDE); + Iterable overrideableRules = Lists.newArrayList(); + final FormatConfiguration extendedModel = model.getExtendedFormatConfiguration(); + if (extendedModel != null && !extendedModel.eIsProxy()) { + overrideableRules = collectRules(extendedModel); + } + final Map overrideableAbstractRuleMap = Maps.newHashMap(); + for (final GrammarRule rule : Iterables.filter(overrideableRules, GrammarRule.class)) { + overrideableAbstractRuleMap.put(TARGET_RULE.apply(rule), rule); + } + // Check GrammarRules + for (final GrammarRule overrideRule : Iterables.filter(overrideRules, GrammarRule.class)) { + if (!overrideableAbstractRuleMap.containsKey(TARGET_RULE.apply(overrideRule))) { + error(OVERRIDE_ILLEGAL_MESSAGE, overrideRule, FormatPackage.Literals.GRAMMAR_RULE__TARGET_RULE, OVERRIDE_ILLEGAL_CODE); + } + } + // Check WildcardRule + if (!Iterables.isEmpty(Iterables.filter(overrideRules, WildcardRule.class)) && Iterables.isEmpty(Iterables.filter(overrideableRules, WildcardRule.class))) { + error(OVERRIDE_ILLEGAL_MESSAGE, Iterables.filter(overrideRules, WildcardRule.class).iterator().next(), null, OVERRIDE_ILLEGAL_CODE); + } + } + + /** + * Check that extended configuration's grammar is compatible. + * + * @param model + * the model + */ + @Check + public void checkExtendedGrammarCompatible(final FormatConfiguration model) { + final FormatConfiguration extendedModel = model.getExtendedFormatConfiguration(); + if (extendedModel == null || extendedModel.eIsProxy()) { + return; + } + if (!extendedModel.getTargetGrammar().eIsProxy()) { + final List grammars = Lists.newArrayList(model.getTargetGrammar()); + grammars.addAll(model.getTargetGrammar().getUsedGrammars()); + for (final Grammar grammar : grammars) { + if (extendedModel.getTargetGrammar().getName().equals(grammar.getName())) { + return; + } + } + } + error("Extended format configuration has incompatible grammar", FormatPackage.Literals.FORMAT_CONFIGURATION__EXTENDED_FORMAT_CONFIGURATION, EXTENDED_GRAMMAR_INCOMPATIBLE_CODE); + } + + /** + * Check that identically named parent grammar rules are implemented in the model if the parent configuration implements the rule. + * If both grammars have an identically named rule then the extending FormatConfiguration must declare formatting if the parent FormatConfiguration does. + * + * @param model + * the model + */ + @Check + public void checkRequiredRulesImplemented(final FormatConfiguration model) { + final FormatConfiguration extendedModel = model.getExtendedFormatConfiguration(); + if (extendedModel == null || extendedModel.eIsProxy() || Objects.equals(model.getTargetGrammar(), extendedModel.getTargetGrammar())) { // NOPMD + return; + } + final Iterable inheritedRules = Iterables.filter(collectRules(extendedModel), GrammarRule.class); + final Set ruleNames = Sets.newHashSet(Iterables.transform(inheritedRules, new Function() { + @Override + public String apply(final GrammarRule from) { + return from.getTargetRule().getName(); + } + })); + for (final GrammarRule rule : Iterables.filter(model.getRules(), GrammarRule.class)) { + if (rule.getTargetRule() != null && !rule.getTargetRule().eIsProxy()) { + ruleNames.remove(rule.getTargetRule().getName()); + } + } + for (final AbstractRule rule : model.getTargetGrammar().getRules()) { + if (ruleNames.contains(rule.getName())) { + error(NLS.bind("Required formatting for rule \"{0}\" missing", rule.getName()), FormatPackage.Literals.FORMAT_CONFIGURATION__TARGET_GRAMMAR, GRAMMAR_RULE_MISSING_CODE, rule.getName()); + } + } + } + + /** + * Check that referenced value is of integer type. + * + * @param value + * the value + */ + @Check + public void checkIntValueConstantReference(final IntValue value) { + if (value.getReference() == null || value.getReference().eIsProxy()) { + return; + } + if (value.getReference().getIntValue() == null) { + error(NLS.bind("Referenced const \"{0}\" is not an integer", value.getReference().getName()), FormatPackage.Literals.INT_VALUE__REFERENCE); + } + } + + /** + * Check that referenced value is of string type. + * + * @param value + * the value + */ + @Check + public void checkStringValueConstantReference(final StringValue value) { + if (value.getReference() == null || value.getReference().eIsProxy()) { + return; + } + if (value.getReference().getIntValue() == null) { + error(NLS.bind("Referenced const \"{0}\" is not a string", value.getReference().getName()), FormatPackage.Literals.STRING_VALUE__REFERENCE); + } + } + + /** + * Check that value is consistent with the (optional) declared type. + * + * @param value + * the value + */ + @Check + public void checkConstantOptionalType(final Constant value) { + if (value.isIntType() && value.getIntValue() == null) { + error("Value is not an integer", FormatPackage.Literals.CONSTANT__NAME); + } else if (value.isStringType() && value.getStringValue() == null) { + error("Value is not a string", FormatPackage.Literals.CONSTANT__NAME); + } + } + + /** + * Collect all rules contained by the given model (including any of its extended configurations). + * + * @param model + * the model + * @return all rules + */ + private Iterable collectRules(final FormatConfiguration model) { + Iterable result = model.getRules(); + final FormatConfiguration extendedModel = model.getExtendedFormatConfiguration(); + if (extendedModel != null && !extendedModel.eIsProxy()) { + result = Iterables.concat(result, collectRules(extendedModel)); + } + return result; + } + + /** + * Verify that there are exactly two grammar elements used in the containing directive if the type of the matcher equals the given MatcherType. + * + * @param matcher + * the Matcher + * @param matcherType + * the MatcherType + */ + private void checkTwoArgumentMatcherType(final Matcher matcher, final MatcherType matcherType) { + if (Objects.equals(matcher.getType(), matcherType)) { + final SpecificDirective directive = EcoreUtil2.getContainerOfType(matcher, SpecificDirective.class); + if (directive == null || directive.getGrammarElements().size() != 2) { + error(NLS.bind("\"{0}\" may only be used with exactly two elements", matcherType.getName()), FormatPackage.Literals.MATCHER__TYPE); + } + } + } + + /** + * Collects all GramarElementReferences, GrammarElementLookups and KeywordPairs contained by a GrammarRule. + * + * @param rule + * the grammar rule + * @return Iterator with all grammar element accessors + */ + private static Iterator collectGrammarElementAccessors(final GrammarRule rule) { + return Iterators.filter(rule.eAllContents(), new Predicate() { + @Override + public boolean apply(final EObject input) { + return input instanceof KeywordPair || input instanceof GrammarElementReference || input instanceof GrammarElementLookup; + } + }); + } +} diff --git a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/validation/FormatValidator.xtend b/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/validation/FormatValidator.xtend deleted file mode 100644 index bfe4c0d1ff..0000000000 --- a/com.avaloq.tools.ddk.xtext.format/src/com/avaloq/tools/ddk/xtext/format/validation/FormatValidator.xtend +++ /dev/null @@ -1,376 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.format.validation - -import com.avaloq.tools.ddk.xtext.format.format.Constant -import com.avaloq.tools.ddk.xtext.format.format.FormatConfiguration -import com.avaloq.tools.ddk.xtext.format.format.GrammarElementLookup -import com.avaloq.tools.ddk.xtext.format.format.GrammarElementReference -import com.avaloq.tools.ddk.xtext.format.format.GrammarRule -import com.avaloq.tools.ddk.xtext.format.format.IntValue -import com.avaloq.tools.ddk.xtext.format.format.KeywordPair -import com.avaloq.tools.ddk.xtext.format.format.Matcher -import com.avaloq.tools.ddk.xtext.format.format.MatcherType -import com.avaloq.tools.ddk.xtext.format.format.Rule -import com.avaloq.tools.ddk.xtext.format.format.SpecificDirective -import com.avaloq.tools.ddk.xtext.format.format.StringValue -import com.avaloq.tools.ddk.xtext.format.format.WildcardRule -import com.google.common.collect.Iterables -import com.google.common.collect.Iterators -import com.google.common.collect.Lists -import com.google.common.collect.Maps -import com.google.common.collect.Sets -import java.util.Iterator -import org.eclipse.emf.ecore.EObject -import org.eclipse.osgi.util.NLS -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.EcoreUtil2; -import org.eclipse.xtext.EnumRule -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.GrammarUtil -import org.eclipse.xtext.ParserRule -import org.eclipse.xtext.TerminalRule -import org.eclipse.xtext.validation.Check -import com.avaloq.tools.ddk.xtext.format.format.FormatPackage -import com.google.common.base.Predicate -import com.google.common.base.Function -import com.google.common.base.Predicates - -/** - * This class contains custom validation rules. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation - */ -class FormatValidator extends AbstractFormatValidator { - - /** - * Validation code for illegal format directives for Terminal or Datatype rules. - */ - public static val ILLEGAL_DIRECTIVE_CODE = "com.avaloq.tools.ddk.xtext.format.validation.IllegalDirective" - /** - * Validation code for override missing. - */ - public static val OVERRIDE_MISSING_CODE = "com.avaloq.tools.ddk.xtext.format.validation.MissingOverride" - /** - * Validation code for illegal override. - */ - public static val OVERRIDE_ILLEGAL_CODE = "com.avaloq.tools.ddk.xtext.format.validation.IllegalOverride" - /** - * Validation code for incompatible grammar of extended format. - */ - public static val EXTENDED_GRAMMAR_INCOMPATIBLE_CODE = "com.avaloq.tools.ddk.xtext.format.validation.ExtendedGrammarIncompatible" - /** - * Validation code for required grammar rule missing. - */ - public static val GRAMMAR_RULE_MISSING_CODE = "com.avaloq.tools.ddk.xtext.format.validation.GrammarRuleMissing" - static val OVERRIDE_MISSING_MESSAGE = "Override missing" - static val OVERRIDE_ILLEGAL_MESSAGE = "Override illegal" - static val IS_OVERRIDE = new Predicate() { - override apply(Rule input) { - return input.isOverride() - } - - }; - static val TARGET_RULE = new Function() { - override apply(GrammarRule from) { - return from.getTargetRule() - } - }; - - /** - * Verify that only rule self directives are used for terminal, enum and data type rules. - * - * @param model - * the GrammarRule - */ - @Check - def void checkDataTypeOrEnumRule(GrammarRule model) { - if (model.getTargetRule() instanceof TerminalRule || model.getTargetRule() instanceof EnumRule - || (model.getTargetRule() instanceof ParserRule && GrammarUtil.isDatatypeRule(model.getTargetRule() as ParserRule))) { - val grammarElementAccessors = collectGrammarElementAccessors(model) - val selfAccessOnly = Iterators.all(grammarElementAccessors, new Predicate() { - override apply(EObject input) { - return input instanceof GrammarElementReference && (input as GrammarElementReference).getSelf() !== null - } - }); - if (!selfAccessOnly) { - error(NLS.bind("For data type, enum or terminal rule {0} only ''rule'' directive may be used", model.getTargetRule().getName()), FormatPackage::Literals::GRAMMAR_RULE__DIRECTIVES, ILLEGAL_DIRECTIVE_CODE) - } - } - } - - /** - * Verify that there are exactly two grammar elements if "between" is used. - * - * @param matcher - * the Matcher - */ - @Check - def void checkBetweenArguments(Matcher matcher) { - checkTwoArgumentMatcherType(matcher, MatcherType::BETWEEN) - } - - /** - * Verify that there are exactly two grammar elements if "range" is used. - * - * @param matcher - * the Matcher - */ - @Check - def void checkRangeArguments(Matcher matcher) { - checkTwoArgumentMatcherType(matcher, MatcherType::RANGE) - } - - /** - * Verify that references to other grammar rules at most occur once and that "between" or "range" is used. - * - * @param elementReference - * reference to a grammar element - */ - @Check - def void checkRuleReference(GrammarElementReference elementReference) { - if (elementReference.getRule() === null) { - return - } - val directive = EcoreUtil2.getContainerOfType(elementReference, SpecificDirective); - if (directive.getMatcherList() !== null) { - val twoArgumentMatcherTypesOnly = Iterables.all(directive.getMatcherList().getMatchers(), new Predicate() { - override apply(Matcher input) { - return input.getType() == MatcherType::BETWEEN || input.getType() == MatcherType::RANGE - } - }); - if (!twoArgumentMatcherTypesOnly) { - error(NLS.bind("Grammar rules may only be used with \"{0}\" or \"{1}\"", MatcherType::RANGE.getName(), MatcherType::BETWEEN.getName()), FormatPackage::Literals::GRAMMAR_ELEMENT_REFERENCE__RULE) - } - val ruleGrammarElements = Iterables.filter(directive.getGrammarElements(), new Predicate() { - override apply(GrammarElementReference input) { - return input.getRule() !== null - } - }) - if (Iterables.size(ruleGrammarElements) != 1) { - error(NLS.bind("Only one grammar rule may be referenced with \"{0}\" and \"{1}\"", MatcherType::RANGE.getName(), MatcherType::BETWEEN.getName()), FormatPackage::Literals::GRAMMAR_ELEMENT_REFERENCE__RULE) - } - } - } - - /** - * Checks that rules declare overrides when there is a corresponding inherited rule. - * - * @param model - * the model - */ - @Check - def void checkOverrideMissing(FormatConfiguration model) { - val extendedModel = model.getExtendedFormatConfiguration(); - if (extendedModel === null || extendedModel.eIsProxy()) { - return - } - val nonOverrideRules = Iterables.filter(model.getRules(), Predicates.not(IS_OVERRIDE)) - val overriddenRules = collectRules(extendedModel) - val localAbstractRuleMap = Maps.newHashMap() - for (GrammarRule rule : Iterables.filter(nonOverrideRules, GrammarRule)) { - localAbstractRuleMap.put(TARGET_RULE.apply(rule), rule) - } - // Check GrammarRules - for (GrammarRule overriddenRule : Iterables.filter(overriddenRules, GrammarRule)) { - if (localAbstractRuleMap.containsKey(TARGET_RULE.apply(overriddenRule))) { - val localRule = localAbstractRuleMap.get(TARGET_RULE.apply(overriddenRule)) - error(OVERRIDE_MISSING_MESSAGE, localRule, FormatPackage::Literals::GRAMMAR_RULE__TARGET_RULE, OVERRIDE_MISSING_CODE) - } - } - // Check WildcardRule - if (!Iterables.isEmpty(Iterables.filter(nonOverrideRules, WildcardRule)) - && !Iterables.isEmpty(Iterables.filter(overriddenRules, WildcardRule))) { - error(OVERRIDE_MISSING_MESSAGE, Iterables.filter(nonOverrideRules, WildcardRule).iterator().next(), null, OVERRIDE_MISSING_CODE) - } - } - - /** - * Checks that no rule declares override when there is no corresponding inherited rule. - * - * @param model - * the model - */ - @Check - def void checkIllegalOverride(FormatConfiguration model) { - val overrideRules = Iterables.filter(model.getRules(), IS_OVERRIDE) - var Iterable overrideableRules = Lists.newArrayList() - val extendedModel = model.getExtendedFormatConfiguration() - if (extendedModel !== null && !extendedModel.eIsProxy()) { - overrideableRules = collectRules(extendedModel) - } - val overrideableAbstractRuleMap = Maps.newHashMap() - for (GrammarRule rule : Iterables.filter(overrideableRules, GrammarRule)) { - overrideableAbstractRuleMap.put(TARGET_RULE.apply(rule), rule) - } - // Check GrammarRules - for (GrammarRule overrideRule : Iterables.filter(overrideRules, GrammarRule)) { - if (!overrideableAbstractRuleMap.containsKey(TARGET_RULE.apply(overrideRule))) { - error(OVERRIDE_ILLEGAL_MESSAGE, overrideRule, FormatPackage::Literals::GRAMMAR_RULE__TARGET_RULE, OVERRIDE_ILLEGAL_CODE) - } - } - // Check WildcardRule - if (!Iterables.isEmpty(Iterables.filter(overrideRules, WildcardRule)) && Iterables.isEmpty(Iterables.filter(overrideableRules, WildcardRule))) { - error(OVERRIDE_ILLEGAL_MESSAGE, Iterables.filter(overrideRules, WildcardRule).iterator().next(), null, OVERRIDE_ILLEGAL_CODE) - } - } - - /** - * Check that extended configuration's grammar is compatible. - * - * @param model - * the model - */ - @Check - def void checkExtendedGrammarCompatible(FormatConfiguration model) { - val extendedModel = model.getExtendedFormatConfiguration() - if (extendedModel === null || extendedModel.eIsProxy()) { - return - } - if (!extendedModel.getTargetGrammar().eIsProxy()) { - val grammars = Lists.newArrayList(model.getTargetGrammar()) - grammars.addAll(model.getTargetGrammar().getUsedGrammars()) - for (Grammar grammar : grammars) { - if (extendedModel.getTargetGrammar().getName().equals(grammar.getName())) { - return - } - } - } - error("Extended format configuration has incompatible grammar", FormatPackage::Literals::FORMAT_CONFIGURATION__EXTENDED_FORMAT_CONFIGURATION, EXTENDED_GRAMMAR_INCOMPATIBLE_CODE) - } - - /** - * Check that identically named parent grammar rules are implemented in the model if the parent configuration implements the rule. - * If both grammars have an identically named rule then the extending FormatConfiguration must declare formatting if the parent FormatConfiguration does. - * - * @param model - * the model - */ - @Check - def void checkRequiredRulesImplemented(FormatConfiguration model) { - val extendedModel = model.getExtendedFormatConfiguration(); - if (extendedModel === null || extendedModel.eIsProxy() || model.getTargetGrammar() == extendedModel.getTargetGrammar()) {// NOPMD - return - } - val inheritedRules = Iterables.filter(collectRules(extendedModel), GrammarRule); - val ruleNames = Sets.newHashSet(Iterables.transform(inheritedRules, new Function() { - override apply(GrammarRule from) { - return from.getTargetRule().getName() - } - })) - for (GrammarRule rule : Iterables.filter(model.getRules(), GrammarRule)) { - if (rule.getTargetRule() !== null && !rule.getTargetRule().eIsProxy()) { - ruleNames.remove(rule.getTargetRule().getName()) - } - } - for (AbstractRule rule : model.getTargetGrammar().getRules()) { - if (ruleNames.contains(rule.getName())) { - error(NLS.bind("Required formatting for rule \"{0}\" missing", rule.getName()), FormatPackage::Literals::FORMAT_CONFIGURATION__TARGET_GRAMMAR, GRAMMAR_RULE_MISSING_CODE, rule.getName()) - } - } - } - - /** - * Check that referenced value is of integer type. - * - * @param value - * the value - */ - @Check - def void checkIntValueConstantReference(IntValue value) { - if (value.getReference() === null || value.getReference().eIsProxy()) { - return - } - if (value.getReference().getIntValue() === null) { - error(NLS.bind("Referenced const \"{0}\" is not an integer", value.getReference().getName()), FormatPackage::Literals::INT_VALUE__REFERENCE) - } - } - - /** - * Check that referenced value is of string type. - * - * @param value - * the value - */ - @Check - def void checkStringValueConstantReference(StringValue value) { - if (value.getReference() === null || value.getReference().eIsProxy()) { - return - } - if (value.getReference().getIntValue() === null) { - error(NLS.bind("Referenced const \"{0}\" is not a string", value.getReference().getName()), FormatPackage::Literals::STRING_VALUE__REFERENCE) - } - } - - /** - * Check that value is consistent with the (optional) declared type. - * - * @param value - * the value - */ - @Check - def void checkConstantOptionalType(Constant value) { - if (value.isIntType() && value.getIntValue() === null) { - error("Value is not an integer", FormatPackage::Literals::CONSTANT__NAME) - } else if (value.isStringType() && value.getStringValue() === null) { - error("Value is not a string", FormatPackage::Literals::CONSTANT__NAME) - } - } - - /** - * Collect all rules contained by the given model (including any of its extended configurations). - * - * @param model - * the model - * @return all rules - */ - private def Iterable collectRules(FormatConfiguration model) { - var Iterable result = model.getRules() - val extendedModel = model.getExtendedFormatConfiguration() - if (extendedModel !== null && !extendedModel.eIsProxy()) { - result = Iterables.concat(result, collectRules(extendedModel)) - } - return result; - } - - /** - * Verify that there are exactly two grammar elements used in the containing directive if the type of the matcher equals the given MatcherType. - * - * @param matcher - * the Matcher - * @param matcherType - * the MatcherType - */ - private def void checkTwoArgumentMatcherType(Matcher matcher, MatcherType matcherType) { - if (matcher.getType() == matcherType) { - val directive = EcoreUtil2.getContainerOfType(matcher, SpecificDirective) - if (directive === null || directive.getGrammarElements().size() != 2) { - error(NLS.bind("\"{0}\" may only be used with exactly two elements", matcherType.getName()), FormatPackage::Literals::MATCHER__TYPE) - } - } - } - - /** - * Collects all GramarElementReferences, GrammarElementLookups and KeywordPairs contained by a GrammarRule. - * - * @param rule - * the grammar rule - * @return Iterator with all grammar element accessors - */ - private def static Iterator collectGrammarElementAccessors(GrammarRule rule) { - return Iterators.filter(rule.eAllContents(), new Predicate() { - override apply(EObject input) { - return input instanceof KeywordPair || input instanceof GrammarElementReference || input instanceof GrammarElementLookup - } - }) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.format/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.format/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.generator.test/.classpath b/com.avaloq.tools.ddk.xtext.generator.test/.classpath index ed47baa3cf..526bfab37b 100644 --- a/com.avaloq.tools.ddk.xtext.generator.test/.classpath +++ b/com.avaloq.tools.ddk.xtext.generator.test/.classpath @@ -1,6 +1,5 @@ - diff --git a/com.avaloq.tools.ddk.xtext.generator.test/build.properties b/com.avaloq.tools.ddk.xtext.generator.test/build.properties index d6c49e5cf1..6b7927735f 100644 --- a/com.avaloq.tools.ddk.xtext.generator.test/build.properties +++ b/com.avaloq.tools.ddk.xtext.generator.test/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.java new file mode 100644 index 0000000000..0438423d21 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.xbase.test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Iterator; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractMetamodelDeclaration; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.Group; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.TypeRef; +import org.eclipse.xtext.XtextPackage; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.xbase.lib.Pair; +import org.eclipse.xtext.xtext.generator.xbase.XbaseUsageDetector; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.avaloq.tools.ddk.test.core.jupiter.BugTest; +import com.avaloq.tools.ddk.test.core.jupiter.BugTestAwareRule; + + +/** + * Tests for {@link XbaseUsageDetector}. + */ +@ExtendWith(InjectionExtension.class) +@SuppressWarnings("nls") +public class XbaseGeneratorFragmentTest { + + // CHECKSTYLE:CONSTANTS-OFF + @RegisterExtension + private final BugTestAwareRule bugTestRule = BugTestAwareRule.getInstance(); + + private static final String THIS_PACKAGE_NAME = "thisPackage"; + private static final String XTYPE_PACKAGE_NAME = "xtype"; + private static final String X_IMPORT_SECTION_RULE_NAME = "XImportSection"; + + private final XbaseUsageDetector detector = new XbaseUsageDetector(); + + /** + * Set expectations prior to calling usesXImportSection.apply(). + * + * @param mockRule + * Mock Rule, in which to set expectations. + * @param packageName + * Package name to use. + * @param ruleName + * Rule name to use. + */ + @SuppressWarnings("unchecked") + private void setExpectationsForApply(final ParserRule mockRule, final String packageName, final String ruleName) { + final TypeRef mockType = mock(TypeRef.class); + final AbstractMetamodelDeclaration mockMetamodel = mock(AbstractMetamodelDeclaration.class); + final EPackage mockEPackage = mock(EPackage.class); + + when(mockRule.getName()).thenReturn(ruleName); + + when(mockRule.getType()).thenReturn(mockType); + when(mockType.getMetamodel()).thenReturn(mockMetamodel); + when(mockMetamodel.getEPackage()).thenReturn(mockEPackage); + when(mockEPackage.getName()).thenReturn(packageName); + } + + /** + * Set expectations prior to calling usesXImportSection(). + * + * @param mockGrammar + * Mock Grammar, in which to set expectations. + * @param packageAndRuleNamesOfLeafRules + * Package and rule names to use for leaf rules. + */ + @SafeVarargs + @SuppressWarnings("unchecked") + private void setExpectationsForUsesXImportSection(final Grammar mockGrammar, final Pair... packageAndRuleNamesOfLeafRules) { + final ParserRule mockRootRule = mock(ParserRule.class); + final Group mockAlternatives = mock(Group.class); + final EList mockElements = new org.eclipse.emf.common.util.BasicEList(); + + final EList mockLeafRules = new org.eclipse.emf.common.util.BasicEList(); + for (final Pair pair : packageAndRuleNamesOfLeafRules) { + mockLeafRules.add(mock(ParserRule.class)); + } + + final EList mockRules = new org.eclipse.emf.common.util.BasicEList(); + mockRules.add(mockRootRule); + mockRules.addAll(mockLeafRules); + + // Calls made by UsedRulesFinder.compute() + when(mockGrammar.getRules()).thenReturn(mockRules); + + // Calls made by doSwitch(firstRule) + when(mockRootRule.eClass()).thenReturn(XtextPackage.Literals.PARSER_RULE); + when(mockRootRule.getAlternatives()).thenReturn(mockAlternatives); + + when(mockAlternatives.eClass()).thenReturn(XtextPackage.Literals.GROUP); + when(mockAlternatives.getElements()).thenReturn(mockElements); + + // Calls made per leaf rule by doSwitch(firstRule) + for (final ParserRule mockLeafRule : mockLeafRules) { + final Assignment mockAssignment = mock(Assignment.class); + final RuleCall mockTerminal = mock(RuleCall.class); + mockElements.add(mockAssignment); + + when(mockAssignment.eClass()).thenReturn(XtextPackage.Literals.ASSIGNMENT); + when(mockAssignment.getTerminal()).thenReturn(mockTerminal); + + when(mockTerminal.eClass()).thenReturn(XtextPackage.Literals.RULE_CALL); + when(mockTerminal.getRule()).thenReturn(mockLeafRule); + + when(mockLeafRule.eClass()).thenReturn(XtextPackage.Literals.PARSER_RULE); + when(mockLeafRule.getAlternatives()).thenReturn(null); + when(mockLeafRule.isDefinesHiddenTokens()).thenReturn(false); + } + + // Calls made by doSwitch(grammar) + when(mockGrammar.eClass()).thenReturn(XtextPackage.Literals.GRAMMAR); + when(mockGrammar.isDefinesHiddenTokens()).thenReturn(false); + when(mockGrammar.getUsedGrammars()).thenReturn(new org.eclipse.emf.common.util.BasicEList()); + + // Calls made per rule by XbaseGeneratorFragmentOverride.usesXImportSection.apply() + setExpectationsForApply(mockRootRule, THIS_PACKAGE_NAME, "rootRule"); + + Iterator> packageAndRuleNameIterator = Arrays.asList(packageAndRuleNamesOfLeafRules).iterator(); + for (ParserRule mockLeafRule : mockLeafRules) { + final Pair packageandRuleName = packageAndRuleNameIterator.next(); + + final String packageName = packageandRuleName.getKey(); + final String ruleName = packageandRuleName.getValue(); + + setExpectationsForApply(mockLeafRule, packageName, ruleName); + } + } + + /** + * Test usesXImportSection() when passed a null XtextResource + */ + @Test + public void testUsesXImportSectionWithNullGrammar() { + assertThrows(NullPointerException.class, () -> detector.usesXImportSection(null)); + } + + /** + * Test usesXImportSection() when the grammar does not use xtype::XImportSection(). + */ + @Test + @BugTest(value = "DSL-244") + public void testUsesXImportSectionWhenNotUsed() { + // ARRANGE + final Grammar mockGrammar = mock(Grammar.class); + + // Use a selection of rules which do not include xtype::XImportSection + setExpectationsForUsesXImportSection(mockGrammar, Pair.of(null, "leafRule1"), Pair.of(THIS_PACKAGE_NAME, "leafRule2"), Pair.of(XTYPE_PACKAGE_NAME, "leafRule3"), Pair.of(null, X_IMPORT_SECTION_RULE_NAME), Pair.of(THIS_PACKAGE_NAME, X_IMPORT_SECTION_RULE_NAME)); + + // ACT + final boolean usesXImportSection = detector.usesXImportSection(mockGrammar); + + // ASSERT + assertFalse(usesXImportSection, "usesXImportSection() should return false when the grammar does not use XImportSection"); + } + + /** + * Test usesXImportSection() when the grammar uses xtype::XImportSection(). + */ + @Test + @BugTest(value = "DSL-244") + public void testUsesXImportSectionWhenUsed() { + // ARRANGE + final Grammar mockGrammar = mock(Grammar.class); + + // Use a selection of rules including xtype::XImportSection + setExpectationsForUsesXImportSection(mockGrammar, Pair.of(null, "leafRule1"), Pair.of(THIS_PACKAGE_NAME, "leafRule2"), Pair.of(XTYPE_PACKAGE_NAME, "leafRule3"), Pair.of(null, X_IMPORT_SECTION_RULE_NAME), Pair.of(THIS_PACKAGE_NAME, X_IMPORT_SECTION_RULE_NAME), Pair.of(XTYPE_PACKAGE_NAME, X_IMPORT_SECTION_RULE_NAME)); + + // ACT + final boolean usesXImportSection = detector.usesXImportSection(mockGrammar); + + // ASSERT + assertTrue(usesXImportSection, "usesXImportSection() should return true when the grammar uses XImportSection"); + } + // CHECKSTYLE:CONSTANTS-ON + +} diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.xtend b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.xtend deleted file mode 100644 index 1aed804f45..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.xtend +++ /dev/null @@ -1,200 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.xbase.test - -import com.avaloq.tools.ddk.test.core.jupiter.BugTest -import org.eclipse.emf.common.util.BasicEList -import org.eclipse.emf.ecore.EPackage -import org.eclipse.xtext.AbstractElement -import org.eclipse.xtext.AbstractMetamodelDeclaration -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.Assignment -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.Group -import org.eclipse.xtext.ParserRule -import org.eclipse.xtext.RuleCall -import org.eclipse.xtext.TypeRef -import org.eclipse.xtext.XtextPackage - -import static org.mockito.Mockito.mock -import static org.mockito.Mockito.when -import org.eclipse.xtext.xtext.generator.xbase.XbaseUsageDetector -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.junit.jupiter.api.Test -import static org.junit.jupiter.api.Assertions.assertFalse -import static org.junit.jupiter.api.Assertions.assertThrows -import static org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.^extension.RegisterExtension -import com.avaloq.tools.ddk.test.core.jupiter.BugTestAwareRule - -/** - * Tests for {@link XbaseUsageDetector}. - */ -@ExtendWith(InjectionExtension) -class XbaseGeneratorFragmentTest { - - @RegisterExtension - public BugTestAwareRule bugTestRule = BugTestAwareRule.getInstance(); - - val thisPackageName = "thisPackage" - val xtypePackageName = "xtype" - val xImportSectionRuleName = "XImportSection" - - val detector = new XbaseUsageDetector - - /** - * Set expectations prior to calling usesXImportSection.apply(). - * - * @param mockRule Mock Rule, in which to set expectations. - * @param packageName Package name to use. - * @param ruleName Rule name to use. - */ - private def setExpectationsForApply(ParserRule mockRule, String packageName, String ruleName) { - val mockType = mock(TypeRef) - val mockMetamodel = mock(AbstractMetamodelDeclaration) - val mockEPackage = mock(EPackage) - - when(mockRule.name).thenReturn(ruleName) - - when(mockRule.type).thenReturn(mockType) - when(mockType.metamodel).thenReturn(mockMetamodel) - when(mockMetamodel.EPackage).thenReturn(mockEPackage) - when(mockEPackage.name).thenReturn(packageName) - } - - /** - * Set expectations prior to calling usesXImportSection(). - * - * @param mockGrammar Mock Grammar, in which to set expectations. - * @param packageAndRuleNamesOfLeafRules Package and rule names to use for leaf rules. - */ - private def setExpectationsForUsesXImportSection(Grammar mockGrammar, Pair... packageAndRuleNamesOfLeafRules) { - val mockRootRule = mock(ParserRule) - val mockAlternatives = mock(Group) - val mockElements = new BasicEList - - val mockLeafRules = new BasicEList - packageAndRuleNamesOfLeafRules.forEach[mockLeafRules.add(mock(ParserRule))] - - val mockRules = new BasicEList() - mockRules.add(mockRootRule) - mockRules.addAll(mockLeafRules) - - // Calls made by UsedRulesFinder.compute() - when(mockGrammar.rules).thenReturn(mockRules); - - // Calls made by doSwitch(firstRule) - when(mockRootRule.eClass).thenReturn(XtextPackage.Literals.PARSER_RULE) - when(mockRootRule.alternatives).thenReturn(mockAlternatives) - - when(mockAlternatives.eClass).thenReturn(XtextPackage.Literals.GROUP) - when(mockAlternatives.elements).thenReturn(mockElements) - - // Calls made per leaf rule by doSwitch(firstRule) - mockLeafRules.forEach[mockLeafRule | - val mockAssignment = mock(Assignment) - val mockTerminal = mock(RuleCall) - mockElements.add(mockAssignment) - - when(mockAssignment.eClass).thenReturn(XtextPackage.Literals.ASSIGNMENT) - when(mockAssignment.terminal).thenReturn(mockTerminal) - - when(mockTerminal.eClass).thenReturn(XtextPackage.Literals.RULE_CALL) - when(mockTerminal.rule).thenReturn(mockLeafRule) - - when(mockLeafRule.eClass).thenReturn(XtextPackage.Literals.PARSER_RULE) - when(mockLeafRule.alternatives).thenReturn(null) - when(mockLeafRule.definesHiddenTokens).thenReturn(false) - ] - - // Calls made by doSwitch(grammar) - when(mockGrammar.eClass).thenReturn(XtextPackage.Literals.GRAMMAR) - when(mockGrammar.definesHiddenTokens).thenReturn(false) - when(mockGrammar.usedGrammars).thenReturn(new BasicEList) - - // Calls made per rule by XbaseGeneratorFragmentOverride.usesXImportSection.apply() - mockRootRule.setExpectationsForApply(thisPackageName, "rootRule") - - val mockLeafRuleIterator = mockLeafRules.iterator - val packageAndRuleNameIterator = packageAndRuleNamesOfLeafRules.iterator - while (mockLeafRuleIterator.hasNext) { - val mockLeafRule = mockLeafRuleIterator.next - val packageandRuleName = packageAndRuleNameIterator.next - - val packageName = packageandRuleName.key - val ruleName = packageandRuleName.value - - mockLeafRule.setExpectationsForApply(packageName, ruleName) - } - } - - /** - * Test usesXImportSection() when passed a null XtextResource - */ - @Test - def void testUsesXImportSectionWithNullGrammar() { - assertThrows(NullPointerException, [|detector.usesXImportSection(null)]); - } - - /** - * Test usesXImportSection() when the grammar does not use xtype::XImportSection(). - */ - @Test - @BugTest(value = "DSL-244") - def void testUsesXImportSectionWhenNotUsed() { - // ARRANGE - val mockGrammar = mock(Grammar) - - // Use a selection of rules which do not include xtype::XImportSection - mockGrammar.setExpectationsForUsesXImportSection( - null -> "leafRule1", - thisPackageName -> "leafRule2", - xtypePackageName -> "leafRule3", - null -> xImportSectionRuleName, - thisPackageName -> xImportSectionRuleName - ) - - // ACT - val usesXImportSection = detector.usesXImportSection(mockGrammar) - - // ASSERT - assertFalse(usesXImportSection, "usesXImportSection() should return false when the grammar does not use XImportSection") - } - - /** - * Test usesXImportSection() when the grammar uses xtype::XImportSection(). - */ - @Test - @BugTest(value = "DSL-244") - def void testUsesXImportSectionWhenUsed() { - // ARRANGE - val mockGrammar = mock(Grammar) - - // Use a selection of rules including xtype::XImportSection - mockGrammar.setExpectationsForUsesXImportSection( - null -> "leafRule1", - thisPackageName -> "leafRule2", - xtypePackageName -> "leafRule3", - null -> xImportSectionRuleName, - thisPackageName -> xImportSectionRuleName, - xtypePackageName -> xImportSectionRuleName - ) - - // ACT - val usesXImportSection = detector.usesXImportSection(mockGrammar) - - // ASSERT - assertTrue(usesXImportSection, "usesXImportSection() should return true when the grammar uses XImportSection") - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator.test/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.generator.test/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.generator/.classpath b/com.avaloq.tools.ddk.xtext.generator/.classpath index aba0d9bb22..69e01f9099 100644 --- a/com.avaloq.tools.ddk.xtext.generator/.classpath +++ b/com.avaloq.tools.ddk.xtext.generator/.classpath @@ -1,11 +1,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.generator/build.properties b/com.avaloq.tools.ddk.xtext.generator/build.properties index d6c49e5cf1..6b7927735f 100644 --- a/com.avaloq.tools.ddk.xtext.generator/build.properties +++ b/com.avaloq.tools.ddk.xtext.generator/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/BundleVersionStripperFragment.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/BundleVersionStripperFragment.java new file mode 100644 index 0000000000..2bd2a7e8e3 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/BundleVersionStripperFragment.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) Avaloq Group AG + * Schwerzistrasse 6, 8807 Freienbach, Switzerland, http://www.avaloq.com + * All Rights Reserved. + * + * This software is the confidential and proprietary information of Avaloq Group AG. + * You shall not disclose whole or parts of it and shall use it only in accordance with the terms of the + * licence agreement you entered into with Avaloq Group AG. + */ + +package com.avaloq.tools.ddk.xtext.generator; + +import com.google.common.collect.Iterables; +import java.util.ArrayList; +import java.util.List; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.model.ManifestAccess; +import org.eclipse.xtext.xtext.generator.model.project.IBundleProjectConfig; + +/** + * Removes the bundle-version from specific bundles in generated manifests + * + * In some projects, we choose not to include bundle-versions in the manifest, but various fragments generate them anyway. + * Run this fragment at the end to remove versions for specific bundles + */ +public class BundleVersionStripperFragment extends AbstractXtextGeneratorFragment { + + private final List bundles = new ArrayList<>(); + + public void addBundle(final String bundleName) { + bundles.add(bundleName); + } + + @Override + public void generate() { + for (IBundleProjectConfig project : Iterables.filter(getProjectConfig().getEnabledProjects(), IBundleProjectConfig.class)) { + ManifestAccess manifest = project.getManifest(); + if (manifest != null) { + stripBundleVersions(manifest); + } + } + } + + public void stripBundleVersions(final ManifestAccess manifest) { + for (String bundleName : bundles) { + if (manifest.getRequiredBundles().removeIf(it -> it.startsWith(bundleName))) { + manifest.getRequiredBundles().add(bundleName); + } + } + } + + protected List getBundles() { + return bundles; + } +} + +/* Copyright (c) Avaloq Group AG */ diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/BundleVersionStripperFragment.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/BundleVersionStripperFragment.xtend deleted file mode 100644 index 2a26eb1198..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/BundleVersionStripperFragment.xtend +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) Avaloq Group AG - * Schwerzistrasse 6, 8807 Freienbach, Switzerland, http://www.avaloq.com - * All Rights Reserved. - * - * This software is the confidential and proprietary information of Avaloq Group AG. - * You shall not disclose whole or parts of it and shall use it only in accordance with the terms of the - * licence agreement you entered into with Avaloq Group AG. - */ - -package com.avaloq.tools.ddk.xtext.generator - -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtend.lib.annotations.Accessors -import java.util.List -import org.eclipse.xtext.xtext.generator.model.ManifestAccess -import org.eclipse.xtext.xtext.generator.model.project.IBundleProjectConfig - -/** - * Removes the bundle-version from specific bundles in generated manifests - * - * In some projects, we choose not to include bundle-versions in the manifest, but various fragments generate them anyway. - * Run this fragment at the end to remove versions for specific bundles - */ -class BundleVersionStripperFragment extends AbstractXtextGeneratorFragment { - - @Accessors(PROTECTED_GETTER) - val List bundles = newArrayList - - def void addBundle(String bundleName) { - bundles.add(bundleName) - } - - override generate() { - projectConfig.enabledProjects.filter(typeof(IBundleProjectConfig)).map[manifest].filterNull.forEach[stripBundleVersions] - } - - def stripBundleVersions(ManifestAccess manifest) { - bundles.forEach[ bundleName | - if (manifest.requiredBundles.removeIf([it.startsWith(bundleName)])) { - manifest.requiredBundles.add(bundleName) - } - ] - } - -} - -/* Copyright (c) Avaloq Group AG */ \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/DefaultFragmentWithOverride.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/DefaultFragmentWithOverride.java new file mode 100644 index 0000000000..c0df730444 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/DefaultFragmentWithOverride.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator; + +import com.google.inject.Injector; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.IXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.Issues; + +/** + * Allow different fragments to be generated depending on a condition. + * + *

By default we generate the defaultFragment (or nothing if it is null). + * If useOverride is true, we generate the overrideFragment (or nothing if it is null). + */ +public class DefaultFragmentWithOverride extends AbstractXtextGeneratorFragment { + + /** + * Whether to use the override fragment. False by default + */ + private boolean useOverride; + + private IXtextGeneratorFragment defaultFragment; + + private IXtextGeneratorFragment overrideFragment; + + public boolean isUseOverride() { + return useOverride; + } + + public void setUseOverride(final boolean useOverride) { + this.useOverride = useOverride; + } + + public IXtextGeneratorFragment getDefaultFragment() { + return defaultFragment; + } + + public void setDefaultFragment(final IXtextGeneratorFragment defaultFragment) { + this.defaultFragment = defaultFragment; + } + + public IXtextGeneratorFragment getOverrideFragment() { + return overrideFragment; + } + + public void setOverrideFragment(final IXtextGeneratorFragment overrideFragment) { + this.overrideFragment = overrideFragment; + } + + @Override + public void generate() { + IXtextGeneratorFragment fragment = useOverride ? overrideFragment : defaultFragment; + if (fragment != null) { + fragment.generate(); + } + } + + @Override + public void initialize(final Injector injector) { + super.initialize(injector); + if (overrideFragment != null) { + overrideFragment.initialize(injector); + } + if (defaultFragment != null) { + defaultFragment.initialize(injector); + } + } + + @Override + public void checkConfiguration(final Issues issues) { + overrideFragment.checkConfiguration(issues); + defaultFragment.checkConfiguration(issues); + } + +} +/* Copyright (c) Avaloq Group AG */ diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/DefaultFragmentWithOverride.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/DefaultFragmentWithOverride.xtend deleted file mode 100644 index 01099bed21..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/DefaultFragmentWithOverride.xtend +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator - -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtend.lib.annotations.Accessors -import org.eclipse.xtext.xtext.generator.IXtextGeneratorFragment -import com.google.inject.Injector -import org.eclipse.xtext.xtext.generator.Issues - -/** - * Allow different fragments to be generated depending on a condition - * - * By default we generate the defaultFragment (or nothing if it is null) - * if useOverride is true, we generate the overrideFragment (or nothing if it is null) - * - */ -class DefaultFragmentWithOverride extends AbstractXtextGeneratorFragment { - - /** - * Whether to use the override fragment. False by default - */ - @Accessors boolean useOverride = false - - @Accessors IXtextGeneratorFragment defaultFragment - - @Accessors IXtextGeneratorFragment overrideFragment - - override generate() { - (if (useOverride) overrideFragment else defaultFragment)?.generate() - } - - override initialize(Injector injector) { - super.initialize(injector) - overrideFragment?.initialize(injector) - defaultFragment?.initialize(injector) - } - - override checkConfiguration(Issues issues) { - overrideFragment.checkConfiguration(issues) - defaultFragment.checkConfiguration(issues) - } - -} -/* Copyright (c) Avaloq Group AG */ diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/BuilderIntegrationFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/BuilderIntegrationFragment2.java new file mode 100644 index 0000000000..44826cfaa7 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/BuilderIntegrationFragment2.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.builder; + +import com.avaloq.tools.ddk.xtext.linking.FastLazyURIEncoder; +import com.avaloq.tools.ddk.xtext.linking.LazyLinkingResource2; +import com.google.inject.name.Names; +import org.eclipse.xtext.linking.lazy.LazyLinkingResource; +import org.eclipse.xtext.linking.lazy.LazyURIEncoder; +import org.eclipse.xtext.resource.IContainer; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtend2.lib.StringConcatenationClient; + +@SuppressWarnings("nls") +public class BuilderIntegrationFragment2 extends org.eclipse.xtext.xtext.generator.builder.BuilderIntegrationFragment2 { + + private static final String BUILDER_BUNDLE_NAME = "com.avaloq.tools.ddk.xtext.builder"; + + @Override + protected void addRuntimeGuiceBindings() { + super.addRuntimeGuiceBindings(); + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(IContainer.Manager.class), new TypeReference("com.avaloq.tools.ddk.xtext.builder.CachingStateBasedContainerManager")) + .addTypeToType(TypeReference.typeRef(LazyLinkingResource.class), TypeReference.typeRef(LazyLinkingResource2.class)) + .addTypeToType(TypeReference.typeRef(LazyURIEncoder.class), TypeReference.typeRef(FastLazyURIEncoder.class)) + .contributeTo(getLanguage().getRuntimeGenModule()); + if (getProjectConfig().getRuntime().getManifest() != null) { + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add(BUILDER_BUNDLE_NAME); + } + } + + @Override + protected void addEclipsePluginGuiceBindings() { + super.addEclipsePluginGuiceBindings(); + final StringConcatenationClient statement1 = new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("binder.bind("); + target.append(TypeReference.typeRef(IResourceDescriptions.class)); + target.append(".class"); + target.newLineIfNotEmpty(); + target.append(").annotatedWith("); + target.append(TypeReference.typeRef(Names.class)); + target.append(".named("); + target.append(TypeReference.typeRef(ResourceDescriptionsProvider.class)); + target.append(".NAMED_BUILDER_SCOPE)"); + target.newLineIfNotEmpty(); + target.append(").to("); + target.append(new TypeReference("com.avaloq.tools.ddk.xtext.builder", "CurrentDescriptions2.ResourceSetAware")); + target.append(".class);"); + target.newLineIfNotEmpty(); + } + }; + new GuiceModuleAccess.BindingFactory() + .addConfiguredBinding(IResourceDescriptions.class.getSimpleName() + "BuilderScope", statement1) + .contributeTo(getLanguage().getEclipsePluginGenModule()); + if (getProjectConfig().getEclipsePlugin().getManifest() != null) { + getProjectConfig().getEclipsePlugin().getManifest().getRequiredBundles().add(BUILDER_BUNDLE_NAME); + } + } + +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/BuilderIntegrationFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/BuilderIntegrationFragment2.xtend deleted file mode 100644 index 1fb9a07b92..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/BuilderIntegrationFragment2.xtend +++ /dev/null @@ -1,60 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.builder - -import com.google.inject.name.Names -import org.eclipse.xtext.resource.IResourceDescriptions -import com.avaloq.tools.ddk.xtext.linking.FastLazyURIEncoder -import org.eclipse.xtext.linking.lazy.LazyURIEncoder -import org.eclipse.xtext.linking.lazy.LazyLinkingResource -import org.eclipse.xtext.resource.IContainer -import com.avaloq.tools.ddk.xtext.linking.LazyLinkingResource2 -import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess - -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* -import org.eclipse.xtext.xtext.generator.model.TypeReference -import org.eclipse.xtend2.lib.StringConcatenationClient - -class BuilderIntegrationFragment2 extends org.eclipse.xtext.xtext.generator.builder.BuilderIntegrationFragment2 { - - static val BUILDER_BUNDLE_NAME = "com.avaloq.tools.ddk.xtext.builder"; - - override addRuntimeGuiceBindings() { - super.addRuntimeGuiceBindings - new GuiceModuleAccess.BindingFactory() - .addTypeToType(IContainer.Manager.typeRef, new TypeReference("com.avaloq.tools.ddk.xtext.builder.CachingStateBasedContainerManager")) - .addTypeToType(LazyLinkingResource.typeRef, LazyLinkingResource2.typeRef) - .addTypeToType(LazyURIEncoder.typeRef, FastLazyURIEncoder.typeRef) - .contributeTo(language.runtimeGenModule) - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest.requiredBundles += BUILDER_BUNDLE_NAME - } - } - - override addEclipsePluginGuiceBindings() { - super.addEclipsePluginGuiceBindings - val StringConcatenationClient statement1 = - ''' - binder.bind(«IResourceDescriptions».class - ).annotatedWith(«Names».named(«ResourceDescriptionsProvider».NAMED_BUILDER_SCOPE) - ).to(«new TypeReference('com.avaloq.tools.ddk.xtext.builder', 'CurrentDescriptions2.ResourceSetAware')».class); - ''' - new GuiceModuleAccess.BindingFactory() - .addConfiguredBinding(IResourceDescriptions.simpleName + 'BuilderScope', statement1) - .contributeTo(language.eclipsePluginGenModule) - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.eclipsePlugin.manifest.requiredBundles += BUILDER_BUNDLE_NAME - } - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/LspBuilderIntegrationFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/LspBuilderIntegrationFragment2.java new file mode 100644 index 0000000000..07b0333456 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/LspBuilderIntegrationFragment2.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.builder; + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.osgi.util.NLS; +import org.eclipse.xtext.AbstractMetamodelDeclaration; +import org.eclipse.xtext.GeneratedMetamodel; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtend2.lib.StringConcatenationClient; + +import com.google.inject.Inject; + + +@SuppressWarnings("nls") +public class LspBuilderIntegrationFragment2 extends AbstractXtextGeneratorFragment { + + private static final Logger LOGGER = LogManager.getLogger(LspBuilderIntegrationFragment2.class); + + @Inject + private FileAccessFactory fileAccessFactory; + + @Inject + private XtextGeneratorNaming packageNaming; + + + + private TypeReference createSuffixedTypeReference(final String suffix) { + return new TypeReference( + packageNaming.getGenericIdeBasePackage(getGrammar()), GrammarUtil.getSimpleName(getGrammar()) + suffix + ); + } + + protected TypeReference getLspBuildSetupGeneratedClass() { + return createSuffixedTypeReference("LspBuildSetupGenerated"); + } + + protected TypeReference getLspBuildSetupServiceClass() { + return createSuffixedTypeReference("LspBuildSetupService"); + } + + @Override + public void generate() { + if (getProjectConfig().getGenericIde().isEnabled()) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(NLS.bind("executing generate for {0}", getClass().getName())); + } + generateServiceRegistration(); + generateBuildService(); + generateBuildSetup(); + } + } + + public void generateServiceRegistration() { + String content = getLspBuildSetupServiceClass().getName() + '\n'; + fileAccessFactory.createTextFile("META-INF/services/com.avaloq.tools.ddk.xtext.build.ILspLanguageSetup", + toClient(content)).writeTo(getProjectConfig().getGenericIde().getSrcGen()); + } + + // CHECKSTYLE:CONSTANTS-OFF + public void generateBuildService() { + final Grammar grammar = getGrammar(); + final TypeReference lspBuildSetupServiceClass = getLspBuildSetupServiceClass(); + StringBuilder sb = new StringBuilder(1024); + sb.append("import java.util.List;\n"); + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.xtext.build.AbstractDynamicSetupService;\n"); + sb.append("import com.avaloq.tools.ddk.xtext.build.ILspLanguageSetup;\n"); + sb.append("import com.google.common.collect.ImmutableList;\n"); + sb.append("import com.google.inject.Injector;\n"); + sb.append("import com.google.inject.Module;\n"); + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Generated by com.avaloq.tools.ddk.xtext.generator.builder.LspBuilderIntegrationFragment2.\n"); + sb.append(" */\n"); + sb.append("public class ").append(lspBuildSetupServiceClass.getSimpleName()).append(" extends AbstractDynamicSetupService implements ILspLanguageSetup {\n"); + sb.append('\n'); + sb.append(" @SuppressWarnings(\"nls\")\n"); + sb.append(" private static final String GRAMMAR = \"").append(grammar.getName()).append("\";\n"); + sb.append(" @SuppressWarnings(\"nls\")\n"); + sb.append(" private static final List PARENTS = ImmutableList.of(//\n"); + List allUsedGrammars = GrammarUtil.allUsedGrammars(grammar); + for (int i = 0; i < allUsedGrammars.size(); i++) { + Grammar g = allUsedGrammars.get(i); + sb.append(" \"").append(g.getName()).append("\" //"); + if (i < allUsedGrammars.size() - 1) { + sb.append(','); + } + sb.append('\n'); + } + sb.append(" );\n"); + sb.append('\n'); + sb.append(" public Injector doSetup(Module overrideModule, Module... additionalModules) {\n"); + sb.append(" return new ").append(GrammarUtil.getSimpleName(grammar)).append("LspBuildSetupGenerated(SETUP_LOCK, overrideModule, additionalModules).createInjectorAndDoEMFRegistration();\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public List getParentLanguages() {\n"); + sb.append(" return PARENTS;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public String getLanguageName() {\n"); + sb.append(" return GRAMMAR;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append("}\n"); + fileAccessFactory.createJavaFile(lspBuildSetupServiceClass, + toClient(sb)).writeTo(getProjectConfig().getGenericIde().getSrcGen()); + } + + public void generateBuildSetup() { + final Grammar grammar = getGrammar(); + final TypeReference lspBuildSetupGeneratedClass = getLspBuildSetupGeneratedClass(); + StringBuilder sb = new StringBuilder(2048); + sb.append("import org.eclipse.xtext.util.Modules2;\n"); + sb.append('\n'); + sb.append("import com.google.common.collect.ImmutableList;\n"); + sb.append("import com.google.inject.Guice;\n"); + sb.append("import com.google.inject.Injector;\n"); + sb.append("import com.google.inject.Module;\n"); + sb.append("import com.google.inject.util.Modules;\n"); + sb.append('\n'); + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Generated by com.avaloq.tools.ddk.xtext.generator.builder.LspBuilderIntegrationFragment2.\n"); + sb.append(" */\n"); + sb.append("public class ").append(lspBuildSetupGeneratedClass.getSimpleName()).append(" extends ").append(packageNaming.getGenericIdeSetup(grammar)).append(" {\n"); + sb.append('\n'); + sb.append(" private final Module overrideModule;\n"); + sb.append(" private final Module[] additionalModules;\n"); + sb.append(" private final Object lock;\n"); + sb.append('\n'); + sb.append(" public ").append(lspBuildSetupGeneratedClass.getSimpleName()).append("(final Object lock, final Module overrideModule, Module... additionalModules) {\n"); + sb.append(" this.lock = lock;\n"); + sb.append(" this.overrideModule = overrideModule;\n"); + sb.append(" this.additionalModules = additionalModules;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" public ").append(lspBuildSetupGeneratedClass.getSimpleName()).append("(final Module overrideModule, Module... additionalModules) {\n"); + sb.append(" this.lock = null;\n"); + sb.append(" this.overrideModule = overrideModule;\n"); + sb.append(" this.additionalModules = additionalModules;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public Injector createInjectorAndDoEMFRegistration() {\n"); + sb.append(" registerEPackages();\n"); + sb.append(" Injector injector = createInjector();\n"); + sb.append(" if (lock != null) {\n"); + sb.append(" synchronized (lock) {\n"); + sb.append(" register(injector);\n"); + sb.append(" }\n"); + sb.append(" } else {\n"); + sb.append(" register(injector);\n"); + sb.append(" }\n"); + sb.append(" return injector;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public Injector createInjector() {\n"); + sb.append(" return Guice.createInjector(getModules());\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" protected void registerEPackages() {\n"); + for (AbstractMetamodelDeclaration decl : grammar.getMetamodelDeclarations()) { + if (decl instanceof GeneratedMetamodel) { + GeneratedMetamodel mmd = (GeneratedMetamodel) decl; + String ns = GrammarUtil.getNamespace(grammar); + String name = mmd.getName(); + String nameUpper = name.substring(0, 1).toUpperCase() + name.substring(1); + sb.append(" if (").append(ns).append('.').append(name).append('.').append(nameUpper).append("Package.eINSTANCE == null) {\n"); + sb.append(" throw new IllegalStateException(\"EPackage could not be initialized: \" + ").append(ns).append('.').append(name).append('.').append(nameUpper).append("Package.eNS_URI); //$NON-NLS-1$\n"); + sb.append(" }\n"); + } + } + sb.append(" }\n"); + sb.append('\n'); + sb.append(" protected Iterable getModules() {\n"); + sb.append(" return ImmutableList. builder().add(Modules.override(Modules2.mixin(new ").append(grammar.getName()).append("RuntimeModule(), new ").append(packageNaming.getGenericIdeModule(grammar)).append("())).with(overrideModule)).add(additionalModules).build();\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append("}\n"); + fileAccessFactory.createJavaFile(lspBuildSetupGeneratedClass, + toClient(sb)).writeTo(getProjectConfig().getGenericIde().getSrcGen()); + } + // CHECKSTYLE:CONSTANTS-ON + + private static StringConcatenationClient toClient(final CharSequence sb) { + final String content = sb.toString(); + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append(content); + } + }; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/LspBuilderIntegrationFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/LspBuilderIntegrationFragment2.xtend deleted file mode 100644 index f9fc447e36..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/LspBuilderIntegrationFragment2.xtend +++ /dev/null @@ -1,174 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.builder - -import com.google.inject.Inject -import org.apache.logging.log4j.LogManager; -import org.eclipse.osgi.util.NLS -import org.eclipse.xtext.GeneratedMetamodel -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import org.eclipse.xtext.xtext.generator.model.TypeReference - -import static extension org.eclipse.xtext.EcoreUtil2.* -import static extension org.eclipse.xtext.GrammarUtil.* -import org.eclipse.xtext.GrammarUtil - -class LspBuilderIntegrationFragment2 extends AbstractXtextGeneratorFragment { - - static val LOGGER = LogManager.getLogger(LspBuilderIntegrationFragment2) - - @Inject FileAccessFactory fileAccessFactory - - @Inject XtextGeneratorNaming packageNaming - - def private TypeReference createSuffixedTypeReference(String suffix) { - return new TypeReference( - packageNaming.getGenericIdeBasePackage(grammar), GrammarUtil.getSimpleName(grammar) + suffix - ) - } - - def protected TypeReference getLspBuildSetupGeneratedClass() { - createSuffixedTypeReference("LspBuildSetupGenerated") - } - - def protected TypeReference getLspBuildSetupServiceClass() { - createSuffixedTypeReference("LspBuildSetupService") - } - - override generate() { - if (projectConfig.genericIde.enabled) { - if (LOGGER.isInfoEnabled()) { - LOGGER.info(NLS.bind("executing generate for {0}", getClass().getName())); - } - generateServiceRegistration - generateBuildService - generateBuildSetup - } - } - - def generateServiceRegistration() { - fileAccessFactory.createTextFile("META-INF/services/com.avaloq.tools.ddk.xtext.build.ILspLanguageSetup", ''' - «getLspBuildSetupServiceClass.name» - ''').writeTo(projectConfig.genericIde.srcGen) - } - - def generateBuildService() { - fileAccessFactory.createJavaFile(getLspBuildSetupServiceClass, ''' - import java.util.List; - - import com.avaloq.tools.ddk.xtext.build.AbstractDynamicSetupService; - import com.avaloq.tools.ddk.xtext.build.ILspLanguageSetup; - import com.google.common.collect.ImmutableList; - import com.google.inject.Injector; - import com.google.inject.Module; - - /** - * Generated by com.avaloq.tools.ddk.xtext.generator.builder.LspBuilderIntegrationFragment2. - */ - public class «lspBuildSetupServiceClass.simpleName» extends AbstractDynamicSetupService implements ILspLanguageSetup { - - @SuppressWarnings("nls") - private static final String GRAMMAR = "«grammar.name»"; - @SuppressWarnings("nls") - private static final List PARENTS = ImmutableList.of(// - «FOR g: grammar.allUsedGrammars SEPARATOR ' -,'»"«g.name»" //«ENDFOR» - ); - - public Injector doSetup(Module overrideModule, Module... additionalModules) { - return new «grammar.simpleName»LspBuildSetupGenerated(SETUP_LOCK, overrideModule, additionalModules).createInjectorAndDoEMFRegistration(); - } - - @Override - public List getParentLanguages() { - return PARENTS; - } - - @Override - public String getLanguageName() { - return GRAMMAR; - } - - } - ''').writeTo(projectConfig.genericIde.srcGen) - } - - def generateBuildSetup() { - fileAccessFactory.createJavaFile(getLspBuildSetupGeneratedClass, ''' - import org.eclipse.xtext.util.Modules2; - - import com.google.common.collect.ImmutableList; - import com.google.inject.Guice; - import com.google.inject.Injector; - import com.google.inject.Module; - import com.google.inject.util.Modules; - - - /** - * Generated by com.avaloq.tools.ddk.xtext.generator.builder.LspBuilderIntegrationFragment2. - */ - public class «lspBuildSetupGeneratedClass.simpleName» extends «packageNaming.getGenericIdeSetup(grammar)» { - - private final Module overrideModule; - private final Module[] additionalModules; - private final Object lock; - - public «lspBuildSetupGeneratedClass.simpleName»(final Object lock, final Module overrideModule, Module... additionalModules) { - this.lock = lock; - this.overrideModule = overrideModule; - this.additionalModules = additionalModules; - } - - public «lspBuildSetupGeneratedClass.simpleName»(final Module overrideModule, Module... additionalModules) { - this.lock = null; - this.overrideModule = overrideModule; - this.additionalModules = additionalModules; - } - - @Override - public Injector createInjectorAndDoEMFRegistration() { - registerEPackages(); - Injector injector = createInjector(); - if (lock != null) { - synchronized (lock) { - register(injector); - } - } else { - register(injector); - } - return injector; - } - - @Override - public Injector createInjector() { - return Guice.createInjector(getModules()); - } - - protected void registerEPackages() { - «FOR mmd: grammar.metamodelDeclarations.typeSelect(GeneratedMetamodel)» - if («grammar.namespace».«mmd.name».«mmd.name.toFirstUpper()»Package.eINSTANCE == null) { - throw new IllegalStateException("EPackage could not be initialized: " + «grammar.namespace».«mmd.name».«mmd.name.toFirstUpper()»Package.eNS_URI); //$NON-NLS-1$ - } - «ENDFOR» - } - - protected Iterable getModules() { - return ImmutableList. builder().add(Modules.override(Modules2.mixin(new «grammar.name»RuntimeModule(), new «packageNaming.getGenericIdeModule(grammar)»())).with(overrideModule)).add(additionalModules).build(); - } - - } - ''').writeTo(projectConfig.genericIde.srcGen) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/StandaloneBuilderIntegrationFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/StandaloneBuilderIntegrationFragment2.java new file mode 100644 index 0000000000..3d4cc7cc61 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/StandaloneBuilderIntegrationFragment2.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.builder; + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.osgi.util.NLS; +import org.eclipse.xtext.AbstractMetamodelDeclaration; +import org.eclipse.xtext.GeneratedMetamodel; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtend2.lib.StringConcatenationClient; + +import com.google.inject.Inject; + + +@SuppressWarnings("nls") +public class StandaloneBuilderIntegrationFragment2 extends AbstractXtextGeneratorFragment { + + private static final Logger LOGGER = LogManager.getLogger(StandaloneBuilderIntegrationFragment2.class); + + @Inject + private FileAccessFactory fileAccessFactory; + + private TypeReference createSuffixedTypeReference(final String suffix) { + return new TypeReference( + getGrammar().getName() + suffix + ); + } + + protected TypeReference getStandaloneBuildSetupGeneratedClass() { + return createSuffixedTypeReference("StandaloneBuildSetupGenerated"); + } + + protected TypeReference getStandaloneBuildSetupServiceClass() { + return createSuffixedTypeReference("StandaloneBuildSetupService"); + } + + @Override + public void generate() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(NLS.bind("executing generate for {0}", getClass().getName())); + } + generateServiceRegistration(); + generateBuildService(); + generateBuildSetup(); + } + + public void generateServiceRegistration() { + String content = getStandaloneBuildSetupServiceClass().getName() + '\n'; + fileAccessFactory.createTextFile("META-INF/services/com.avaloq.tools.ddk.xtext.build.IDynamicSetupService", + toClient(content)).writeTo(getProjectConfig().getRuntime().getSrcGen()); + } + + // CHECKSTYLE:CONSTANTS-OFF + public void generateBuildService() { + final Grammar grammar = getGrammar(); + final TypeReference standaloneBuildSetupServiceClass = getStandaloneBuildSetupServiceClass(); + StringBuilder sb = new StringBuilder(2048); + sb.append("import java.util.List;\n"); + sb.append('\n'); + sb.append("import com.avaloq.tools.ddk.xtext.build.AbstractDynamicSetupService;\n"); + sb.append("import com.google.common.collect.ImmutableList;\n"); + sb.append("import com.google.inject.Injector;\n"); + sb.append("import com.google.inject.Module;\n"); + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Generated by com.avaloq.tools.ddk.xtext.generator.builder.StandaloneBuilderIntegrationFragment2.\n"); + sb.append(" */\n"); + sb.append("public class ").append(standaloneBuildSetupServiceClass.getSimpleName()).append(" extends AbstractDynamicSetupService {\n"); + sb.append('\n'); + sb.append(" @SuppressWarnings(\"nls\")\n"); + sb.append(" private static final String GRAMMAR = \"").append(grammar.getName()).append("\";\n"); + sb.append(" @SuppressWarnings(\"nls\")\n"); + sb.append(" private static final List PARENTS = ImmutableList.of(//\n"); + List allUsedGrammars = GrammarUtil.allUsedGrammars(grammar); + for (int i = 0; i < allUsedGrammars.size(); i++) { + Grammar g = allUsedGrammars.get(i); + sb.append(" \"").append(g.getName()).append("\" //"); + if (i < allUsedGrammars.size() - 1) { + sb.append(','); + } + sb.append('\n'); + } + sb.append(" );\n"); + sb.append('\n'); + sb.append(" public Injector doSetup(Module overrideModule, Module... additionalModules) {\n"); + sb.append(" return new ").append(GrammarUtil.getSimpleName(grammar)).append("StandaloneBuildSetupGenerated(SETUP_LOCK, overrideModule, additionalModules).createInjectorAndDoEMFRegistration();\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public List getParentLanguages() {\n"); + sb.append(" return PARENTS;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public String getLanguageName() {\n"); + sb.append(" return GRAMMAR;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append("}\n"); + fileAccessFactory.createJavaFile(standaloneBuildSetupServiceClass, + toClient(sb)).writeTo(getProjectConfig().getRuntime().getSrcGen()); + } + + public void generateBuildSetup() { + final Grammar grammar = getGrammar(); + final TypeReference standaloneBuildSetupGeneratedClass = getStandaloneBuildSetupGeneratedClass(); + StringBuilder sb = new StringBuilder(2048); + sb.append("import com.google.common.collect.ImmutableList;\n"); + sb.append("import com.google.inject.Guice;\n"); + sb.append("import com.google.inject.Injector;\n"); + sb.append("import com.google.inject.Module;\n"); + sb.append("import com.google.inject.util.Modules;\n"); + sb.append('\n'); + sb.append('\n'); + sb.append("/**\n"); + sb.append(" * Generated by com.avaloq.tools.ddk.xtext.generator.builder.StandaloneBuilderIntegrationFragment2.\n"); + sb.append(" */\n"); + sb.append("public class ").append(standaloneBuildSetupGeneratedClass.getSimpleName()).append(" extends ").append(GrammarUtil.getSimpleName(grammar)).append("StandaloneSetup {\n"); + sb.append('\n'); + sb.append(" private final Module overrideModule;\n"); + sb.append(" private final Module[] additionalModules;\n"); + sb.append(" private final Object lock;\n"); + sb.append('\n'); + sb.append(" public ").append(standaloneBuildSetupGeneratedClass.getSimpleName()).append("(final Object lock, final Module overrideModule, Module... additionalModules) {\n"); + sb.append(" this.lock = lock;\n"); + sb.append(" this.overrideModule = overrideModule;\n"); + sb.append(" this.additionalModules = additionalModules;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" public ").append(standaloneBuildSetupGeneratedClass.getSimpleName()).append("(final Module overrideModule, Module... additionalModules) {\n"); + sb.append(" this.lock = null;\n"); + sb.append(" this.overrideModule = overrideModule;\n"); + sb.append(" this.additionalModules = additionalModules;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public Injector createInjectorAndDoEMFRegistration() {\n"); + sb.append(" registerEPackages();\n"); + sb.append(" Injector injector = createInjector();\n"); + sb.append(" if (lock != null) {\n"); + sb.append(" synchronized (lock) {\n"); + sb.append(" register(injector);\n"); + sb.append(" }\n"); + sb.append(" } else {\n"); + sb.append(" register(injector);\n"); + sb.append(" }\n"); + sb.append(" return injector;\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" @Override\n"); + sb.append(" public Injector createInjector() {\n"); + sb.append(" return Guice.createInjector(getModules());\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append(" protected void registerEPackages() {\n"); + for (AbstractMetamodelDeclaration decl : grammar.getMetamodelDeclarations()) { + if (decl instanceof GeneratedMetamodel) { + GeneratedMetamodel mmd = (GeneratedMetamodel) decl; + String ns = GrammarUtil.getNamespace(grammar); + String name = mmd.getName(); + String nameUpper = name.substring(0, 1).toUpperCase() + name.substring(1); + sb.append(" if (").append(ns).append('.').append(name).append('.').append(nameUpper).append("Package.eINSTANCE == null) {\n"); + sb.append(" throw new IllegalStateException(\"EPackage could not be initialized: \" + ").append(ns).append('.').append(name).append('.').append(nameUpper).append("Package.eNS_URI); //$NON-NLS-1$\n"); + sb.append(" }\n"); + } + } + sb.append(" }\n"); + sb.append('\n'); + sb.append(" protected Iterable getModules() {\n"); + sb.append(" return ImmutableList. builder().add(Modules.override(new ").append(grammar.getName()).append("RuntimeModule()).with(overrideModule)).add(additionalModules).build();\n"); + sb.append(" }\n"); + sb.append('\n'); + sb.append("}\n"); + fileAccessFactory.createJavaFile(standaloneBuildSetupGeneratedClass, + toClient(sb)).writeTo(getProjectConfig().getRuntime().getSrcGen()); + } + // CHECKSTYLE:CONSTANTS-ON + + private static StringConcatenationClient toClient(final CharSequence sb) { + final String content = sb.toString(); + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append(content); + } + }; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/StandaloneBuilderIntegrationFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/StandaloneBuilderIntegrationFragment2.xtend deleted file mode 100644 index f7813972a1..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/builder/StandaloneBuilderIntegrationFragment2.xtend +++ /dev/null @@ -1,165 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.builder - -import org.apache.logging.log4j.LogManager; -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.osgi.util.NLS -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import com.google.inject.Inject - -import static extension org.eclipse.xtext.GrammarUtil.* -import static extension org.eclipse.xtext.EcoreUtil2.* -import org.eclipse.xtext.xtext.generator.model.TypeReference -import org.eclipse.xtext.GeneratedMetamodel - -class StandaloneBuilderIntegrationFragment2 extends AbstractXtextGeneratorFragment { - - static val LOGGER = LogManager.getLogger(StandaloneBuilderIntegrationFragment2) - - @Inject FileAccessFactory fileAccessFactory - - def private TypeReference createSuffixedTypeReference(String suffix) { - return new TypeReference( - grammar.name + suffix - ) - } - - def protected TypeReference getStandaloneBuildSetupGeneratedClass() { - createSuffixedTypeReference("StandaloneBuildSetupGenerated") - } - - def protected TypeReference getStandaloneBuildSetupServiceClass() { - createSuffixedTypeReference("StandaloneBuildSetupService") - } - - override generate() { - if (LOGGER.isInfoEnabled()) { - LOGGER.info(NLS.bind("executing generate for {0}", getClass().getName())); - } - generateServiceRegistration - generateBuildService - generateBuildSetup - } - - def generateServiceRegistration() { - fileAccessFactory.createTextFile("META-INF/services/com.avaloq.tools.ddk.xtext.build.IDynamicSetupService", ''' - «getStandaloneBuildSetupServiceClass.name» - ''').writeTo(projectConfig.runtime.srcGen) - } - - def generateBuildService() { - fileAccessFactory.createJavaFile(getStandaloneBuildSetupServiceClass, ''' - import java.util.List; - - import com.avaloq.tools.ddk.xtext.build.AbstractDynamicSetupService; - import com.google.common.collect.ImmutableList; - import com.google.inject.Injector; - import com.google.inject.Module; - - /** - * Generated by com.avaloq.tools.ddk.xtext.generator.builder.StandaloneBuilderIntegrationFragment2. - */ - public class «standaloneBuildSetupServiceClass.simpleName» extends AbstractDynamicSetupService { - - @SuppressWarnings("nls") - private static final String GRAMMAR = "«grammar.name»"; - @SuppressWarnings("nls") - private static final List PARENTS = ImmutableList.of(// - «FOR g: grammar.allUsedGrammars SEPARATOR ' -,'»"«g.name»" //«ENDFOR» - ); - - public Injector doSetup(Module overrideModule, Module... additionalModules) { - return new «grammar.simpleName»StandaloneBuildSetupGenerated(SETUP_LOCK, overrideModule, additionalModules).createInjectorAndDoEMFRegistration(); - } - - @Override - public List getParentLanguages() { - return PARENTS; - } - - @Override - public String getLanguageName() { - return GRAMMAR; - } - - } - ''').writeTo(projectConfig.runtime.srcGen) - } - - def generateBuildSetup() { - fileAccessFactory.createJavaFile(getStandaloneBuildSetupGeneratedClass, ''' - import com.google.common.collect.ImmutableList; - import com.google.inject.Guice; - import com.google.inject.Injector; - import com.google.inject.Module; - import com.google.inject.util.Modules; - - - /** - * Generated by com.avaloq.tools.ddk.xtext.generator.builder.StandaloneBuilderIntegrationFragment2. - */ - public class «standaloneBuildSetupGeneratedClass.simpleName» extends «grammar.simpleName»StandaloneSetup { - - private final Module overrideModule; - private final Module[] additionalModules; - private final Object lock; - - public «standaloneBuildSetupGeneratedClass.simpleName»(final Object lock, final Module overrideModule, Module... additionalModules) { - this.lock = lock; - this.overrideModule = overrideModule; - this.additionalModules = additionalModules; - } - - public «standaloneBuildSetupGeneratedClass.simpleName»(final Module overrideModule, Module... additionalModules) { - this.lock = null; - this.overrideModule = overrideModule; - this.additionalModules = additionalModules; - } - - @Override - public Injector createInjectorAndDoEMFRegistration() { - registerEPackages(); - Injector injector = createInjector(); - if (lock != null) { - synchronized (lock) { - register(injector); - } - } else { - register(injector); - } - return injector; - } - - @Override - public Injector createInjector() { - return Guice.createInjector(getModules()); - } - - protected void registerEPackages() { - «FOR mmd: grammar.metamodelDeclarations.typeSelect(GeneratedMetamodel)» - if («grammar.namespace».«mmd.name».«mmd.name.toFirstUpper()»Package.eINSTANCE == null) { - throw new IllegalStateException("EPackage could not be initialized: " + «grammar.namespace».«mmd.name».«mmd.name.toFirstUpper()»Package.eNS_URI); //$NON-NLS-1$ - } - «ENDFOR» - } - - protected Iterable getModules() { - return ImmutableList. builder().add(Modules.override(new «grammar.name»RuntimeModule()).with(overrideModule)).add(additionalModules).build(); - } - - } - ''').writeTo(projectConfig.runtime.srcGen) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/formatting/FormatterFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/formatting/FormatterFragment2.java new file mode 100644 index 0000000000..f693c47ad1 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/formatting/FormatterFragment2.java @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.formatting; + +import com.avaloq.tools.ddk.xtext.formatting.DirectNodeModelStreamer; +import com.avaloq.tools.ddk.xtext.formatting.RegionNodeModelFormatter; +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.osgi.util.NLS; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.formatting.IFormatter; +import org.eclipse.xtext.formatting.INodeModelFormatter; +import org.eclipse.xtext.formatting.INodeModelStreamer; +import org.eclipse.xtext.xtext.generator.AbstractStubGeneratingFragment; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.JavaFileAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtext.xtext.generator.model.XtendFileAccess; +import org.eclipse.xtend2.lib.StringConcatenationClient; + +@SuppressWarnings("nls") +public class FormatterFragment2 extends AbstractStubGeneratingFragment { + + @Inject + private FileAccessFactory fileAccessFactory; + + @Inject + private XtextGeneratorNaming xtextGeneratorNaming; + + @Inject + private GrammarAccessExtensions grammarAccessExtensions; + + /** + * Class-wide logger. + */ + private static final Logger LOGGER = LogManager.getLogger(FormatterFragment2.class); + + // CHECKSTYLE:CONSTANTS-OFF + protected TypeReference getFormatterStub(final Grammar grammar) { + return new TypeReference(xtextGeneratorNaming.getRuntimeBasePackage(grammar) + ".formatting." + GrammarUtil.getSimpleName(grammar) + "Formatter"); + } + // CHECKSTYLE:CONSTANTS-ON + + @Override + public void generate() { + if (!isGenerateStub()) { + return; + } + if (LOGGER.isInfoEnabled()) { + LOGGER.info(NLS.bind("executing generate for {0}", getClass().getName())); + } + + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(IFormatter.class), getFormatterStub(getLanguage().getGrammar())) + .addTypeToType(TypeReference.typeRef(INodeModelFormatter.class), TypeReference.typeRef(RegionNodeModelFormatter.class)) + .addTypeToType(TypeReference.typeRef(INodeModelStreamer.class), TypeReference.typeRef(DirectNodeModelStreamer.class)) + .contributeTo(getLanguage().getRuntimeGenModule()); + if (getProjectConfig().getRuntime().getManifest() != null) { + getProjectConfig().getRuntime().getManifest().getExportedPackages().add(xtextGeneratorNaming.getRuntimeBasePackage(getGrammar()) + ".formatting"); + } + doGenerateStubFile(); + } + + protected void doGenerateStubFile() { + if (!isGenerateStub()) { + return; + } + if (isGenerateXtendStub()) { + final XtendFileAccess xtendFile = doGetXtendStubFile(); + if (xtendFile != null) { + xtendFile.writeTo(getProjectConfig().getRuntime().getSrc()); + } + } else { + final JavaFileAccess javaFile = doGetJavaStubFile(); + if (javaFile != null) { + javaFile.writeTo(getProjectConfig().getRuntime().getSrc()); + } + } + } + + protected XtendFileAccess doGetXtendStubFile() { + final XtendFileAccess xtendFile = fileAccessFactory.createXtendFile(getFormatterStub(getGrammar())); + xtendFile.setResourceSet(getLanguage().getResourceSet()); + + final String stubSimpleName = getFormatterStub(getGrammar()).getSimpleName(); + final TypeReference grammarAccessRef = grammarAccessExtensions.getGrammarAccess(getGrammar()); + + // CHECKSTYLE:CONSTANTS-OFF + xtendFile.setContent(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("import org.eclipse.xtext.formatting.impl.AbstractDeclarativeFormatter"); + target.newLineIfNotEmpty(); + target.append("import org.eclipse.xtext.formatting.impl.FormattingConfig"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append("/**"); + target.newLineIfNotEmpty(); + target.append(" * This class contains custom formatting declarations."); + target.newLineIfNotEmpty(); + target.append(" *"); + target.newLineIfNotEmpty(); + target.append(" * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting"); + target.newLineIfNotEmpty(); + target.append(" * on how and when to use it."); + target.newLineIfNotEmpty(); + target.append(" *"); + target.newLineIfNotEmpty(); + target.append(" * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example"); + target.newLineIfNotEmpty(); + target.append(" */"); + target.newLineIfNotEmpty(); + target.append("class " + stubSimpleName + " extends AbstractDeclarativeFormatter {"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append(" @"); + target.append(TypeReference.typeRef(Inject.class)); + target.append(" extension "); + target.append(grammarAccessRef); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append(" override protected void configureFormatting(FormattingConfig c) {"); + target.newLineIfNotEmpty(); + target.append("// It's usually a good idea to activate the following three statements."); + target.newLineIfNotEmpty(); + target.append("// They will add and preserve newlines around comments"); + target.newLineIfNotEmpty(); + target.append("// c.setLinewrap(0, 1, 2).before(SL_COMMENTRule)"); + target.newLineIfNotEmpty(); + target.append("// c.setLinewrap(0, 1, 2).before(ML_COMMENTRule)"); + target.newLineIfNotEmpty(); + target.append("// c.setLinewrap(0, 1, 1).after(ML_COMMENTRule)"); + target.newLineIfNotEmpty(); + target.append(" }"); + target.newLineIfNotEmpty(); + target.append("}"); + target.newLineIfNotEmpty(); + } + }); + // CHECKSTYLE:CONSTANTS-ON + return xtendFile; + } + + protected JavaFileAccess doGetJavaStubFile() { + final JavaFileAccess javaFile = fileAccessFactory.createJavaFile(getFormatterStub(getGrammar())); + javaFile.setResourceSet(getLanguage().getResourceSet()); + + final String stubSimpleName = getFormatterStub(getGrammar()).getSimpleName(); + final TypeReference grammarAccessRef = grammarAccessExtensions.getGrammarAccess(getGrammar()); + + // CHECKSTYLE:CONSTANTS-OFF + javaFile.setContent(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("import org.eclipse.xtext.formatting.impl.AbstractDeclarativeFormatter;"); + target.newLineIfNotEmpty(); + target.append("import org.eclipse.xtext.formatting.impl.FormattingConfig;"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append("/**"); + target.newLineIfNotEmpty(); + target.append(" * This class contains custom formatting declarations."); + target.newLineIfNotEmpty(); + target.append(" *"); + target.newLineIfNotEmpty(); + target.append(" * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting"); + target.newLineIfNotEmpty(); + target.append(" * on how and when to use it."); + target.newLineIfNotEmpty(); + target.append(" *"); + target.newLineIfNotEmpty(); + target.append(" * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example"); + target.newLineIfNotEmpty(); + target.append(" */"); + target.newLineIfNotEmpty(); + target.append("class " + stubSimpleName + " extends AbstractDeclarativeFormatter {"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append(" @"); + target.append(TypeReference.typeRef(Inject.class)); + target.append(" "); + target.append(grammarAccessRef); + target.append(" grammarAccess;"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append(" @Override"); + target.newLineIfNotEmpty(); + target.append(" protected void configureFormatting(FormattingConfig c) {"); + target.newLineIfNotEmpty(); + target.append("// It's usually a good idea to activate the following three statements."); + target.newLineIfNotEmpty(); + target.append("// They will add and preserve newlines around comments"); + target.newLineIfNotEmpty(); + target.append("// c.setLinewrap(0, 1, 2).before(grammarAccess.getSL_COMMENTRule());"); + target.newLineIfNotEmpty(); + target.append("// c.setLinewrap(0, 1, 2).before(grammarAccess.getML_COMMENTRule());"); + target.newLineIfNotEmpty(); + target.append("// c.setLinewrap(0, 1, 1).after(grammarAccess.getML_COMMENTRule());"); + target.newLineIfNotEmpty(); + target.append(" }"); + target.newLineIfNotEmpty(); + target.append("}"); + target.newLineIfNotEmpty(); + } + }); + // CHECKSTYLE:CONSTANTS-ON + return javaFile; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/formatting/FormatterFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/formatting/FormatterFragment2.xtend deleted file mode 100644 index 5b0738cc93..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/formatting/FormatterFragment2.xtend +++ /dev/null @@ -1,147 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.formatting - -import com.avaloq.tools.ddk.xtext.formatting.DirectNodeModelStreamer -import com.avaloq.tools.ddk.xtext.formatting.RegionNodeModelFormatter -import com.google.inject.Inject -import org.apache.logging.log4j.Logger -import org.apache.logging.log4j.LogManager; -import org.eclipse.osgi.util.NLS -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.formatting.IFormatter -import org.eclipse.xtext.formatting.INodeModelFormatter -import org.eclipse.xtext.formatting.INodeModelStreamer -import org.eclipse.xtext.xtext.generator.AbstractStubGeneratingFragment -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess -import org.eclipse.xtext.xtext.generator.model.TypeReference - -import static org.eclipse.xtext.GrammarUtil.* - -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* - -class FormatterFragment2 extends AbstractStubGeneratingFragment { - - @Inject FileAccessFactory fileAccessFactory - @Inject extension XtextGeneratorNaming - @Inject extension GrammarAccessExtensions - - /** - * Class-wide logger. - */ - static final Logger LOGGER = LogManager::getLogger(typeof(FormatterFragment2)) - - protected def TypeReference getFormatterStub(Grammar grammar) { - new TypeReference(grammar.runtimeBasePackage + '.formatting.' + getSimpleName(grammar) + 'Formatter') - } - - override generate() { - if (!isGenerateStub()) { - return; - } - if (LOGGER.isInfoEnabled()) { - LOGGER.info(NLS.bind("executing generate for {0}", getClass().getName())); - } - - new GuiceModuleAccess.BindingFactory() - .addTypeToType(IFormatter.typeRef, language.grammar.formatterStub) - .addTypeToType(INodeModelFormatter.typeRef, RegionNodeModelFormatter.typeRef) - .addTypeToType(INodeModelStreamer.typeRef, DirectNodeModelStreamer.typeRef) - .contributeTo(language.runtimeGenModule) - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest.exportedPackages += grammar.runtimeBasePackage + '.formatting' - } - doGenerateStubFile() - } - - protected def doGenerateStubFile() { - if(!isGenerateStub) { - return - } - if (isGenerateXtendStub) { - val xtendFile = doGetXtendStubFile - xtendFile?.writeTo(projectConfig.runtime.src) - } else { - val javaFile = doGetJavaStubFile - javaFile?.writeTo(projectConfig.runtime.src) - } - } - - protected def doGetXtendStubFile() { - val xtendFile = fileAccessFactory.createXtendFile(grammar.formatterStub) - xtendFile.resourceSet = language.resourceSet - - xtendFile.content = ''' - import org.eclipse.xtext.formatting.impl.AbstractDeclarativeFormatter - import org.eclipse.xtext.formatting.impl.FormattingConfig - - /** - * This class contains custom formatting declarations. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting - * on how and when to use it. - * - * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example - */ - class «grammar.formatterStub.simpleName» extends AbstractDeclarativeFormatter { - - @«Inject» extension «grammar.grammarAccess» - - override protected void configureFormatting(FormattingConfig c) { - // It's usually a good idea to activate the following three statements. - // They will add and preserve newlines around comments - // c.setLinewrap(0, 1, 2).before(SL_COMMENTRule) - // c.setLinewrap(0, 1, 2).before(ML_COMMENTRule) - // c.setLinewrap(0, 1, 1).after(ML_COMMENTRule) - } - } - ''' - return xtendFile - } - - protected def doGetJavaStubFile() { - val javaFile = fileAccessFactory.createJavaFile(grammar.formatterStub) - javaFile.resourceSet = language.resourceSet - - javaFile.content = ''' - import org.eclipse.xtext.formatting.impl.AbstractDeclarativeFormatter; - import org.eclipse.xtext.formatting.impl.FormattingConfig; - - /** - * This class contains custom formatting declarations. - * - * See https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#formatting - * on how and when to use it. - * - * Also see {@link org.eclipse.xtext.xtext.XtextFormatter} as an example - */ - class «grammar.formatterStub.simpleName» extends AbstractDeclarativeFormatter { - - @«Inject» «grammar.grammarAccess» grammarAccess; - - @Override - protected void configureFormatting(FormattingConfig c) { - // It's usually a good idea to activate the following three statements. - // They will add and preserve newlines around comments - // c.setLinewrap(0, 1, 2).before(grammarAccess.getSL_COMMENTRule()); - // c.setLinewrap(0, 1, 2).before(grammarAccess.getML_COMMENTRule()); - // c.setLinewrap(0, 1, 1).after(grammarAccess.getML_COMMENTRule()); - } - } - ''' - return javaFile - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/languageconstants/LanguageConstantsFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/languageconstants/LanguageConstantsFragment2.java new file mode 100644 index 0000000000..6f8d63f2dd --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/languageconstants/LanguageConstantsFragment2.java @@ -0,0 +1,200 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.languageconstants; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.osgi.util.NLS; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.IXtextGeneratorFileSystemAccess; +import org.eclipse.xtext.xtext.generator.model.JavaFileAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtext.xtext.generator.model.XtextGeneratorFileSystemAccess; +import org.eclipse.xtend2.lib.StringConcatenationClient; + +@SuppressWarnings("nls") +public class LanguageConstantsFragment2 extends AbstractXtextGeneratorFragment { + + @Inject + private FileAccessFactory fileAccessFactory; + + @Inject + private XtextGeneratorNaming xtextGeneratorNaming; + + /** Class-wide logger. */ + private static final Logger LOGGER = LogManager.getLogger(LanguageConstantsFragment2.class); + + /** + * An alternative implementation is to use + * com.avaloq.tools.ddk.xtext.generator.util.GeneratorUtil.getFileExtensions(org.eclipse.xtext.Grammar). + * However, we want to use the preferred file extension. + */ + private String preferredFileExtension; + + private String metamodelSrcGenPath; + + private IXtextGeneratorFileSystemAccess metamodelSrcGen; + + public void setPreferredFileExtension(final String preferredFileExtension) { + this.preferredFileExtension = preferredFileExtension; + } + + public String getMetamodelSrcGenPath() { + return metamodelSrcGenPath; + } + + public void setMetamodelSrcGenPath(final String metamodelSrcGenPath) { + this.metamodelSrcGenPath = metamodelSrcGenPath; + } + + protected IXtextGeneratorFileSystemAccess getMetamodelSrcGen() { + return metamodelSrcGen; + } + + /** + * Returns the preferred file extension. If not manually set, the + * first item in {@link #getFileExtensions() fileExtensions} is returned. + * + * @return the preferred file extension + */ + public String getPreferredFileExtension() { + if (preferredFileExtension != null) { + return preferredFileExtension; + } else if (!getLanguage().getFileExtensions().isEmpty()) { + return getLanguage().getFileExtensions().get(0); + } else { + return GrammarUtil.getSimpleName(getGrammar()).toLowerCase(); + } + } + + @Override + public void generate() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info(NLS.bind("executing generate for {0}", getClass().getName())); + } + if (getLanguage().getFileExtensions().isEmpty()) { + LOGGER.error(NLS.bind("There must be at least one extension for {0}", getGrammar().getName())); + return; + } + doGenerateFiles(); + } + + public void doGenerateFiles() { + final JavaFileAccess constantsFile = doGetConstantsClassFile(); + if (constantsFile != null) { + constantsFile.writeTo(getMetamodelSrcGen()); + } + } + + // CHECKSTYLE:CONSTANTS-OFF + public JavaFileAccess doGetConstantsClassFile() { + final TypeReference typeReference = getTypeReference(getGrammar()); + final JavaFileAccess javaFile = fileAccessFactory.createJavaFile(typeReference); + javaFile.setResourceSet(getLanguage().getResourceSet()); + + final String grammarName = getGrammar().getName(); + final String simpleName = typeReference.getSimpleName(); + final String preferredExt = getPreferredFileExtension().replaceAll("%20", " "); + final String allExtensions = String.join(",", getLanguage().getFileExtensions()); + final String grammarSimpleName = GrammarUtil.getSimpleName(getGrammar()); + + javaFile.setContent(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("/**"); + target.newLineIfNotEmpty(); + target.append(" * Provides language specific constants for " + grammarName + "."); + target.newLineIfNotEmpty(); + target.append(" *"); + target.newLineIfNotEmpty(); + target.append(" * Theses constants are used e.g. by the test plug-ins."); + target.newLineIfNotEmpty(); + target.append(" */"); + target.newLineIfNotEmpty(); + target.append("@SuppressWarnings(\"nls\")"); + target.newLineIfNotEmpty(); + target.append("public final class " + simpleName + " {"); + target.newLineIfNotEmpty(); + target.append(" public static final String GRAMMAR = \"" + grammarName + "\";"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append(" /** Preferred file extension (for testing). */"); + target.newLineIfNotEmpty(); + target.append(" public static final String FILE_EXTENSION = \"" + preferredExt + "\";"); + target.newLineIfNotEmpty(); + target.append(" /** All file extensions. */"); + target.newLineIfNotEmpty(); + target.append(" public static final String FILE_EXTENSIONS = \"" + allExtensions + "\";"); + target.newLineIfNotEmpty(); + target.append(" /** Private constant specifying an URI pattern that match all files of the preferred extension. */"); + target.newLineIfNotEmpty(); + target.append(" private static final String ALL_" + grammarSimpleName.toUpperCase() + "_URI = \"*.\"+FILE_EXTENSION;"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append(" private " + simpleName + "() {}"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append(" /**"); + target.newLineIfNotEmpty(); + target.append(" * An URI pattern that matches all files of the preferred file extension."); + target.newLineIfNotEmpty(); + target.append(" *"); + target.newLineIfNotEmpty(); + target.append(" * @return this pattern"); + target.newLineIfNotEmpty(); + target.append(" */"); + target.newLineIfNotEmpty(); + target.append(" public static final String getAll" + grammarSimpleName + "URI() {"); + target.newLineIfNotEmpty(); + target.append(" return ALL_" + grammarSimpleName.toUpperCase() + "_URI;"); + target.newLineIfNotEmpty(); + target.append(" }"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + target.append("}"); + target.newLineIfNotEmpty(); + } + }); + + return javaFile; + } + // CHECKSTYLE:CONSTANTS-ON + + /** + * Returns the type reference of the Constants class. + * + * @param grammar + * the grammar + * @return the type reference + */ + public TypeReference getTypeReference(final Grammar grammar) { + return new TypeReference(xtextGeneratorNaming.getRuntimeBasePackage(grammar) + "." + GrammarUtil.getSimpleName(grammar) + "Constants"); + } + + @Override + public void initialize(final Injector injector) { + super.initialize(injector); + if (metamodelSrcGenPath != null && !metamodelSrcGenPath.isEmpty()) { + metamodelSrcGen = new XtextGeneratorFileSystemAccess(metamodelSrcGenPath, true); + metamodelSrcGen.initialize(injector); + } else { + metamodelSrcGen = getProjectConfig().getRuntime().getSrcGen(); + } + } + +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/languageconstants/LanguageConstantsFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/languageconstants/LanguageConstantsFragment2.xtend deleted file mode 100644 index 8108686691..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/languageconstants/LanguageConstantsFragment2.xtend +++ /dev/null @@ -1,144 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.languageconstants - -import com.google.inject.Inject -import org.apache.logging.log4j.Logger -import org.apache.logging.log4j.LogManager; -import org.eclipse.osgi.util.NLS -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.GrammarUtil -import static extension org.eclipse.xtext.GrammarUtil.* -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import org.eclipse.xtext.xtext.generator.model.TypeReference -import org.eclipse.xtend.lib.annotations.Accessors -import org.eclipse.xtext.xtext.generator.model.XtextGeneratorFileSystemAccess -import com.google.inject.Injector -import org.eclipse.xtext.xtext.generator.model.IXtextGeneratorFileSystemAccess - -class LanguageConstantsFragment2 extends AbstractXtextGeneratorFragment { - - @Inject FileAccessFactory fileAccessFactory - @Inject extension XtextGeneratorNaming - - /** Class-wide logger. */ - val static Logger LOGGER = LogManager.getLogger(LanguageConstantsFragment2); - - /** - * An alternative implementation is to use - * com.avaloq.tools.ddk.xtext.generator.util.GeneratorUtil.getFileExtensions(org.eclipse.xtext.Grammar). - * However, we want to use the preferred file extension. - */ - @Accessors(PUBLIC_SETTER) String preferredFileExtension - - @Accessors String metamodelSrcGenPath - - @Accessors(PROTECTED_GETTER) IXtextGeneratorFileSystemAccess metamodelSrcGen - - /** - * Returns the preferred file extension. If not manually set, the - * first item in {@link fileExtensions} is returned. - * - * @param grammar - * the grammar for which the preferred file extension applies - * @return the preferred file extension - */ - def String getPreferredFileExtension() { - if (preferredFileExtension !== null) { - return preferredFileExtension - } else if (!language.fileExtensions.empty) { - return language.fileExtensions.get(0); - } else { - return GrammarUtil.getSimpleName(grammar).toLowerCase(); - } - } - - override generate() { - if (LOGGER.isInfoEnabled()) { - LOGGER.info(NLS.bind("executing generate for {0}", class.name)) - } - if (language.fileExtensions.size == 0) { - LOGGER.error(NLS.bind("There must be at least one extension for {0}", grammar.name)) - return - } - doGenerateFiles() - } - - def doGenerateFiles() { - val constantsFile = doGetConstantsClassFile - constantsFile?.writeTo(getMetamodelSrcGen) - } - - def doGetConstantsClassFile() { - val typeReference = grammar.typeReference - val javaFile = fileAccessFactory.createJavaFile(typeReference) - javaFile.resourceSet = language.resourceSet - - javaFile.content = - ''' - /** - * Provides language specific constants for «grammar.name». - * - * Theses constants are used e.g. by the test plug-ins. - */ - @SuppressWarnings("nls") - public final class «typeReference.simpleName» { - public static final String GRAMMAR = "«grammar.name»"; - - /** Preferred file extension (for testing). */ - public static final String FILE_EXTENSION = "«getPreferredFileExtension.replaceAll("%20"," ")»"; - /** All file extensions. */ - public static final String FILE_EXTENSIONS = "«language.fileExtensions.join(",")»"; - /** Private constant specifying an URI pattern that match all files of the preferred extension. */ - private static final String ALL_«grammar.simpleName.toUpperCase()»_URI = "*."+FILE_EXTENSION; - - private «typeReference.simpleName»() {} - - /** - * An URI pattern that matches all files of the preferred file extension. - * - * @return this pattern - */ - public static final String getAll«grammar.simpleName»URI() { - return ALL_«grammar.simpleName.toUpperCase()»_URI; - } - - } - ''' - - return javaFile - } - - /** - * Returns the type reference of the Constants class. - * - * @param grammar - * the grammar - * @return the type reference - */ - def TypeReference getTypeReference(Grammar grammar) { - return new TypeReference(grammar.runtimeBasePackage + "." + GrammarUtil.getSimpleName(grammar) + "Constants") - } - - override initialize(Injector injector) { - super.initialize(injector) - if (!metamodelSrcGenPath.isNullOrEmpty) { - metamodelSrcGen = new XtextGeneratorFileSystemAccess(metamodelSrcGenPath, true) - metamodelSrcGen.initialize(injector) - } else { - metamodelSrcGen = projectConfig.runtime.srcGen - } - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/model/project/ProjectConfig.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/model/project/ProjectConfig.java new file mode 100644 index 0000000000..4f0fd99f5f --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/model/project/ProjectConfig.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.model.project; + +import org.eclipse.xtext.xtext.generator.model.project.StandardProjectConfig; +import org.eclipse.xtext.xtext.generator.model.project.SubProjectConfig; + +@SuppressWarnings("nls") +public class ProjectConfig extends StandardProjectConfig { + + private String runtimeSuffix = ""; + private String testSuffix = "test"; + private String eclipsePluginSuffix = "ui"; + private String genericIdeSuffix = "ide"; + private boolean forceDisableIdeProject = true; + + @Override + protected String computeName(final SubProjectConfig project) { + if (isMavenLayout()) { + return super.computeName(project); + } + if (project == getRuntime()) { + return runtimeSuffix.isEmpty() ? getBaseName() : getBaseName() + "." + runtimeSuffix; + } else if (project == getRuntimeTest()) { + return runtimeSuffix.isEmpty() ? getBaseName() + "." + testSuffix : getBaseName() + "." + runtimeSuffix + "." + testSuffix; + } else if (project == getGenericIde()) { + return getBaseName() + "." + genericIdeSuffix; + } else if (project == getEclipsePlugin()) { + return getBaseName() + "." + eclipsePluginSuffix; + } else if (project == getEclipsePluginTest()) { + return getBaseName() + "." + eclipsePluginSuffix + "." + testSuffix; + } else { + return super.computeName(project); + } + } + + @Override + public void setDefaults() { + super.setDefaults(); + if (forceDisableIdeProject) { + getGenericIde().setEnabled(false); + } + } + + public String getRuntimeSuffix() { + return runtimeSuffix; + } + + public void setRuntimeSuffix(final String runtimeSuffix) { + this.runtimeSuffix = runtimeSuffix; + } + + public String getTestSuffix() { + return testSuffix; + } + + public void setTestSuffix(final String testSuffix) { + this.testSuffix = testSuffix; + } + + public String getEclipsePluginSuffix() { + return eclipsePluginSuffix; + } + + public void setEclipsePluginSuffix(final String eclipsePluginSuffix) { + this.eclipsePluginSuffix = eclipsePluginSuffix; + } + + public String getGenericIdeSuffix() { + return genericIdeSuffix; + } + + public void setGenericIdeSuffix(final String genericIdeSuffix) { + this.genericIdeSuffix = genericIdeSuffix; + } + + public boolean isForceDisableIdeProject() { + return forceDisableIdeProject; + } + + public void setForceDisableIdeProject(final boolean forceDisableIdeProject) { + this.forceDisableIdeProject = forceDisableIdeProject; + } +} + +/* Copyright (c) Avaloq Group AG */ diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/model/project/ProjectConfig.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/model/project/ProjectConfig.xtend deleted file mode 100644 index 47cbc2593d..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/model/project/ProjectConfig.xtend +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.model.project - -import org.eclipse.xtend.lib.annotations.Accessors -import org.eclipse.xtext.xtext.generator.model.project.StandardProjectConfig -import org.eclipse.xtext.xtext.generator.model.project.SubProjectConfig - -class ProjectConfig extends StandardProjectConfig { - - @Accessors var String runtimeSuffix = "" - @Accessors var String testSuffix = "test" - @Accessors var String eclipsePluginSuffix = "ui" - @Accessors var String genericIdeSuffix = "ide" - @Accessors var boolean forceDisableIdeProject = true - - override protected computeName(SubProjectConfig project) { - if (mavenLayout) { - return super.computeName(project) - } - switch project { - case runtime: runtimeSuffix.empty ? '''«baseName»''':'''«baseName».«runtimeSuffix»''' - case runtimeTest: runtimeSuffix.empty ? '''«baseName».«testSuffix»''' : '''«baseName».«runtimeSuffix».«testSuffix»''' - case genericIde: '''«baseName».«genericIdeSuffix»''' - case eclipsePlugin: '''«baseName».«eclipsePluginSuffix»''' - case eclipsePluginTest: '''«baseName».«eclipsePluginSuffix».«testSuffix»''' - default: super.computeName(project) - } - } - - override setDefaults() { - super.setDefaults - if (forceDisableIdeProject) { - genericIde.enabled = false - } - } - -} - -/* Copyright (c) Avaloq Group AG */ \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/modelinference/ModelInferenceFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/modelinference/ModelInferenceFragment2.java new file mode 100644 index 0000000000..387c1d54aa --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/modelinference/ModelInferenceFragment2.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.modelinference; + +import com.avaloq.tools.ddk.xtext.modelinference.IInferredModelAssociations; +import com.avaloq.tools.ddk.xtext.modelinference.IInferredModelAssociator; +import com.avaloq.tools.ddk.xtext.modelinference.InferredModelAssociator; +import com.avaloq.tools.ddk.xtext.ui.editor.findrefs.InferredModelReferenceQueryExecutor; +import org.eclipse.xtext.resource.IDerivedStateComputer; +import org.eclipse.xtext.ui.editor.findrefs.ReferenceQueryExecutor; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; + +@SuppressWarnings("nls") +public class ModelInferenceFragment2 extends AbstractXtextGeneratorFragment { + + @Override + public void generate() { + new GuiceModuleAccess.BindingFactory() + .addTypeToTypeSingleton(TypeReference.typeRef(IInferredModelAssociations.class), TypeReference.typeRef(InferredModelAssociator.class)) + .addTypeToTypeSingleton(TypeReference.typeRef(IInferredModelAssociator.class), TypeReference.typeRef(InferredModelAssociator.class)) + .addTypeToTypeSingleton(TypeReference.typeRef(IDerivedStateComputer.class), TypeReference.typeRef(InferredModelAssociator.class)) + .contributeTo(getLanguage().getRuntimeGenModule()); + if (getProjectConfig().getEclipsePlugin().isEnabled()) { + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(ReferenceQueryExecutor.class), TypeReference.typeRef(InferredModelReferenceQueryExecutor.class)) + .contributeTo(getLanguage().getEclipsePluginGenModule()); + } + + if (getProjectConfig().getRuntime().getManifest() != null) { + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add("com.avaloq.tools.ddk.xtext"); + } + if (getProjectConfig().getEclipsePlugin().getManifest() != null) { + getProjectConfig().getEclipsePlugin().getManifest().getRequiredBundles().add("com.avaloq.tools.ddk.xtext.ui"); + } + if (getProjectConfig().getGenericIde().getManifest() != null) { + getProjectConfig().getGenericIde().getManifest().getRequiredBundles().add("com.avaloq.tools.ddk.xtext.ide"); + } + } + +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/modelinference/ModelInferenceFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/modelinference/ModelInferenceFragment2.xtend deleted file mode 100644 index 8d21063264..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/modelinference/ModelInferenceFragment2.xtend +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.modelinference - -import com.avaloq.tools.ddk.xtext.modelinference.IInferredModelAssociations -import com.avaloq.tools.ddk.xtext.modelinference.IInferredModelAssociator -import com.avaloq.tools.ddk.xtext.modelinference.InferredModelAssociator -import com.avaloq.tools.ddk.xtext.ui.editor.findrefs.InferredModelReferenceQueryExecutor -import org.eclipse.xtext.resource.IDerivedStateComputer -import org.eclipse.xtext.ui.editor.findrefs.ReferenceQueryExecutor -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess - -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* - -class ModelInferenceFragment2 extends AbstractXtextGeneratorFragment { - - override generate() { - new GuiceModuleAccess.BindingFactory() - .addTypeToTypeSingleton(IInferredModelAssociations.typeRef, InferredModelAssociator.typeRef) - .addTypeToTypeSingleton(IInferredModelAssociator.typeRef, InferredModelAssociator.typeRef) - .addTypeToTypeSingleton(IDerivedStateComputer.typeRef, InferredModelAssociator.typeRef) - .contributeTo(language.runtimeGenModule) - - if (projectConfig.eclipsePlugin.enabled) { - new GuiceModuleAccess.BindingFactory() - .addTypeToType(ReferenceQueryExecutor.typeRef, InferredModelReferenceQueryExecutor.typeRef) - .contributeTo(language.eclipsePluginGenModule) - } - - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest.requiredBundles += "com.avaloq.tools.ddk.xtext" - } - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.eclipsePlugin.manifest.requiredBundles += "com.avaloq.tools.ddk.xtext.ui" - } - if (projectConfig.genericIde.manifest !== null) { - projectConfig.genericIde.manifest.requiredBundles += "com.avaloq.tools.ddk.xtext.ide" - } - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AbstractAnnotationAwareAntlrGrammarGenerator.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AbstractAnnotationAwareAntlrGrammarGenerator.java new file mode 100644 index 0000000000..9eb0a8708c --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AbstractAnnotationAwareAntlrGrammarGenerator.java @@ -0,0 +1,271 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.parser.antlr; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.xtext.FlattenedGrammarAccess; +import org.eclipse.xtext.xtext.RuleFilter; +import org.eclipse.xtext.xtext.RuleNames; +import org.eclipse.xtext.xtext.generator.CodeConfig; +import org.eclipse.xtext.xtext.generator.model.IXtextGeneratorFileSystemAccess; +import org.eclipse.xtext.xtext.generator.parser.antlr.AbstractAntlrGrammarWithActionsGenerator; +import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil; +import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrOptions; +import org.eclipse.xtext.xtext.generator.parser.antlr.CombinedGrammarMarker; +import org.eclipse.xtext.xtext.generator.parser.antlr.KeywordHelper; + +import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations; +import com.avaloq.tools.ddk.xtext.generator.parser.common.PredicatesNaming; +import com.google.common.collect.Iterators; +import com.google.inject.Inject; + +// CHECKSTYLE:CONSTANTS-OFF + +@SuppressWarnings({"PMD.UnusedFormalParameter", "nls"}) +public abstract class AbstractAnnotationAwareAntlrGrammarGenerator extends AbstractAntlrGrammarWithActionsGenerator { + + // CHECKSTYLE:CHECK-OFF VisibilityModifier + @Inject + protected GrammarRuleAnnotations annotations; + + @Inject + protected PredicatesNaming predicatesNaming; + // CHECKSTYLE:CHECK-ON VisibilityModifier + + @Inject + private CodeConfig codeConfig; + + private Grammar originalGrammar; + + @Override + public void generate(final Grammar it, final AntlrOptions options, final IXtextGeneratorFileSystemAccess fsa) { + this.keywordHelper = KeywordHelper.getHelper(it); + this.originalGrammar = it; + final RuleFilter filter = new RuleFilter(); + filter.setDiscardUnreachableRules(true); // options.skipUnusedRules + filter.setDiscardTerminalRules(false); // options.skipUnusedRules + final RuleNames ruleNames = RuleNames.getRuleNames(it, true); + final Grammar flattened = new FlattenedGrammarAccess(ruleNames, filter).getFlattenedGrammar(); + new CombinedGrammarMarker(isCombinedGrammar()).attachToEmfObject(flattened); + fsa.generateFile(getGrammarNaming().getParserGrammar(it).getGrammarFileName(), compileParser(flattened, options)); + if (!isCombinedGrammar()) { + fsa.generateFile(getGrammarNaming().getLexerGrammar(it).getGrammarFileName(), compileLexer(flattened, options)); + } + } + + @Override + protected boolean isCombinedGrammar() { + return getGrammarNaming().isCombinedGrammar(originalGrammar); + } + + @Override + protected CharSequence compileLexer(final Grammar it, final AntlrOptions options) { + final StringConcatenation sb = new StringConcatenation(); + sb.append(codeConfig.getFileHeader()); + sb.newLineIfNotEmpty(); + sb.append("lexer grammar "); + sb.append(getGrammarNaming().getLexerGrammar(it).getSimpleName()); + sb.append(';'); + sb.newLineIfNotEmpty(); + sb.append(compileLexerOptions(it, options)); + sb.newLineIfNotEmpty(); + sb.append(compileTokens(it, options)); + sb.newLineIfNotEmpty(); + sb.append(compileLexerHeader(it, options)); + sb.newLineIfNotEmpty(); + sb.append(compileLexerMembers(it, options)); + sb.newLineIfNotEmpty(); + sb.append(compileKeywordRules(it, options)); + sb.newLineIfNotEmpty(); + sb.append(compileTerminalRules(it, options)); + sb.newLineIfNotEmpty(); + return sb; + } + + protected CharSequence compileLexerMembers(final Grammar it, final AntlrOptions options) { + final StringConcatenation sb = new StringConcatenation(); + sb.append("@members {"); + sb.newLine(); + sb.append(" "); + sb.append("protected int getSingleLineCommentRule() {"); + sb.newLine(); + sb.append(" "); + sb.append("return RULE_SL_COMMENT;"); + sb.newLine(); + sb.append(" "); + sb.append("}"); + sb.newLine(); + sb.newLine(); + sb.append(" "); + sb.append("protected int getMultiLineCommentRule() {"); + sb.newLine(); + sb.append(" "); + sb.append("return RULE_ML_COMMENT;"); + sb.newLine(); + sb.append(" "); + sb.append("}"); + sb.newLine(); + sb.newLine(); + sb.append(" "); + sb.append("protected int getEndOfFileRule() {"); + sb.newLine(); + sb.append(" "); + sb.append("return EOF;"); + sb.newLine(); + sb.append(" "); + sb.append("}"); + sb.newLine(); + sb.append("}"); + sb.newLine(); + return sb; + } + + @Override + protected String compileParserImports(final Grammar it, final AntlrOptions options) { + final StringConcatenation sb = new StringConcatenation(); + sb.append("import "); + sb.append(predicatesNaming.getSemanticPredicatesFullName(it)); + sb.append(';'); + sb.newLineIfNotEmpty(); + sb.append("import com.avaloq.tools.ddk.xtext.parser.antlr.ParserContext;"); + sb.newLine(); + return sb.toString(); + } + + protected CharSequence compileParserMemberDeclarations(final Grammar it, final String access) { + final StringConcatenation sb = new StringConcatenation(); + sb.append(access); + sb.append(' '); + sb.append(_grammarAccessExtensions.getGrammarAccess(it).getSimpleName()); + sb.append(" grammarAccess;"); + sb.newLineIfNotEmpty(); + sb.append(access); + sb.append(' '); + sb.append(predicatesNaming.getSemanticPredicatesSimpleName(it)); + sb.append(" predicates;"); + sb.newLineIfNotEmpty(); + sb.append(access); + sb.append(" ParserContext parserContext;"); + sb.newLineIfNotEmpty(); + return sb; + } + + protected CharSequence compileParserSetTokenStreamMethod() { + final StringConcatenation sb = new StringConcatenation(); + sb.append("/**"); + sb.newLine(); + sb.append(" * Set token stream in parser context."); + sb.newLine(); + sb.append(" * @param input Token stream"); + sb.newLine(); + sb.append(" */"); + sb.newLine(); + sb.append("@Override"); + sb.newLine(); + sb.append("public void setTokenStream(TokenStream input) {"); + sb.newLine(); + sb.append(" "); + sb.append("super.setTokenStream(input);"); + sb.newLine(); + sb.append(" "); + sb.append("if(parserContext != null){"); + sb.newLine(); + sb.append(" "); + sb.append("parserContext.setTokenStream(input);"); + sb.newLine(); + sb.append(" "); + sb.append("}"); + sb.newLine(); + sb.append("}"); + sb.newLine(); + return sb; + } + + @Override + protected CharSequence compileKeywordRules(final Grammar it, final AntlrOptions options) { + // implementation from xtext, but keywords are from the given grammar only (which has been flattened and filtered correctly) + final Set allKeywords = getAllKeywords(it, options); + final List allTerminalRules = GrammarUtil.allTerminalRules(it); + + final List syntheticKwAlternatives = new java.util.ArrayList<>(); + for (final String kw : allKeywords) { + final String ruleName = keywordHelper.getRuleName(kw); + syntheticKwAlternatives.add("(FRAGMENT_" + ruleName + ")=> FRAGMENT_" + ruleName + " {$type = " + ruleName + "; }"); + } + for (final AbstractRule rule : allTerminalRules) { + if (rule instanceof TerminalRule) { + final TerminalRule terminalRule = (TerminalRule) rule; + if (!_syntheticTerminalDetector.isSyntheticTerminalRule(terminalRule) && !terminalRule.isFragment()) { + syntheticKwAlternatives.add("(FRAGMENT_" + AntlrGrammarGenUtil.getRuleName(rule) + ")=> FRAGMENT_" + AntlrGrammarGenUtil.getRuleName(rule) + " {$type = " + AntlrGrammarGenUtil.getRuleName(rule) + "; }"); + } + } + } + + final StringConcatenation sb = new StringConcatenation(); + if (options.isBacktrackLexer()) { + sb.append("SYNTHETIC_ALL_KEYWORDS :"); + sb.newLine(); + for (int i = 0; i < syntheticKwAlternatives.size(); i++) { + sb.append(" "); + if (i > 0) { + sb.append("| "); + } + sb.append(syntheticKwAlternatives.get(i)); + sb.newLine(); + } + sb.append(';'); + sb.newLine(); + for (final String kw : allKeywords) { + sb.append("fragment FRAGMENT_"); + sb.append(keywordHelper.getRuleName(kw)); + sb.append(" : \'"); + sb.append(AntlrGrammarGenUtil.toAntlrString(kw)); + sb.append("\';"); + sb.newLine(); + } + } else { + for (final String rule : allKeywords) { + sb.append(compileRule(rule, it, options)); + sb.newLineIfNotEmpty(); + } + } + return sb; + } + + private static Set getAllKeywords(final Grammar flattenedGrammar, final AntlrOptions options) { + final Set result = new TreeSet<>(KeywordHelper.keywordComparator); + final List parserRules = GrammarUtil.allParserRules(flattenedGrammar); + final List enumRules = GrammarUtil.allEnumRules(flattenedGrammar); + final Iterator iter = Iterators.concat(EcoreUtil.getAllContents(parserRules), EcoreUtil.getAllContents(enumRules)); + Iterators.addAll(result, Iterators.transform( + Iterators.filter(iter, Keyword.class), + kw -> options.isIgnoreCase() ? kw.getValue().toLowerCase(Locale.ENGLISH) : kw.getValue() + )); + return Collections.unmodifiableSet(result); + } + +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AbstractAnnotationAwareAntlrGrammarGenerator.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AbstractAnnotationAwareAntlrGrammarGenerator.xtend deleted file mode 100644 index a6b605450c..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AbstractAnnotationAwareAntlrGrammarGenerator.xtend +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.parser.antlr - -import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations -import com.avaloq.tools.ddk.xtext.generator.parser.common.PredicatesNaming -import com.google.common.collect.Iterators -import com.google.inject.Inject -import java.util.Collections -import java.util.Locale -import java.util.TreeSet -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.util.EcoreUtil -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.GrammarUtil -import org.eclipse.xtext.Keyword -import org.eclipse.xtext.xtext.FlattenedGrammarAccess -import org.eclipse.xtext.xtext.RuleFilter -import org.eclipse.xtext.xtext.RuleNames -import org.eclipse.xtext.xtext.generator.CodeConfig -import org.eclipse.xtext.xtext.generator.model.IXtextGeneratorFileSystemAccess -import org.eclipse.xtext.xtext.generator.parser.antlr.AbstractAntlrGrammarWithActionsGenerator -import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrOptions -import org.eclipse.xtext.xtext.generator.parser.antlr.CombinedGrammarMarker -import org.eclipse.xtext.xtext.generator.parser.antlr.KeywordHelper - -import static extension org.eclipse.xtext.GrammarUtil.* -import static extension org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil.* - -abstract class AbstractAnnotationAwareAntlrGrammarGenerator extends AbstractAntlrGrammarWithActionsGenerator { - @Inject protected extension GrammarRuleAnnotations annotations - @Inject protected extension PredicatesNaming predicatesNaming - @Inject CodeConfig codeConfig - - Grammar originalGrammar - - override generate(Grammar it, AntlrOptions options, IXtextGeneratorFileSystemAccess fsa) { - this.keywordHelper = KeywordHelper.getHelper(it) - this.originalGrammar = it - val RuleFilter filter = new RuleFilter(); - filter.discardUnreachableRules = true // options.skipUnusedRules - filter.discardTerminalRules = false // options.skipUnusedRules - val RuleNames ruleNames = RuleNames.getRuleNames(it, true); - val Grammar flattened = new FlattenedGrammarAccess(ruleNames, filter).getFlattenedGrammar(); - new CombinedGrammarMarker(combinedGrammar).attachToEmfObject(flattened) - fsa.generateFile(grammarNaming.getParserGrammar(it).grammarFileName, flattened.compileParser(options)) - if (!isCombinedGrammar) { - fsa.generateFile(grammarNaming.getLexerGrammar(it).grammarFileName, flattened.compileLexer(options)) - } - } - - protected override isCombinedGrammar() { - grammarNaming.isCombinedGrammar(originalGrammar) - } - - protected override compileLexer(Grammar it, AntlrOptions options) ''' - «codeConfig.fileHeader» - lexer grammar «grammarNaming.getLexerGrammar(it).simpleName»; - «compileLexerOptions(options)» - «compileTokens(options)» - «compileLexerHeader(options)» - «compileLexerMembers(options)» - «compileKeywordRules(options)» - «compileTerminalRules(options)» - ''' - - protected def compileLexerMembers(Grammar it, AntlrOptions options) ''' - @members { - protected int getSingleLineCommentRule() { - return RULE_SL_COMMENT; - } - - protected int getMultiLineCommentRule() { - return RULE_ML_COMMENT; - } - - protected int getEndOfFileRule() { - return EOF; - } - } - ''' - - protected override compileParserImports(Grammar it, AntlrOptions options) ''' - import «grammar.semanticPredicatesFullName»; - import com.avaloq.tools.ddk.xtext.parser.antlr.ParserContext; - ''' - - protected def compileParserMemberDeclarations(Grammar it, String access) ''' - «access» «grammarAccess.simpleName» grammarAccess; - «access» «getSemanticPredicatesSimpleName()» predicates; - «access» ParserContext parserContext; - ''' - - protected def compileParserSetTokenStreamMethod() ''' - /** - * Set token stream in parser context. - * @param input Token stream - */ - @Override - public void setTokenStream(TokenStream input) { - super.setTokenStream(input); - if(parserContext != null){ - parserContext.setTokenStream(input); - } - } - ''' - - protected override compileKeywordRules(Grammar it, AntlrOptions options) { - // implementation from xtext, but keywords are from the given grammar only (which has been flattened and filtered correctly) - val allKeywords = getAllKeywords(options) - val allTerminalRules = allTerminalRules - - val synthetic_kw_alternatives = newArrayList - synthetic_kw_alternatives.addAll(allKeywords.indexed.map[ - val ruleName = keywordHelper.getRuleName(value) - return '''(FRAGMENT_«ruleName»)=> FRAGMENT_«ruleName» {$type = «ruleName»; }''' - ]) - synthetic_kw_alternatives.addAll(allTerminalRules.indexed.map[ - if (!isSyntheticTerminalRule(value) && !value.fragment) { - return '''(FRAGMENT_«value.ruleName»)=> FRAGMENT_«value.ruleName» {$type = «value.ruleName»; }''' - } - ].filterNull.toList) - ''' - «IF options.isBacktrackLexer» - SYNTHETIC_ALL_KEYWORDS : - «FOR kw: synthetic_kw_alternatives SEPARATOR ' |'» - «kw» - «ENDFOR» - ; - «FOR kw: allKeywords» - fragment FRAGMENT_«keywordHelper.getRuleName(kw)» : '«kw.toAntlrString()»'; - «ENDFOR» - «ELSE» - «FOR rule:allKeywords» - «rule.compileRule(it, options)» - «ENDFOR» - «ENDIF» - ''' - } - - private static def getAllKeywords(Grammar flattenedGrammar, AntlrOptions options) { - val result = new TreeSet(KeywordHelper.keywordComparator) - val parserRules=GrammarUtil.allParserRules(flattenedGrammar) - val enumRules=GrammarUtil.allEnumRules(flattenedGrammar) - val iter=Iterators.concat(EcoreUtil.getAllContents(parserRules), EcoreUtil.getAllContents(enumRules)) - Iterators.addAll(result, iter.filter(Keyword).map[if (options.ignoreCase) value.toLowerCase(Locale.ENGLISH) else value]) - Collections.unmodifiableSet(result) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrContentAssistGrammarGenerator.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrContentAssistGrammarGenerator.java new file mode 100644 index 0000000000..f75c6f37ae --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrContentAssistGrammarGenerator.java @@ -0,0 +1,1198 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.generator.parser.antlr; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Alternatives; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.CrossReference; +import org.eclipse.xtext.EnumRule; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Group; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.UnorderedGroup; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.Pair; +import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil; +import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrOptions; +import org.eclipse.xtext.xtext.generator.parser.antlr.ContentAssistGrammarNaming; +import org.eclipse.xtext.xtext.generator.parser.antlr.GrammarNaming; + +import com.google.common.collect.Iterables; +import com.google.inject.Inject; + +// CHECKSTYLE:CONSTANTS-OFF + + +/** + * This implementation is strongly based on AntlrContentAssistGrammarGenerator but with a different base class. + * The following extension is supported: + * A datatype grammar rule containing only one ID terminal rule can be annotated + * with @KeywordRule annotation provided a list of words so only these words can + * be accepted by this rule. + * Example: + * /** + * * @KeywordRule(visible, invisible) + * * / + * VisibleKind returns VisibleKind: + * ID + * ; + * The above rule will accept only 'visible' and 'invisible' identifiers. + * This rule in ASMD is called a keyword rule because it is intended to replace + * usages of keywords which shall not be reserved words in the language. + * Reserved words are words that are not allowed to be used in identifiers. + * The above example can therefore replace the following enumeration: + * enum VisibleKind : + * VISIBLE = "visible" + * | INVISIBLE = "invisible" + * ; + * Please note that a corresponding value converter is needed. + * Implementation remark: + * - This template will insert validating semantic predicates in the rule + * - If the rule is used from an alternative a gated semantic predicate will + * be used in the alternative + * - Error messages will be adjusted correspondingly + */ +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class AnnotationAwareAntlrContentAssistGrammarGenerator extends AbstractAnnotationAwareAntlrGrammarGenerator { + + @Inject + private ContentAssistGrammarNaming naming; + + @Override + protected GrammarNaming getGrammarNaming() { + return this.naming; + } + + @Override + protected boolean isParserBackTracking(final Grammar it, final AntlrOptions options) { + return super.isParserBackTracking(it, options) || !GrammarUtil.getAllPredicatedElements(it).isEmpty(); + } + + @Override + protected String compileParserImports(final Grammar it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + if (!this.isCombinedGrammar()) { + builder.append("import java.util.Map;"); + builder.newLine(); + builder.append("import java.util.HashMap;"); + builder.newLine(); + } + builder.newLine(); + builder.append("import java.io.InputStream;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.*;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.parser.*;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.parser.impl.*;"); + builder.newLine(); + builder.append("import org.eclipse.emf.ecore.util.EcoreUtil;"); + builder.newLine(); + builder.append("import org.eclipse.emf.ecore.EObject;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.parser.antlr.XtextTokenStream;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.parser.antlr.XtextTokenStream.HiddenTokens;"); + builder.newLine(); + builder.append("import "); + builder.append(this.getGrammarNaming().getInternalParserSuperClass(it).getName()); + builder.append(';'); + builder.newLineIfNotEmpty(); + builder.append("import org.eclipse.xtext.ide.editor.contentassist.antlr.internal.DFA;"); + builder.newLine(); + builder.append("import "); + builder.append(this._grammarAccessExtensions.getGrammarAccess(it).getName()); + builder.append(';'); + builder.newLineIfNotEmpty(); + builder.append(super.compileParserImports(it, options)); + builder.newLineIfNotEmpty(); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String compileParserMembers(final Grammar it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append('@'); + if (this.isCombinedGrammar()) { + builder.append("parser::"); + } + builder.append("members {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.compileParserMemberDeclarations(it, "protected"), " "); + builder.newLineIfNotEmpty(); + if (!this.isCombinedGrammar()) { + builder.append(" "); + builder.append("private final Map tokenNameToValue = new HashMap();"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append('{'); + builder.newLine(); + for (final String kw : IterableExtensions.sortBy(IterableExtensions.sort(GrammarUtil.getAllKeywords(it)), (final String it1) -> Integer.valueOf(it1.length()))) { + builder.append(" "); + builder.append(" "); + builder.append("tokenNameToValue.put(\""); + builder.append(this.keywordHelper.getRuleName(kw), " "); + builder.append("\", \"\'"); + builder.append(AntlrGrammarGenUtil.toStringInAntlrAction(kw).replace("$", "\\u0024"), " "); + builder.append("\'\");"); + builder.newLineIfNotEmpty(); + } + builder.append(" "); + builder.append('}'); + builder.newLine(); + } + builder.newLine(); + builder.append(" "); + builder.append(this.compileParserSetTokenStreamMethod(), " "); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("public void setPredicates("); + builder.append(this.predicatesNaming.getSemanticPredicatesSimpleName(it), " "); + builder.append(" predicates) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("this.predicates = predicates;"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("public void setGrammarAccess("); + builder.append(this._grammarAccessExtensions.getGrammarAccess(it).getSimpleName(), " "); + builder.append(" grammarAccess) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("this.grammarAccess = grammarAccess;"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append("public void setParserContext(ParserContext parserContext) {"); + builder.newLine(); + builder.append(" "); + builder.append("this.parserContext = parserContext;"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected Grammar getGrammar() {"); + builder.newLine(); + builder.append(" "); + builder.append("return grammarAccess.getGrammar();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected String getValueForTokenName(String tokenName) {"); + builder.newLine(); + if (this.isCombinedGrammar()) { + builder.append(" "); + builder.append("return tokenName;"); + builder.newLine(); + } else { + builder.append(" "); + builder.append("String result = tokenNameToValue.get(tokenName);"); + builder.newLine(); + builder.append(" "); + builder.append("if (result == null)"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("result = tokenName;"); + builder.newLine(); + builder.append(" "); + builder.append("return result;"); + builder.newLine(); + } + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected CharSequence compileRules(final Grammar g, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + final Iterable allRulesAndElements = IterableExtensions.filter(Iterables.concat(Iterables.concat(Iterables.concat(Iterables.concat(Iterables. concat(GrammarUtil.allParserRules(g), GrammarUtil.allEnumRules(g)), GrammarUtil.getAllAlternatives(g)), GrammarUtil.getAllGroups(g)), GrammarUtil.getAllUnorderedGroups(g)), GrammarUtil.getAllAssignments(g)), (final EObject it) -> this._grammarAccessExtensions.isCalled(GrammarUtil.containingRule(it), g)); + for (final EObject rule : allRulesAndElements) { + builder.newLine(); + builder.append(this.compileRule(rule, g, options)); + builder.newLineIfNotEmpty(); + } + if (this.isCombinedGrammar()) { + builder.append(this.compileTerminalRules(g, options)); + builder.newLineIfNotEmpty(); + } + return builder; + } + + @Override + protected CharSequence _compileRule(final ParserRule it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + if (AntlrGrammarGenUtil.isValidEntryRule(it)) { + builder.append("// Entry rule "); + builder.append(this._grammarAccessExtensions.entryRuleName(it)); + builder.newLineIfNotEmpty(); + builder.append(this._grammarAccessExtensions.entryRuleName(it)); + builder.newLineIfNotEmpty(); + if (it.isDefinesHiddenTokens()) { + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append(this.compileInitHiddenTokens(it, options), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + } + builder.append(':'); + builder.newLine(); + builder.append("{ before(grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it))); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this._grammarAccessExtensions.ruleName(it), " "); + builder.newLineIfNotEmpty(); + builder.append("{ after(grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it))); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("EOF"); + builder.newLine(); + builder.append(';'); + builder.newLine(); + if (it.isDefinesHiddenTokens()) { + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append(this.compileRestoreHiddenTokens(it, options), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + } + } + builder.newLine(); + builder.append("// Rule "); + builder.append(AntlrGrammarGenUtil. getOriginalElement(it).getName()); + builder.newLineIfNotEmpty(); + builder.append(this._grammarAccessExtensions.ruleName(it)); + builder.newLineIfNotEmpty(); + if (this.annotations.hasNoBacktrackAnnotation(it)) { + builder.append(" "); + builder.append("// Enclosing rule was annotated with @NoBacktrack"); + builder.newLine(); + builder.append(" "); + builder.append("options { backtrack=false; }"); + builder.newLine(); + } + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append(this.compileInitHiddenTokens(it, options), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(" "); + builder.append(':'); + builder.newLine(); + builder.append(" "); + if (this.annotations.hasValidatingPredicate(it)) { + builder.append(this.annotations.generateValidatingPredicate(it), " "); + } + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.ebnf(it.getAlternatives(), options, false), " "); + builder.newLineIfNotEmpty(); + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append(" "); + builder.append(this.compileRestoreHiddenTokens(it, options), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + return builder; + } + + @Override + protected CharSequence _compileRule(final EnumRule it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("// Rule "); + builder.append(AntlrGrammarGenUtil. getOriginalElement(it).getName()); + builder.newLineIfNotEmpty(); + builder.append(this._grammarAccessExtensions.ruleName(it)); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(" "); + builder.append(this.ebnf(it.getAlternatives(), options, false), " "); + builder.newLineIfNotEmpty(); + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder; + } + + protected CharSequence _compileRule(final Alternatives it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(" "); + boolean hasElements = false; + for (final AbstractElement element : it.getElements()) { + if (!hasElements) { + hasElements = true; + } else { + builder.appendImmediate("\n|", " "); + } + builder.append(this.ebnf(element, options, false), " "); + } + builder.newLineIfNotEmpty(); + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder; + } + + protected CharSequence _compileRule(final Assignment it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(" "); + builder.append(this.assignmentEbnf(it.getTerminal(), it, options, false), " "); + builder.newLineIfNotEmpty(); + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder; + } + + protected CharSequence _compileRule(final UnorderedGroup it, final Grammar grammar, final AntlrOptions options) { + final boolean hasMandatoryContent = IterableExtensions.exists(it.getElements(), (final AbstractElement it1) -> !GrammarUtil.isOptionalCardinality(it1)); + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append("getUnorderedGroupHelper().enter(grammarAccess."); + builder.append(this._grammarAccessExtensions.gaRuleElementAccessor(AntlrGrammarGenUtil. getOriginalElement(it)), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(" "); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it)), " "); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it)), " "); + builder.append("__0"); + builder.newLineIfNotEmpty(); + if (hasMandatoryContent) { + builder.append(" "); + builder.append("{getUnorderedGroupHelper().canLeave(grammarAccess."); + builder.append(this._grammarAccessExtensions.gaRuleElementAccessor(AntlrGrammarGenUtil. getOriginalElement(it)), " "); + builder.append(")}?"); + builder.newLineIfNotEmpty(); + } else { + builder.append(" "); + builder.append('?'); + builder.newLine(); + } + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("getUnorderedGroupHelper().leave(grammarAccess."); + builder.append(this._grammarAccessExtensions.gaRuleElementAccessor(AntlrGrammarGenUtil. getOriginalElement(it)), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append(this.ruleImpl(it, grammar, options)); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(this.ruleImpl(it, grammar, options, 0)); + builder.newLineIfNotEmpty(); + return builder; + } + + protected CharSequence _compileRule(final Group it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(this.ruleImpl(it, grammar, options, 0)); + builder.newLineIfNotEmpty(); + return builder; + } + + /** + * Dispatch method for compileRule. + */ + @Override + protected CharSequence compileRule(final Object it, final Grammar grammar, final AntlrOptions options) { + if (it instanceof ParserRule) { + return _compileRule((ParserRule) it, grammar, options); + } else if (it instanceof EnumRule) { + return _compileRule((EnumRule) it, grammar, options); + } else if (it instanceof Alternatives) { + return _compileRule((Alternatives) it, grammar, options); + } else if (it instanceof Assignment) { + return _compileRule((Assignment) it, grammar, options); + } else if (it instanceof UnorderedGroup) { + return _compileRule((UnorderedGroup) it, grammar, options); + } else if (it instanceof Group) { + return _compileRule((Group) it, grammar, options); + } else { + return super.compileRule(it, grammar, options); + } + } + + protected CharSequence ruleImpl(final UnorderedGroup it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + builder.append("__Impl"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append("boolean selected = false;"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(" "); + builder.append('('); + builder.newLine(); + boolean hasElements = false; + for (final Pair element : IterableExtensions.indexed(it.getElements())) { + if (!hasElements) { + hasElements = true; + } else { + builder.appendImmediate("|", " "); + } + final String originalAccessor = this._grammarAccessExtensions.gaRuleElementAccessor(AntlrGrammarGenUtil. getOriginalElement(it)); + final String originalElementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(element.getValue())); + builder.append(" "); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("{getUnorderedGroupHelper().canSelect(grammarAccess."); + builder.append(originalAccessor, " "); + builder.append(", "); + builder.append(element.getKey(), " "); + builder.append(")}?=>("); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("getUnorderedGroupHelper().select(grammarAccess."); + builder.append(originalAccessor, " "); + builder.append(", "); + builder.append(element.getKey(), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("selected = true;"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append('('); + builder.newLine(); + if (GrammarUtil.isMultipleCardinality(element.getValue())) { + builder.append(" "); + builder.append(" "); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(originalElementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append(" "); + builder.append('('); + builder.append(this.ebnf2(element.getValue(), options, false), " "); + builder.append(')'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(originalElementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append(')'); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(originalElementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append(" "); + builder.append("(("); + builder.append(this.ebnf2(element.getValue(), options, false), " "); + builder.append(")=>"); + builder.append(this.ebnf2(element.getValue(), options, false), " "); + builder.append(")*"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(originalElementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append(')'); + builder.newLine(); + } else { + builder.append(" "); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(originalElementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append('('); + builder.append(this.ebnf2(element.getValue(), options, false), " "); + builder.append(')'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(originalElementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + } + builder.append(" "); + builder.append(" "); + builder.append(')'); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append(')'); + builder.newLine(); + builder.append(" "); + builder.append(')'); + builder.newLine(); + } + builder.append(" "); + builder.append(')'); + builder.newLine(); + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("if (selected)"); + builder.newLine(); + builder.append(" "); + builder.append("getUnorderedGroupHelper().returnFromSelection(grammarAccess."); + builder.append(this._grammarAccessExtensions.gaRuleElementAccessor(AntlrGrammarGenUtil. getOriginalElement(it)), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder; + } + + protected CharSequence ruleImpl(final UnorderedGroup it, final Grammar grammar, final AntlrOptions options, final int index) { + final StringConcatenation builder = new StringConcatenation(); + final String ruleName = AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it)); + final String elementId = this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append(ruleName); + builder.append("__"); + builder.append(elementId); + builder.append("__"); + builder.append(index); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(" "); + builder.append(ruleName, " "); + builder.append("__"); + builder.append(elementId, " "); + builder.append("__Impl"); + builder.newLineIfNotEmpty(); + if (it.getElements().size() > (index + 1)) { + builder.append(" "); + builder.append(ruleName, " "); + builder.append("__"); + builder.append(elementId, " "); + builder.append("__"); + builder.append((index + 1), " "); + builder.append('?'); + builder.newLineIfNotEmpty(); + } + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + builder.newLine(); + if (it.getElements().size() > (index + 1)) { + builder.append(this.ruleImpl(it, grammar, options, (index + 1))); + builder.newLineIfNotEmpty(); + } + return builder; + } + + protected CharSequence ruleImpl(final Group it, final Grammar grammar, final AntlrOptions options, final int index) { + final StringConcatenation builder = new StringConcatenation(); + final String ruleName = AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it)); + final String elementId = this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append(ruleName); + builder.append("__"); + builder.append(elementId); + builder.append("__"); + builder.append(index); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(" "); + builder.append(ruleName, " "); + builder.append("__"); + builder.append(elementId, " "); + builder.append("__"); + builder.append(index, " "); + builder.append("__Impl"); + builder.newLineIfNotEmpty(); + if (it.getElements().size() > (index + 1)) { + builder.append(" "); + builder.append(ruleName, " "); + builder.append("__"); + builder.append(elementId, " "); + builder.append("__"); + builder.append((index + 1), " "); + builder.newLineIfNotEmpty(); + } + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append(ruleName); + builder.append("__"); + builder.append(elementId); + builder.append("__"); + builder.append(index); + builder.append("__Impl"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("int stackSize = keepStackSize();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(':'); + builder.newLine(); + builder.append(this.ebnf(it.getElements().get(index), options, false)); + builder.newLineIfNotEmpty(); + builder.append(';'); + builder.newLine(); + builder.append("finally {"); + builder.newLine(); + builder.append(" "); + builder.append("restoreStackSize(stackSize);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + builder.newLine(); + if (it.getElements().size() > (index + 1)) { + builder.append(this.ruleImpl(it, grammar, options, (index + 1))); + builder.newLineIfNotEmpty(); + } + return builder; + } + + @Override + protected String ebnf(final AbstractElement it, final AntlrOptions options, final boolean supportsActions) { + final StringConcatenation builder = new StringConcatenation(); + final String elementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it)); + final CharSequence paramCfg = this.paramConfig(it); + if ((!GrammarUtil.isOptionalCardinality(it)) && GrammarUtil.isMultipleCardinality(it)) { + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append(paramCfg, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('('); + builder.append(this.ebnf2(it, options, supportsActions), " "); + builder.append(')'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append(paramCfg, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(')'); + builder.newLine(); + builder.append(" "); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append(paramCfg, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('('); + builder.append(this.ebnf2(it, options, supportsActions), " "); + builder.append(")*"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append(paramCfg, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(')'); + builder.newLine(); + builder.append(')'); + builder.newLine(); + } else { + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append(paramCfg, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + if (this.mustBeParenthesized(it)) { + builder.append('('); + builder.append(this.ebnf2(it, options, supportsActions), " "); + builder.append(')'); + } else { + builder.append(this.ebnf2(it, options, supportsActions), " "); + } + builder.append(it.getCardinality(), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append(paramCfg, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(')'); + builder.newLine(); + } + return builder.toString(); + } + + protected CharSequence paramConfig(final AbstractElement it) { + final StringConcatenation builder = new StringConcatenation(); + if (((GrammarUtil.containingRule(it).getAlternatives() == it) && ParserRule.class.isInstance(GrammarUtil.containingRule(it)) + && (!((ParserRule) AntlrGrammarGenUtil. getOriginalElement(GrammarUtil.containingRule(it))).getParameters().isEmpty()))) { + builder.append(", "); + builder.append(AntlrGrammarGenUtil.getParameterConfig((ParserRule) GrammarUtil.containingRule(it))); + builder.newLineIfNotEmpty(); + } + return builder; + } + + @Override + protected String _assignmentEbnf(final AbstractElement it, final Assignment assignment, final AntlrOptions options, final boolean supportsActions) { + final StringConcatenation builder = new StringConcatenation(); + final String elementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.ebnf(it, options, supportsActions), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(')'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String _assignmentEbnf(final CrossReference it, final Assignment assignment, final AntlrOptions options, final boolean supportsActions) { + final StringConcatenation builder = new StringConcatenation(); + final String elementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.crossrefEbnf(it.getTerminal(), it, supportsActions), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(')'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String _assignmentEbnf(final Alternatives it, final Assignment assignment, final AntlrOptions options, final boolean supportsActions) { + final StringConcatenation builder = new StringConcatenation(); + final String elementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('('); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it)), " "); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it)), " "); + builder.append(')'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(')'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String _assignmentEbnf(final RuleCall it, final Assignment assignment, final AntlrOptions options, final boolean supportsActions) { + final StringConcatenation builder = new StringConcatenation(); + final String elementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this._grammarAccessExtensions.ruleName(it.getRule()), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(')'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String _crossrefEbnf(final RuleCall it, final CrossReference ref, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + final String elementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.crossrefEbnf(it.getRule(), it, ref, supportActions), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(')'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String _crossrefEbnf(final Keyword it, final CrossReference ref, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + final String elementAccess = this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil. getOriginalElement(it)); + builder.append('('); + builder.newLine(); + builder.append(" "); + builder.append("{ before(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(super._crossrefEbnf(it, ref, supportActions), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ after(grammarAccess."); + builder.append(elementAccess, " "); + builder.append("); }"); + builder.newLineIfNotEmpty(); + builder.append(')'); + builder.newLine(); + return builder.toString(); + } + + protected String crossrefEbnf(final TerminalRule it, final RuleCall call, final CrossReference ref, final boolean supportActions) { + return this._grammarAccessExtensions.ruleName(it); + } + + protected String crossrefEbnf(final EnumRule it, final RuleCall call, final CrossReference ref, final boolean supportActions) { + return this._grammarAccessExtensions.ruleName(it); + } + + @Override + protected String crossrefEbnf(final AbstractRule it, final RuleCall call, final CrossReference ref, final boolean supportActions) { + if (GrammarUtil.isDatatypeRule(AntlrGrammarGenUtil. getOriginalElement(it))) { + return this._grammarAccessExtensions.ruleName(it); + } + throw new IllegalArgumentException(it.getName() + " is not a datatype rule"); + } + + @Override + protected String _ebnf2(final Alternatives it, final AntlrOptions options, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + return builder.toString(); + } + + @Override + protected String _ebnf2(final Assignment it, final AntlrOptions options, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + return builder.toString(); + } + + @Override + protected String _ebnf2(final Group it, final AntlrOptions options, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + builder.append("__0"); + return builder.toString(); + } + + @Override + protected String _ebnf2(final UnorderedGroup it, final AntlrOptions options, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(AntlrGrammarGenUtil.getContentAssistRuleName(GrammarUtil.containingRule(it))); + builder.append("__"); + builder.append(this._grammarAccessExtensions.gaElementIdentifier(AntlrGrammarGenUtil. getOriginalElement(it))); + return builder.toString(); + } + + @Override + protected String _ebnf2(final RuleCall it, final AntlrOptions options, final boolean supportActions) { + return this._grammarAccessExtensions.ruleName(it.getRule()); + } + + @Override + protected boolean shouldBeSkipped(final TerminalRule it, final Grammar grammar) { + return false; + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrContentAssistGrammarGenerator.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrContentAssistGrammarGenerator.xtend deleted file mode 100644 index b11cb371df..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrContentAssistGrammarGenerator.xtend +++ /dev/null @@ -1,489 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.parser.antlr - -import com.google.inject.Inject -import org.eclipse.xtext.AbstractElement -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.Alternatives -import org.eclipse.xtext.Assignment -import org.eclipse.xtext.CrossReference -import org.eclipse.xtext.EnumRule -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.Group -import org.eclipse.xtext.Keyword -import org.eclipse.xtext.ParserRule -import org.eclipse.xtext.RuleCall -import org.eclipse.xtext.TerminalRule -import org.eclipse.xtext.UnorderedGroup -import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrOptions -import org.eclipse.xtext.xtext.generator.parser.antlr.ContentAssistGrammarNaming - -import static extension org.eclipse.xtext.GrammarUtil.* -import static extension org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil.* - -/** - * This implementation is strongly based on AntlrContentAssistGrammarGenerator but with a different base class. - * The following extension is supported: - * - * A datatype grammar rule containing only one ID terminal rule can be annotated - * with @KeywordRule annotation provided a list of words so only these words can - * be accepted by this rule. - * - * Example: - * - * /** - * * @KeywordRule(visible, invisible) - * * / - * VisibleKind returns VisibleKind: - * ID - * ; - * - * The above rule will accept only 'visible' and 'invisible' identifiers. - * This rule in ASMD is called a keyword rule because it is intended to replace - * usages of keywords which shall not be reserved words in the language. - * Reserved words are words that are not allowed to be used in identifiers. - * - * The above example can therefore replace the following enumeration: - * - * enum VisibleKind : - * VISIBLE = "visible" - * | INVISIBLE = "invisible" - * ; - * - * Please note that a corresponding value converter is needed. - * - * Implementation remark: - * - This template will insert validating semantic predicates in the rule - * - If the rule is used from an alternative a gated semantic predicate will - * be used in the alternative - * - Error messages will be adjusted correspondingly - */ -class AnnotationAwareAntlrContentAssistGrammarGenerator extends AbstractAnnotationAwareAntlrGrammarGenerator { - @Inject - extension ContentAssistGrammarNaming naming - - override protected getGrammarNaming() { - naming - } - - override protected isParserBackTracking(Grammar it, AntlrOptions options) { - super.isParserBackTracking(it, options) || !allPredicatedElements.isEmpty - } - - override protected compileParserImports(Grammar it, AntlrOptions options) ''' - «IF !combinedGrammar» - import java.util.Map; - import java.util.HashMap; - «ENDIF» - - import java.io.InputStream; - import org.eclipse.xtext.*; - import org.eclipse.xtext.parser.*; - import org.eclipse.xtext.parser.impl.*; - import org.eclipse.emf.ecore.util.EcoreUtil; - import org.eclipse.emf.ecore.EObject; - import org.eclipse.xtext.parser.antlr.XtextTokenStream; - import org.eclipse.xtext.parser.antlr.XtextTokenStream.HiddenTokens; - import «grammarNaming.getInternalParserSuperClass(it).name»; - import org.eclipse.xtext.ide.editor.contentassist.antlr.internal.DFA; - import «grammarAccess.name»; - «super.compileParserImports(it, options)» - - ''' - - override protected compileParserMembers(Grammar it, AntlrOptions options) ''' - @«IF combinedGrammar»parser::«ENDIF»members { - «compileParserMemberDeclarations("protected")» - «IF !combinedGrammar» - private final Map tokenNameToValue = new HashMap(); - - { - «FOR kw: allKeywords.sort.sortBy[length]» - tokenNameToValue.put("«keywordHelper.getRuleName(kw)»", "'«kw.toStringInAntlrAction.replace('$', "\\u0024")»'"); - «ENDFOR» - } - «ENDIF» - - «compileParserSetTokenStreamMethod» - - public void setPredicates(«getSemanticPredicatesSimpleName» predicates) { - this.predicates = predicates; - } - - public void setGrammarAccess(«grammarAccess.simpleName» grammarAccess) { - this.grammarAccess = grammarAccess; - } - - public void setParserContext(ParserContext parserContext) { - this.parserContext = parserContext; - } - - @Override - protected Grammar getGrammar() { - return grammarAccess.getGrammar(); - } - - @Override - protected String getValueForTokenName(String tokenName) { - «IF combinedGrammar» - return tokenName; - «ELSE» - String result = tokenNameToValue.get(tokenName); - if (result == null) - result = tokenName; - return result; - «ENDIF» - } - } - ''' - - override protected compileRules(Grammar g, AntlrOptions options) ''' - «FOR rule : (g.allParserRules + g.allEnumRules + g.allAlternatives + g.allGroups + g.allUnorderedGroups + g.allAssignments).filter[containingRule.isCalled(g)]» - - «rule.compileRule(g, options)» - «ENDFOR» - «IF isCombinedGrammar» - «g.compileTerminalRules(options)» - «ENDIF» - ''' - - protected override dispatch compileRule(ParserRule it, Grammar grammar, AntlrOptions options) ''' - «IF isValidEntryRule» - // Entry rule «entryRuleName» - «entryRuleName» - «IF definesHiddenTokens» - @init { - «compileInitHiddenTokens(options)» - } - «ENDIF» - : - { before(grammarAccess.«originalElement.grammarElementAccess»); } - «ruleName» - { after(grammarAccess.«originalElement.grammarElementAccess»); } - EOF - ; - «IF definesHiddenTokens» - finally { - «compileRestoreHiddenTokens(options)» - } - «ENDIF» - «ENDIF» - - // Rule «originalElement.name» - «ruleName» - «IF hasNoBacktrackAnnotation» - // Enclosing rule was annotated with @NoBacktrack - options { backtrack=false; } - «ENDIF» - @init { - «compileInitHiddenTokens(options)» - int stackSize = keepStackSize(); - } - : - «IF hasValidatingPredicate»«generateValidatingPredicate»«ENDIF» - «alternatives.ebnf(options, false)» - ; - finally { - restoreStackSize(stackSize); - «compileRestoreHiddenTokens(options)» - } - ''' - - protected override dispatch compileRule(EnumRule it, Grammar grammar, AntlrOptions options) ''' - // Rule «originalElement.name» - «ruleName()» - @init { - int stackSize = keepStackSize(); - } - : - «alternatives.ebnf(options, false)» - ; - finally { - restoreStackSize(stackSize); - } - ''' - - protected def dispatch compileRule(Alternatives it, Grammar grammar, AntlrOptions options) ''' - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier» - @init { - int stackSize = keepStackSize(); - } - : - «FOR element : elements SEPARATOR '\n|'»«element.ebnf(options, false)»«ENDFOR» - ; - finally { - restoreStackSize(stackSize); - } - ''' - - protected def dispatch compileRule(Assignment it, Grammar grammar, AntlrOptions options) ''' - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier» - @init { - int stackSize = keepStackSize(); - } - : - «terminal.assignmentEbnf(it, options, false)» - ; - finally { - restoreStackSize(stackSize); - } - ''' - - protected def dispatch compileRule(UnorderedGroup it, Grammar grammar, AntlrOptions options) { - val hasMandatoryContent = elements.exists[!isOptionalCardinality] - - ''' - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier()» - @init { - int stackSize = keepStackSize(); - getUnorderedGroupHelper().enter(grammarAccess.«originalElement.gaRuleElementAccessor()»); - } - : - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier()»__0 - «IF hasMandatoryContent» - {getUnorderedGroupHelper().canLeave(grammarAccess.«originalElement.gaRuleElementAccessor()»)}? - «ELSE» - ? - «ENDIF» - ; - finally { - getUnorderedGroupHelper().leave(grammarAccess.«originalElement.gaRuleElementAccessor()»); - restoreStackSize(stackSize); - } - - «ruleImpl(grammar, options)» - - «ruleImpl(grammar, options, 0)» - ''' - } - - protected def dispatch compileRule(Group it, Grammar grammar, AntlrOptions options) ''' - «ruleImpl(grammar, options, 0)» - ''' - - protected def ruleImpl(UnorderedGroup it, Grammar grammar, AntlrOptions options) ''' - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier()»__Impl - @init { - int stackSize = keepStackSize(); - boolean selected = false; - } - : - ( - «FOR element : elements.indexed SEPARATOR '|'» - ( - {getUnorderedGroupHelper().canSelect(grammarAccess.«originalElement.gaRuleElementAccessor()», «element.key»)}?=>( - { - getUnorderedGroupHelper().select(grammarAccess.«originalElement.gaRuleElementAccessor()», «element.key»); - } - { - selected = true; - } - ( - «IF element.value.isMultipleCardinality» - ( - { before(grammarAccess.«element.value.originalElement.grammarElementAccess()»); } - («element.value.ebnf2(options, false)») - { after(grammarAccess.«element.value.originalElement.grammarElementAccess()»); } - ) - ( - { before(grammarAccess.«element.value.originalElement.grammarElementAccess()»); } - ((«element.value.ebnf2(options, false)»)=>«element.value.ebnf2(options, false)»)* - { after(grammarAccess.«element.value.originalElement.grammarElementAccess()»); } - ) - «ELSE» - { before(grammarAccess.«element.value.originalElement.grammarElementAccess()»); } - («element.value.ebnf2(options, false)») - { after(grammarAccess.«element.value.originalElement.grammarElementAccess()»); } - «ENDIF» - ) - ) - ) - «ENDFOR» - ) - ; - finally { - if (selected) - getUnorderedGroupHelper().returnFromSelection(grammarAccess.«originalElement.gaRuleElementAccessor()»); - restoreStackSize(stackSize); - } - ''' - - protected def CharSequence ruleImpl(UnorderedGroup it, Grammar grammar, AntlrOptions options, int index) ''' - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier()»__«index» - @init { - int stackSize = keepStackSize(); - } - : - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier()»__Impl - «IF elements.size > index + 1» - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier()»__«index + 1»? - «ENDIF» - ; - finally { - restoreStackSize(stackSize); - } - - «IF elements.size > index + 1» - «ruleImpl(grammar, options, index + 1)» - «ENDIF» - ''' - - protected def CharSequence ruleImpl(Group it, Grammar grammar, AntlrOptions options, int index) ''' - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier»__«index» - @init { - int stackSize = keepStackSize(); - } - : - «containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier»__«index»__Impl - «IF elements.size > index + 1» - «containingRule().contentAssistRuleName»__«originalElement.gaElementIdentifier»__«index + 1» - «ENDIF» - ; - finally { - restoreStackSize(stackSize); - } - - «containingRule().contentAssistRuleName»__«originalElement.gaElementIdentifier»__«index»__Impl - @init { - int stackSize = keepStackSize(); - } - : - «elements.get(index).ebnf(options, false)» - ; - finally { - restoreStackSize(stackSize); - } - - «IF elements.size > index + 1» - «ruleImpl(grammar, options, index + 1)» - «ENDIF» - ''' - -// TODO - protected override ebnf(AbstractElement it, AntlrOptions options, boolean supportsActions) ''' - «IF !isOptionalCardinality() && isMultipleCardinality()» - ( - ( - { before(grammarAccess.«originalElement.grammarElementAccess»«paramConfig»); } - («ebnf2(options, supportsActions)») - { after(grammarAccess.«originalElement.grammarElementAccess»«paramConfig»); } - ) - ( - { before(grammarAccess.«originalElement.grammarElementAccess»«paramConfig»); } - («ebnf2(options, supportsActions)»)* - { after(grammarAccess.«originalElement.grammarElementAccess»«paramConfig»); } - ) - ) - «ELSE» - ( - { before(grammarAccess.«originalElement.grammarElementAccess»«paramConfig»); } - «IF mustBeParenthesized()»(«ebnf2(options, supportsActions)»)«ELSE»«ebnf2(options, supportsActions)»«ENDIF»«cardinality» - { after(grammarAccess.«originalElement.grammarElementAccess»«paramConfig»); } - ) - «ENDIF» - ''' - - protected def paramConfig(AbstractElement it) ''' - «IF containingRule.alternatives === it && ParserRule.isInstance(containingRule) && !(containingRule.originalElement as ParserRule).parameters.isEmpty» - , «(containingRule as ParserRule).parameterConfig» - «ENDIF» - ''' - - protected override dispatch assignmentEbnf(AbstractElement it, Assignment assignment, AntlrOptions options, boolean supportsActions) ''' - ( - { before(grammarAccess.«originalElement.grammarElementAccess»); } - «ebnf(options, supportsActions)» - { after(grammarAccess.«originalElement.grammarElementAccess»); } - ) - ''' - - protected override dispatch assignmentEbnf(CrossReference it, Assignment assignment, AntlrOptions options, boolean supportsActions) ''' - ( - { before(grammarAccess.«originalElement.grammarElementAccess»); } - «terminal.crossrefEbnf(it, supportsActions)» - { after(grammarAccess.«originalElement.grammarElementAccess»); } - ) - ''' - - protected override dispatch assignmentEbnf(Alternatives it, Assignment assignment, AntlrOptions options, boolean supportsActions) ''' - ( - { before(grammarAccess.«originalElement.grammarElementAccess»); } - («containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier») - { after(grammarAccess.«originalElement.grammarElementAccess»); } - ) - ''' - - protected override dispatch assignmentEbnf(RuleCall it, Assignment assignment, AntlrOptions options, boolean supportsActions) ''' - ( - { before(grammarAccess.«originalElement.grammarElementAccess»); } - «rule.ruleName» - { after(grammarAccess.«originalElement.grammarElementAccess»); } - ) - ''' - - protected dispatch override crossrefEbnf(RuleCall it, CrossReference ref, boolean supportActions) ''' - ( - { before(grammarAccess.«originalElement.grammarElementAccess»); } - «rule.crossrefEbnf(it, ref, supportActions)» - { after(grammarAccess.«originalElement.grammarElementAccess»); } - ) - ''' - - protected dispatch override crossrefEbnf(Keyword it, CrossReference ref, boolean supportActions) ''' - ( - { before(grammarAccess.«originalElement.grammarElementAccess»); } - «super._crossrefEbnf(it, ref, supportActions)» - { after(grammarAccess.«originalElement.grammarElementAccess»); } - ) - ''' - - protected dispatch def crossrefEbnf(TerminalRule it, RuleCall call, CrossReference ref, boolean supportActions) { - ruleName - } - - protected dispatch def crossrefEbnf(EnumRule it, RuleCall call, CrossReference ref, boolean supportActions) { - ruleName - } - - protected def dispatch crossrefEbnf(AbstractRule it, RuleCall call, CrossReference ref, boolean supportActions) { - if (originalElement.isDatatypeRule) { - return ruleName - } - throw new IllegalArgumentException(it.name + " is not a datatype rule") - } - - override protected dispatch ebnf2(Alternatives it, AntlrOptions options, boolean supportActions) { - '''«containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier»''' - } - - override protected dispatch ebnf2(Assignment it, AntlrOptions options, boolean supportActions) { - '''«containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier»''' - } - - override protected dispatch ebnf2(Group it, AntlrOptions options, boolean supportActions) { - '''«containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier»__0''' - } - - override protected dispatch ebnf2(UnorderedGroup it, AntlrOptions options, boolean supportActions) { - '''«containingRule.contentAssistRuleName»__«originalElement.gaElementIdentifier»''' - } - - override protected dispatch ebnf2(RuleCall it, AntlrOptions options, boolean supportActions) { - rule.ruleName - } - - override protected shouldBeSkipped(TerminalRule it, Grammar grammar) { - false - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrGrammarGenerator.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrGrammarGenerator.java new file mode 100644 index 0000000000..0fc9780970 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrGrammarGenerator.java @@ -0,0 +1,1109 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.generator.parser.antlr; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Action; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.CrossReference; +import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.EnumLiteralDeclaration; +import org.eclipse.xtext.EnumRule; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.UnorderedGroup; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xbase.lib.ListExtensions; +import org.eclipse.xtext.xbase.lib.StringExtensions; +import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil; +import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrOptions; +import org.eclipse.xtext.xtext.generator.parser.antlr.GrammarNaming; + +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +// CHECKSTYLE:CONSTANTS-OFF + +/** + * This implementation is strongly based on AntlrGrammarGenerator but with a different base class. + * The following extension is supported: + * + * A datatype grammar rule containing only one ID terminal rule can be annotated + * with @KeywordRule annotation provided a list of words so only these words can + * be accepted by this rule. + * + * Example: + * + * /** + * * @KeywordRule(visible, invisible) + * * / + * VisibleKind returns VisibleKind: + * ID + * ; + * + * The above rule will accept only 'visible' and 'invisible' identifiers. + * This rule in ASMD is called a keyword rule because it is intended to replace + * usages of keywords which shall not be reserved words in the language. + * Reserved words are words that are not allowed to be used in identifiers. + * + * The above example can therefore replace the following enumeration: + * + * enum VisibleKind : + * VISIBLE = "visible" + * | INVISIBLE = "invisible" + * ; + * + * Please note that a corresponding value converter is needed. + * + * Implementation remark: + * - This template will insert validating semantic predicates in the rule + * - If the rule is used from an alternative a gated semantic predicate will + * be used in the alternative + * - Error messages will be adjusted correspondingly + */ +@Singleton +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class AnnotationAwareAntlrGrammarGenerator extends AbstractAnnotationAwareAntlrGrammarGenerator { + + @Inject + private GrammarNaming naming; + + private String lexerSuperClassName = ""; + + public void setLexerSuperClassName(final String lexerSuperClassName) { + this.lexerSuperClassName = lexerSuperClassName; + } + + @Override + protected GrammarNaming getGrammarNaming() { + return this.naming; + } + + @Override + protected String compileParserImports(final Grammar it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.newLine(); + builder.append("import org.eclipse.xtext.*;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.parser.*;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.parser.impl.*;"); + builder.newLine(); + builder.append("import org.eclipse.emf.ecore.util.EcoreUtil;"); + builder.newLine(); + builder.append("import org.eclipse.emf.ecore.EObject;"); + builder.newLine(); + if (!GrammarUtil.allEnumRules(it).isEmpty()) { + builder.append("import org.eclipse.emf.common.util.Enumerator;"); + builder.newLine(); + } + builder.append("import "); + builder.append(this.getGrammarNaming().getInternalParserSuperClass(it).getName()); + builder.append(';'); + builder.newLineIfNotEmpty(); + builder.append("import org.eclipse.xtext.parser.antlr.XtextTokenStream;"); + builder.newLine(); + builder.append("import org.eclipse.xtext.parser.antlr.XtextTokenStream.HiddenTokens;"); + builder.newLine(); + if ((!IterableExtensions.isEmpty(Iterables.filter(Iterables.concat(ListExtensions.map(GrammarUtil.allParserRules(it), (ParserRule it1) -> EcoreUtil2.eAllContentsAsList(it1))), UnorderedGroup.class))) && options.isBacktrack()) { + builder.append("import org.eclipse.xtext.parser.antlr.IUnorderedGroupHelper.UnorderedGroupState;"); + builder.newLine(); + } + builder.append("import org.eclipse.xtext.parser.antlr.AntlrDatatypeRuleToken;"); + builder.newLine(); + builder.append("import "); + builder.append(this._grammarAccessExtensions.getGrammarAccess(it).getName()); + builder.append(';'); + builder.newLineIfNotEmpty(); + builder.append(super.compileParserImports(it, options)); + builder.newLineIfNotEmpty(); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String compileParserMembers(final Grammar it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.newLine(); + builder.append('@'); + if (this.isCombinedGrammar()) { + builder.append("parser::"); + } + builder.append("members {"); + builder.newLineIfNotEmpty(); + builder.newLine(); + if (options.isBacktrack()) { + builder.append("/*"); + builder.newLine(); + builder.append(" "); + builder.append("This grammar contains a lot of empty actions to work around a bug in ANTLR."); + builder.newLine(); + builder.append(" "); + builder.append("Otherwise the ANTLR tool will create synpreds that cannot be compiled in some rare cases."); + builder.newLine(); + builder.append("*/"); + builder.newLine(); + builder.newLine(); + } + builder.append(" "); + builder.append(this.compileParserMemberDeclarations(it, "private"), " "); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("public "); + builder.append(this.naming.getInternalParserClass(it).getSimpleName(), " "); + builder.append("(TokenStream input, "); + builder.append(this._grammarAccessExtensions.getGrammarAccess(it).getSimpleName(), " "); + builder.append(" grammarAccess, ParserContext parserContext, "); + builder.append(this.predicatesNaming.getSemanticPredicatesSimpleName(it), " "); + builder.append(" predicates) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("this(input);"); + builder.newLine(); + builder.append(" "); + builder.append("this.grammarAccess = grammarAccess;"); + builder.newLine(); + builder.append(" "); + builder.append("this.predicates = predicates;"); + builder.newLine(); + builder.append(" "); + builder.append("this.parserContext = parserContext;"); + builder.newLine(); + builder.append(" "); + builder.append("parserContext.setTokenStream(input);"); + builder.newLine(); + builder.append(" "); + builder.append("registerRules(grammarAccess.getGrammar());"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append(this.compileParserSetTokenStreamMethod(), " "); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected String getFirstRuleName() {"); + builder.newLine(); + builder.append(" "); + builder.append("return \""); + builder.append(AntlrGrammarGenUtil.getOriginalElement(IterableExtensions.head(GrammarUtil.allParserRules(it))).getName(), " "); + builder.append("\";"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected "); + builder.append(this._grammarAccessExtensions.getGrammarAccess(it).getSimpleName(), " "); + builder.append(" getGrammarAccess() {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("return grammarAccess;"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected String compileRuleCatch(final Grammar it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.newLine(); + builder.append("@rulecatch {"); + builder.newLine(); + builder.append(" "); + builder.append("catch (RecognitionException re) {"); + builder.newLine(); + builder.append(" "); + builder.append("recover(input,re);"); + builder.newLine(); + builder.append(" "); + builder.append("appendSkippedTokens();"); + builder.newLine(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } + + @Override + protected boolean shouldBeSkipped(final TerminalRule it, final Grammar grammar) { + return false; + } + + @Override + protected CharSequence _compileRule(final ParserRule it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + if (AntlrGrammarGenUtil.isValidEntryRule(it)) { + builder.append(this.compileEntryRule(it, grammar, options)); + builder.newLineIfNotEmpty(); + } + builder.newLine(); + builder.append(this.compileEBNF(it, options)); + builder.newLineIfNotEmpty(); + return builder; + } + + protected String compileEntryRule(final ParserRule it, final Grammar grammar, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("// Entry rule "); + builder.append(this._grammarAccessExtensions.entryRuleName(AntlrGrammarGenUtil.getOriginalElement(it))); + builder.newLineIfNotEmpty(); + builder.append(this._grammarAccessExtensions.entryRuleName(AntlrGrammarGenUtil.getOriginalElement(it))); + builder.append(" returns "); + builder.append(this.compileEntryReturns(it, options)); + builder.append(this.compileEntryInit(it, options)); + builder.append(':'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ "); + builder.append(this.newCompositeNode(it), " "); + builder.append(" }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("iv_"); + builder.append(this._grammarAccessExtensions.ruleName(AntlrGrammarGenUtil.getOriginalElement(it)), " "); + builder.append('='); + builder.append(this._grammarAccessExtensions.ruleName(it), " "); + builder.append(AntlrGrammarGenUtil.getDefaultArgumentList(it), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("{ $current=$iv_"); + builder.append(this._grammarAccessExtensions.ruleName(it), " "); + builder.append(".current"); + if (GrammarUtil.isDatatypeRule(AntlrGrammarGenUtil.getOriginalElement(it))) { + builder.append(".getText()"); + } + builder.append("; }"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("EOF;"); + builder.newLine(); + builder.append(this.compileEntryFinally(it, options)); + builder.newLineIfNotEmpty(); + return builder.toString(); + } + + protected String compileEntryReturns(final ParserRule it, final AntlrOptions options) { + if (GrammarUtil.isDatatypeRule(AntlrGrammarGenUtil.getOriginalElement(it))) { + return "[String current=null]"; + } else { + final StringConcatenation builder = new StringConcatenation(); + builder.append('['); + builder.append(this.getCurrentType()); + builder.append(" current=null]"); + return builder.toString(); + } + } + + @Override + protected String compileInit(final AbstractRule it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + if (it instanceof ParserRule) { + builder.append(AntlrGrammarGenUtil.getParameterList((ParserRule) it, !this.isPassCurrentIntoFragment(), this.getCurrentType())); + } + builder.append(" returns "); + builder.append(this.compileReturns(it, options)); + builder.newLineIfNotEmpty(); + if (this.annotations.hasNoBacktrackAnnotation(it)) { + builder.append("// Enclosing rule was annotated with @NoBacktrack"); + builder.newLine(); + builder.append("options { backtrack=false; }"); + builder.newLine(); + } + builder.append("@init {"); + builder.newLine(); + builder.append(" "); + builder.append("enterRule();"); + builder.newLine(); + builder.append(" "); + builder.append(this.compileInitHiddenTokens(it, options), " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.compileInitUnorderedGroups(it, options), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + builder.append("@after {"); + builder.newLine(); + builder.append(" "); + builder.append("leaveRule();"); + builder.newLine(); + builder.append('}'); + return builder.toString(); + } + + protected CharSequence compileReturns(final AbstractRule it, final AntlrOptions options) { + if (it instanceof EnumRule) { + return "[Enumerator current=null]"; + } + if (it instanceof ParserRule) { + if (GrammarUtil.isDatatypeRule(AntlrGrammarGenUtil.getOriginalElement((ParserRule) it))) { + return "[AntlrDatatypeRuleToken current=new AntlrDatatypeRuleToken()]"; + } + if (GrammarUtil.isEObjectFragmentRule(AntlrGrammarGenUtil.getOriginalElement((ParserRule) it))) { + final StringConcatenation builder = new StringConcatenation(); + builder.append('['); + builder.append(this.getCurrentType()); + builder.append(" current=in_current]"); + return builder; + } + final StringConcatenation builder1 = new StringConcatenation(); + builder1.append('['); + builder1.append(this.getCurrentType()); + builder1.append(" current=null]"); + return builder1; + } + throw new IllegalStateException("Unexpected rule: " + it); + } + + @Override + protected String _dataTypeEbnf2(final Keyword it, final boolean supportActions) { + if (supportActions) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("kw="); + builder.append(super._dataTypeEbnf2(it, supportActions)); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("$current.merge(kw);"); + builder.newLine(); + builder.append(" "); + builder.append(this.newLeafNode(it, "kw"), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else { + return super._dataTypeEbnf2(it, supportActions); + } + } + + @Override + protected String _ebnf2(final Action it, final AntlrOptions options, final boolean supportActions) { + if (supportActions) { + final StringConcatenation builder = new StringConcatenation(); + if (options.isBacktrack()) { + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("/* */"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + } + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("$current = forceCreateModelElement"); + if (it.getFeature() != null) { + builder.append("And"); + builder.append(StringExtensions.toFirstUpper(this._grammarAccessExtensions.setOrAdd(it)), " "); + } + builder.append('('); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil.getOriginalElement(it)), " "); + builder.append(','); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("$current);"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else { + return super._ebnf2(it, options, supportActions); + } + } + + @Override + protected String _ebnf2(final Keyword it, final AntlrOptions options, final boolean supportActions) { + if (!supportActions) { + return super._ebnf2(it, options, supportActions); + } else if (GrammarUtil.isAssigned(it)) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(super._ebnf2(it, options, supportActions)); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append(this.newLeafNode(it, this._grammarAccessExtensions.localVar(GrammarUtil.containingAssignment(it), it)), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else { + final StringConcatenation builder1 = new StringConcatenation(); + builder1.append(this._grammarAccessExtensions.localVar(it)); + builder1.append('='); + builder1.append(super._ebnf2(it, options, supportActions)); + builder1.newLineIfNotEmpty(); + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append(this.newLeafNode(it, this._grammarAccessExtensions.localVar(it)), " "); + builder1.newLineIfNotEmpty(); + builder1.append('}'); + builder1.newLine(); + return builder1.toString(); + } + } + + @Override + protected String _ebnf2(final EnumLiteralDeclaration it, final AntlrOptions options, final boolean supportActions) { + if (!supportActions) { + return super._ebnf2(it, options, supportActions); + } else { + final StringConcatenation builder = new StringConcatenation(); + builder.append(this._grammarAccessExtensions.localVar(it)); + builder.append('='); + builder.append(super._ebnf2(it, options, supportActions)); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("$current = grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil.getOriginalElement(it)), " "); + builder.append(".getEnumLiteral().getInstance();"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.newLeafNode(it, this._grammarAccessExtensions.localVar(it)), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } + } + + @Override + protected String crossrefEbnf(final AbstractRule it, final RuleCall call, final CrossReference ref, final boolean supportActions) { + if (supportActions) { + if (it instanceof EnumRule || it instanceof ParserRule) { + final StringConcatenation builder = new StringConcatenation(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append(this.newCompositeNode(ref), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + builder.append(this._grammarAccessExtensions.ruleName(it)); + builder.append(AntlrGrammarGenUtil.getArgumentList(call, this.isPassCurrentIntoFragment(), (!supportActions))); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("afterParserOrEnumRuleCall();"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else if (it instanceof TerminalRule) { + final StringConcatenation builder1 = new StringConcatenation(); + builder1.append(this._grammarAccessExtensions.localVar(GrammarUtil.containingAssignment(ref))); + builder1.append('='); + builder1.append(this._grammarAccessExtensions.ruleName(it)); + builder1.newLineIfNotEmpty(); + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append(this.newLeafNode(ref, this._grammarAccessExtensions.localVar(GrammarUtil.containingAssignment(ref))), " "); + builder1.newLineIfNotEmpty(); + builder1.append('}'); + builder1.newLine(); + return builder1.toString(); + } else { + throw new IllegalStateException("crossrefEbnf is not supported for " + it); + } + } else { + return super.crossrefEbnf(it, call, ref, supportActions); + } + } + + @Override + protected String _assignmentEbnf(final AbstractElement it, final Assignment assignment, final AntlrOptions options, final boolean supportActions) { + if (supportActions) { + final StringConcatenation builder = new StringConcatenation(); + builder.append(this._grammarAccessExtensions.localVar(assignment, it)); + builder.append('='); + builder.append(super._assignmentEbnf(it, assignment, options, supportActions)); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("if ($current==null) {"); + builder.newLine(); + builder.append(" "); + builder.append("$current = "); + builder.append(this.createModelElement(assignment), " "); + builder.append(';'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(" "); + builder.append(this._grammarAccessExtensions.setOrAdd(assignment), " "); + builder.append("WithLastConsumed($current, \""); + builder.append(assignment.getFeature(), " "); + builder.append("\", "); + builder.append(this._grammarAccessExtensions.localVar(assignment, it), " "); + if (GrammarUtil.isBooleanAssignment(assignment)) { + builder.append(" != null"); + } + builder.append(", "); + builder.append(this._grammarAccessExtensions.toStringLiteral(assignment.getTerminal()), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else { + return super._assignmentEbnf(it, assignment, options, supportActions); + } + } + + @Override + protected boolean isPassCurrentIntoFragment() { + return true; + } + + protected CharSequence createModelElement(final EObject grammarElement) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("createModelElement(grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil.getOriginalElement(GrammarUtil.containingParserRule(grammarElement)))); + builder.append(')'); + return builder; + } + + protected CharSequence createModelElementForParent(final EObject grammarElement) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("createModelElementForParent(grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil.getOriginalElement(GrammarUtil.containingParserRule(grammarElement)))); + builder.append(')'); + return builder; + } + + protected CharSequence newCompositeNode(final EObject it) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("newCompositeNode(grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil.getOriginalElement(it))); + builder.append(");"); + return builder; + } + + protected CharSequence newLeafNode(final EObject it, final String token) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("newLeafNode("); + builder.append(token); + builder.append(", grammarAccess."); + builder.append(this._grammarAccessExtensions.grammarElementAccess(AntlrGrammarGenUtil.getOriginalElement(it))); + builder.append(");"); + return builder; + } + + /** + * Add gated predicate, if necessary, before the action preceding the rule call. + */ + @Override + protected String _dataTypeEbnf2(final RuleCall it, final boolean supportActions) { + if (supportActions) { + final AbstractRule rule = it.getRule(); + if ((rule instanceof EnumRule && GrammarUtil.isAssigned(it)) || (rule instanceof ParserRule && GrammarUtil.isAssigned(it))) { + return super._dataTypeEbnf2(it, supportActions); + } else if (rule instanceof EnumRule || rule instanceof ParserRule) { + final StringConcatenation builder = new StringConcatenation(); + if (this.annotations.isGatedPredicateRequired(it)) { + builder.append(this.annotations.generateGatedPredicate(it)); + } + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append(this.newCompositeNode(it), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + builder.append(this._grammarAccessExtensions.localVar(it)); + builder.append('='); + builder.append(super._dataTypeEbnf2(it, supportActions)); + builder.append(AntlrGrammarGenUtil.getArgumentList(it, this.isPassCurrentIntoFragment(), (!supportActions))); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("$current.merge("); + builder.append(this._grammarAccessExtensions.localVar(it), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("afterParserOrEnumRuleCall();"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else if (rule instanceof TerminalRule) { + final StringConcatenation builder1 = new StringConcatenation(); + builder1.append(this._grammarAccessExtensions.localVar(it)); + builder1.append('='); + builder1.append(super._dataTypeEbnf2(it, supportActions)); + builder1.newLineIfNotEmpty(); + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append("$current.merge("); + builder1.append(this._grammarAccessExtensions.localVar(it), " "); + builder1.append(");"); + builder1.newLineIfNotEmpty(); + builder1.append('}'); + builder1.newLine(); + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append(this.newLeafNode(it, this._grammarAccessExtensions.localVar(it)), " "); + builder1.newLineIfNotEmpty(); + builder1.append('}'); + builder1.newLine(); + return builder1.toString(); + } else { + return super._dataTypeEbnf2(it, supportActions); + } + } else { + return super._dataTypeEbnf2(it, supportActions); + } + } + + /** + * Inserts validating predicate only. Gated predicates will be inserted in alternatives if needed. + */ + @Override + protected String compileEBNF(final AbstractRule it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.append("// Rule "); + builder.append(AntlrGrammarGenUtil.getOriginalElement(it).getName()); + builder.newLineIfNotEmpty(); + builder.append(this._grammarAccessExtensions.ruleName(it)); + builder.append(this.compileInit(it, options)); + builder.append(':'); + builder.newLineIfNotEmpty(); + if ((it instanceof ParserRule) && GrammarUtil.isDatatypeRule(AntlrGrammarGenUtil.getOriginalElement(it))) { + builder.append(" "); + if (this.annotations.hasValidatingPredicate(it)) { + builder.append(this.annotations.generateValidatingPredicate(it), " "); + } + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this.dataTypeEbnf(it.getAlternatives(), true), " "); + builder.newLineIfNotEmpty(); + } else { + builder.append(" "); + builder.append(this.ebnf(it.getAlternatives(), options, true), " "); + builder.newLineIfNotEmpty(); + } + builder.append(';'); + builder.newLine(); + builder.append(this.compileFinally(it, options)); + builder.newLineIfNotEmpty(); + return builder.toString(); + } + + @Override + protected String dataTypeEbnf(final AbstractElement it, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + if (this.mustBeParenthesized(it)) { + builder.append('('); + builder.newLineIfNotEmpty(); + if (this.annotations.hasNoBacktrackAnnotation(it)) { + builder.append(" "); + builder.append("// Enclosing rule was annotated with @NoBacktrack"); + builder.newLine(); + builder.append(" "); + builder.append("options { backtrack=false; }:"); + builder.newLine(); + } + builder.append(" "); + builder.append(this.dataTypeEbnfPredicate(it), " "); + builder.append(this.dataTypeEbnf2(it, supportActions), " "); + builder.newLineIfNotEmpty(); + builder.append(')'); + } else { + builder.append(this.dataTypeEbnf2(it, supportActions)); + } + builder.append(it.getCardinality()); + builder.newLineIfNotEmpty(); + return builder.toString(); + } + + @Override + protected String ebnf(final AbstractElement it, final AntlrOptions options, final boolean supportActions) { + final StringConcatenation builder = new StringConcatenation(); + if (this.mustBeParenthesized(it)) { + builder.append('('); + builder.newLineIfNotEmpty(); + if (this.annotations.hasNoBacktrackAnnotation(it)) { + builder.append(" "); + builder.append("// Enclosing rule was annotated with @NoBacktrack"); + builder.newLine(); + builder.append(" "); + builder.append("options { backtrack=false; }:"); + builder.newLine(); + } + builder.append(" "); + builder.append(this.ebnfPredicate(it, options), " "); + builder.append(this.ebnf2(it, options, supportActions), " "); + builder.newLineIfNotEmpty(); + builder.append(')'); + } else { + builder.append(this.ebnf2(it, options, supportActions)); + } + builder.append(it.getCardinality()); + builder.newLineIfNotEmpty(); + return builder.toString(); + } + + /** + * Add gated predicate, if necessary, before the action preceding the assignment. + */ + @Override + protected String _assignmentEbnf(final RuleCall it, final Assignment assignment, final AntlrOptions options, final boolean supportActions) { + if (supportActions) { + final AbstractRule rule = it.getRule(); + if (rule instanceof EnumRule || rule instanceof ParserRule) { + final StringConcatenation builder = new StringConcatenation(); + if (this.annotations.isGatedPredicateRequired(it)) { + builder.append(this.annotations.generateGatedPredicate(it)); + } + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append(this.newCompositeNode(it), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + builder.append(this._grammarAccessExtensions.localVar(assignment, it)); + builder.append('='); + builder.append(super._assignmentEbnf(it, assignment, options, supportActions)); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("if ($current==null) {"); + builder.newLine(); + builder.append(" "); + builder.append("$current = "); + builder.append(this.createModelElementForParent(assignment), " "); + builder.append(';'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append(" "); + builder.append(this._grammarAccessExtensions.setOrAdd(assignment), " "); + builder.append('('); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("$current,"); + builder.newLine(); + builder.append(" "); + builder.append("\""); + builder.append(assignment.getFeature(), " "); + builder.append("\","); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this._grammarAccessExtensions.localVar(assignment, it), " "); + if (GrammarUtil.isBooleanAssignment(assignment)) { + builder.append(" != null"); + } + builder.append(','); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(this._grammarAccessExtensions.toStringLiteral(it), " "); + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("afterParserOrEnumRuleCall();"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else if (rule instanceof TerminalRule) { + final StringConcatenation builder1 = new StringConcatenation(); + builder1.append(this._grammarAccessExtensions.localVar(assignment, it)); + builder1.append('='); + builder1.append(super._assignmentEbnf(it, assignment, options, supportActions)); + builder1.newLineIfNotEmpty(); + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append(this.newLeafNode(it, this._grammarAccessExtensions.localVar(assignment, it)), " "); + builder1.newLineIfNotEmpty(); + builder1.append('}'); + builder1.newLine(); + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append("if ($current==null) {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("$current = "); + builder1.append(this.createModelElement(assignment), " "); + builder1.append(';'); + builder1.newLineIfNotEmpty(); + builder1.append(" "); + builder1.append('}'); + builder1.newLine(); + builder1.append(" "); + builder1.append(this._grammarAccessExtensions.setOrAdd(assignment), " "); + builder1.append("WithLastConsumed("); + builder1.newLineIfNotEmpty(); + builder1.append(" "); + builder1.append("$current,"); + builder1.newLine(); + builder1.append(" "); + builder1.append("\""); + builder1.append(assignment.getFeature(), " "); + builder1.append("\","); + builder1.newLineIfNotEmpty(); + builder1.append(" "); + builder1.append(this._grammarAccessExtensions.localVar(assignment, it), " "); + if (GrammarUtil.isBooleanAssignment(assignment)) { + builder1.append(" != null"); + } + builder1.append(','); + builder1.newLineIfNotEmpty(); + builder1.append(" "); + builder1.append(this._grammarAccessExtensions.toStringLiteral(it), " "); + builder1.append(");"); + builder1.newLineIfNotEmpty(); + builder1.append('}'); + builder1.newLine(); + return builder1.toString(); + } else { + throw new IllegalStateException("assignmentEbnf is not supported for " + it); + } + } else { + return super._assignmentEbnf(it, assignment, options, supportActions); + } + } + + /** + * Add gated predicate, if necessary, before the action preceding the cross reference. + */ + @Override + protected String _assignmentEbnf(final CrossReference it, final Assignment assignment, final AntlrOptions options, final boolean supportActions) { + if (supportActions) { + final StringConcatenation builder = new StringConcatenation(); + if (this.annotations.isGatedPredicateRequired(it)) { + builder.append(this.annotations.generateGatedPredicate(it)); + } + builder.newLineIfNotEmpty(); + if (options.isBacktrack()) { + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("/* */"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + } + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("if ($current==null) {"); + builder.newLine(); + builder.append(" "); + builder.append("$current = "); + builder.append(this.createModelElement(assignment), " "); + builder.append(';'); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append('}'); + builder.newLine(); + builder.append('}'); + builder.newLine(); + builder.append(super._assignmentEbnf(it, assignment, options, supportActions)); + return builder.toString(); + } else { + return super._assignmentEbnf(it, assignment, options, supportActions); + } + } + + /** + * Add gated predicate, if necessary, before the action preceding the rule call. + */ + @Override + protected String _ebnf2(final RuleCall it, final AntlrOptions options, final boolean supportActions) { + if (!supportActions) { + return super._ebnf2(it, options, supportActions); + } else { + final AbstractRule rule = it.getRule(); + if ((rule instanceof EnumRule && GrammarUtil.isAssigned(it)) || (rule instanceof ParserRule && GrammarUtil.isAssigned(it))) { + return super._ebnf2(it, options, supportActions); + } else if (rule instanceof EnumRule || (rule instanceof ParserRule && GrammarUtil.isDatatypeRule(AntlrGrammarGenUtil.getOriginalElement((ParserRule) rule)))) { + final StringConcatenation builder = new StringConcatenation(); + if (this.annotations.isGatedPredicateRequired(it)) { + builder.append(this.annotations.generateGatedPredicate(it)); + } + builder.newLineIfNotEmpty(); + if (options.isBacktrack()) { + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("/* */"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + } + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append(this.newCompositeNode(it), " "); + builder.newLineIfNotEmpty(); + builder.append('}'); + builder.newLine(); + builder.append(super._ebnf2(it, options, supportActions)); + builder.newLineIfNotEmpty(); + builder.append('{'); + builder.newLine(); + builder.append(" "); + builder.append("afterParserOrEnumRuleCall();"); + builder.newLine(); + builder.append('}'); + builder.newLine(); + return builder.toString(); + } else if (rule instanceof ParserRule) { + final StringConcatenation builder1 = new StringConcatenation(); + if (this.annotations.isGatedPredicateRequired(it)) { + builder1.append(this.annotations.generateGatedPredicate(it)); + } + builder1.newLineIfNotEmpty(); + if (options.isBacktrack()) { + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append("/* */"); + builder1.newLine(); + builder1.append('}'); + builder1.newLine(); + } + builder1.append('{'); + builder1.newLine(); + if (GrammarUtil.isEObjectFragmentRuleCall(it)) { + builder1.append(" "); + builder1.append("if ($current==null) {"); + builder1.newLine(); + builder1.append(" "); + builder1.append("$current = "); + builder1.append(this.createModelElement(it), " "); + builder1.append(';'); + builder1.newLineIfNotEmpty(); + builder1.append(" "); + builder1.append('}'); + builder1.newLine(); + } + builder1.append(" "); + builder1.append(this.newCompositeNode(it), " "); + builder1.newLineIfNotEmpty(); + builder1.append('}'); + builder1.newLine(); + final String localVar = this._grammarAccessExtensions.localVar(it); + builder1.append(localVar); + builder1.append('='); + builder1.append(super._ebnf2(it, options, supportActions)); + builder1.newLineIfNotEmpty(); + builder1.append('{'); + builder1.newLine(); + builder1.append(" "); + builder1.append("$current = $"); + builder1.append(localVar, " "); + builder1.append(".current;"); + builder1.newLineIfNotEmpty(); + builder1.append(" "); + builder1.append("afterParserOrEnumRuleCall();"); + builder1.newLine(); + builder1.append('}'); + builder1.newLine(); + return builder1.toString(); + } else if (rule instanceof TerminalRule) { + final StringConcatenation builder2 = new StringConcatenation(); + final String localVar = this._grammarAccessExtensions.localVar(it); + builder2.append(localVar); + builder2.append('='); + builder2.append(super._ebnf2(it, options, supportActions)); + builder2.newLineIfNotEmpty(); + builder2.append('{'); + builder2.newLine(); + builder2.append(" "); + builder2.append(this.newLeafNode(it, localVar), " "); + builder2.newLineIfNotEmpty(); + builder2.append('}'); + builder2.newLine(); + return builder2.toString(); + } else { + return super._ebnf2(it, options, supportActions); + } + } + } + + @Override + protected String compileLexerImports(final Grammar it, final AntlrOptions options) { + final StringConcatenation builder = new StringConcatenation(); + builder.newLine(); + builder.append("// Hack: Use our own Lexer superclass by means of import."); + builder.newLine(); + builder.append("// Currently there is no other way to specify the superclass for the lexer."); + builder.newLine(); + if (!this.lexerSuperClassName.isEmpty()) { + builder.append("import "); + builder.append(this.lexerSuperClassName); + builder.append(';'); + builder.newLineIfNotEmpty(); + } else { + builder.append("import "); + builder.append(this.getGrammarNaming().getLexerSuperClass(it)); + builder.append(';'); + builder.newLineIfNotEmpty(); + } + return builder.toString(); + } +} +// CHECKSTYLE:CONSTANTS-ON diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrGrammarGenerator.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrGrammarGenerator.xtend deleted file mode 100644 index ebdfd13ab0..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareAntlrGrammarGenerator.xtend +++ /dev/null @@ -1,544 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.parser.antlr - -import com.google.inject.Inject -import com.google.inject.Singleton -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtend.lib.annotations.Accessors -import org.eclipse.xtext.AbstractElement -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.Action -import org.eclipse.xtext.Assignment -import org.eclipse.xtext.CrossReference -import org.eclipse.xtext.EnumLiteralDeclaration -import org.eclipse.xtext.EnumRule -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.Keyword -import org.eclipse.xtext.ParserRule -import org.eclipse.xtext.RuleCall -import org.eclipse.xtext.TerminalRule -import org.eclipse.xtext.UnorderedGroup -import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrOptions -import org.eclipse.xtext.xtext.generator.parser.antlr.GrammarNaming - -import static extension org.eclipse.xtext.EcoreUtil2.* -import static extension org.eclipse.xtext.GrammarUtil.* -import static extension org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil.* - -/** - * This implementation is strongly based on AntlrGrammarGenerator but with a different base class. - * The following extension is supported: - * - * A datatype grammar rule containing only one ID terminal rule can be annotated - * with @KeywordRule annotation provided a list of words so only these words can - * be accepted by this rule. - * - * Example: - * - * /** - * * @KeywordRule(visible, invisible) - * * / - * VisibleKind returns VisibleKind: - * ID - * ; - * - * The above rule will accept only 'visible' and 'invisible' identifiers. - * This rule in ASMD is called a keyword rule because it is intended to replace - * usages of keywords which shall not be reserved words in the language. - * Reserved words are words that are not allowed to be used in identifiers. - * - * The above example can therefore replace the following enumeration: - * - * enum VisibleKind : - * VISIBLE = "visible" - * | INVISIBLE = "invisible" - * ; - * - * Please note that a corresponding value converter is needed. - * - * Implementation remark: - * - This template will insert validating semantic predicates in the rule - * - If the rule is used from an alternative a gated semantic predicate will - * be used in the alternative - * - Error messages will be adjusted correspondingly - */ -@Singleton -class AnnotationAwareAntlrGrammarGenerator extends AbstractAnnotationAwareAntlrGrammarGenerator { - - @Inject extension GrammarNaming naming - - @Accessors(PUBLIC_SETTER) String lexerSuperClassName = ""; - - protected override getGrammarNaming() { - naming - } - - protected override compileParserImports(Grammar it, AntlrOptions options) ''' - - import org.eclipse.xtext.*; - import org.eclipse.xtext.parser.*; - import org.eclipse.xtext.parser.impl.*; - import org.eclipse.emf.ecore.util.EcoreUtil; - import org.eclipse.emf.ecore.EObject; - «IF !allEnumRules.empty» - import org.eclipse.emf.common.util.Enumerator; - «ENDIF» - import «grammarNaming.getInternalParserSuperClass(it).name»; - import org.eclipse.xtext.parser.antlr.XtextTokenStream; - import org.eclipse.xtext.parser.antlr.XtextTokenStream.HiddenTokens; - «IF !allParserRules.map[eAllContentsAsList].flatten.filter(UnorderedGroup).empty && options.backtrack» - import org.eclipse.xtext.parser.antlr.IUnorderedGroupHelper.UnorderedGroupState; - «ENDIF» - import org.eclipse.xtext.parser.antlr.AntlrDatatypeRuleToken; - import «grammarAccess.name»; - «super.compileParserImports(it, options)» - - ''' - - protected override compileParserMembers(Grammar it, AntlrOptions options) ''' - - @«IF combinedGrammar»parser::«ENDIF»members { - - «IF options.backtrack» - /* - This grammar contains a lot of empty actions to work around a bug in ANTLR. - Otherwise the ANTLR tool will create synpreds that cannot be compiled in some rare cases. - */ - - «ENDIF» - «compileParserMemberDeclarations("private")» - - public «internalParserClass.simpleName»(TokenStream input, «grammarAccess.simpleName» grammarAccess, ParserContext parserContext, «getSemanticPredicatesSimpleName()» predicates) { - this(input); - this.grammarAccess = grammarAccess; - this.predicates = predicates; - this.parserContext = parserContext; - parserContext.setTokenStream(input); - registerRules(grammarAccess.getGrammar()); - } - - «compileParserSetTokenStreamMethod» - - @Override - protected String getFirstRuleName() { - return "«allParserRules.head.originalElement.name»"; - } - - @Override - protected «grammarAccess.simpleName» getGrammarAccess() { - return grammarAccess; - } - - } - ''' - - protected override compileRuleCatch(Grammar it, AntlrOptions options) ''' - - @rulecatch { - catch (RecognitionException re) { - recover(input,re); - appendSkippedTokens(); - } - } - ''' - - override protected shouldBeSkipped(TerminalRule it, Grammar grammar) { - false - } - - protected override dispatch compileRule(ParserRule it, Grammar grammar, AntlrOptions options) ''' - «IF isValidEntryRule()» - «compileEntryRule(grammar, options)» - «ENDIF» - - «compileEBNF(options)» - ''' - - protected def String compileEntryRule(ParserRule it, Grammar grammar, AntlrOptions options) ''' - // Entry rule «originalElement.entryRuleName» - «originalElement.entryRuleName» returns «compileEntryReturns(options)»«compileEntryInit(options)»: - { «newCompositeNode» } - iv_«originalElement.ruleName»=«ruleName»«defaultArgumentList» - { $current=$iv_«ruleName».current«IF originalElement.datatypeRule».getText()«ENDIF»; } - EOF; - «compileEntryFinally(options)» - ''' - - protected def compileEntryReturns(ParserRule it, AntlrOptions options) { - if (originalElement.datatypeRule) - return '[String current=null]' - else - return '''[«currentType» current=null]''' - } - - - protected override compileInit(AbstractRule it, AntlrOptions options) ''' - «IF it instanceof ParserRule»«getParameterList(!isPassCurrentIntoFragment, currentType)»«ENDIF» returns «compileReturns(options)» - «IF hasNoBacktrackAnnotation» - // Enclosing rule was annotated with @NoBacktrack - options { backtrack=false; } - «ENDIF» - @init { - enterRule(); - «compileInitHiddenTokens(options)» - «compileInitUnorderedGroups(options)» - } - @after { - leaveRule(); - }''' - - protected def compileReturns(AbstractRule it, AntlrOptions options) { - switch it { - EnumRule: - '[Enumerator current=null]' - ParserRule case originalElement.datatypeRule: - '[AntlrDatatypeRuleToken current=new AntlrDatatypeRuleToken()]' - ParserRule case originalElement.isEObjectFragmentRule: - '''[«currentType» current=in_current]''' - ParserRule: - '''[«currentType» current=null]''' - default: - throw new IllegalStateException("Unexpected rule: " + it) - } - } - - protected override String _dataTypeEbnf2(Keyword it, boolean supportActions) { - if (supportActions) ''' - kw=«super._dataTypeEbnf2(it, supportActions)» - { - $current.merge(kw); - «newLeafNode("kw")» - } - ''' - else - super._dataTypeEbnf2(it, supportActions) - } - - protected override String _ebnf2(Action it, AntlrOptions options, boolean supportActions) { - if (supportActions) ''' - «IF options.backtrack» - { - /* */ - } - «ENDIF» - { - $current = forceCreateModelElement«IF feature !== null»And«setOrAdd.toFirstUpper»«ENDIF»( - grammarAccess.«originalElement.grammarElementAccess», - $current); - } - ''' - else - super._ebnf2(it, options, supportActions) - } - - protected override String _ebnf2(Keyword it, AntlrOptions options, boolean supportActions) { - if (!supportActions) - super._ebnf2(it, options, supportActions) - else if (assigned) ''' - «super._ebnf2(it, options, supportActions)» - { - «newLeafNode(containingAssignment.localVar(it))» - } - ''' - else ''' - «localVar»=«super._ebnf2(it, options, supportActions)» - { - «newLeafNode(localVar)» - } - ''' - } - - override protected _ebnf2(EnumLiteralDeclaration it, AntlrOptions options, boolean supportActions) { - if (!supportActions) - super._ebnf2(it, options, supportActions) - else ''' - «localVar»=«super._ebnf2(it, options, supportActions)» - { - $current = grammarAccess.«grammarElementAccess(originalElement)».getEnumLiteral().getInstance(); - «newLeafNode(localVar)» - } - ''' - } - - protected override String crossrefEbnf(AbstractRule it, RuleCall call, CrossReference ref, boolean supportActions) { - if (supportActions) - switch it { - EnumRule, - ParserRule: ''' - { - «ref.newCompositeNode» - } - «ruleName»«call.getArgumentList(isPassCurrentIntoFragment, !supportActions)» - { - afterParserOrEnumRuleCall(); - } - ''' - TerminalRule: ''' - «ref.containingAssignment.localVar»=«ruleName» - { - «ref.newLeafNode(ref.containingAssignment.localVar)» - } - ''' - default: - throw new IllegalStateException("crossrefEbnf is not supported for " + it) - } - else - super.crossrefEbnf(it, call, ref, supportActions) - } - - override protected _assignmentEbnf(AbstractElement it, Assignment assignment, AntlrOptions options, boolean supportActions) { - if (supportActions) ''' - «assignment.localVar(it)»=«super._assignmentEbnf(it, assignment, options, supportActions)» - { - if ($current==null) { - $current = «assignment.createModelElement»; - } - «assignment.setOrAdd»WithLastConsumed($current, "«assignment.feature»", « - assignment.localVar(it)»«IF assignment.isBooleanAssignment» != null«ENDIF - », «assignment.terminal.toStringLiteral»); - } - ''' - else - super._assignmentEbnf(it, assignment, options, supportActions) - } - - override protected isPassCurrentIntoFragment() { - return true - } - - protected def createModelElement(EObject grammarElement) ''' - createModelElement(grammarAccess.«grammarElement.containingParserRule.originalElement.grammarElementAccess»)''' - - protected def createModelElementForParent(EObject grammarElement) ''' - createModelElementForParent(grammarAccess.«grammarElement.containingParserRule.originalElement.grammarElementAccess»)''' - - protected def newCompositeNode(EObject it) '''newCompositeNode(grammarAccess.«originalElement.grammarElementAccess»);''' - - protected def newLeafNode(EObject it, String token) '''newLeafNode(«token», grammarAccess.«originalElement.grammarElementAccess»);''' - - /** - * Add gated predicate, if necessary, before the action preceding the rule call. - */ - protected override String _dataTypeEbnf2(RuleCall it, boolean supportActions) { - if (supportActions) - switch rule { - EnumRule case assigned, - ParserRule case assigned: - super._dataTypeEbnf2(it, supportActions) - EnumRule, - ParserRule: ''' - «IF isGatedPredicateRequired»«generateGatedPredicate»«ENDIF» - { - «newCompositeNode» - } - «localVar»=«super._dataTypeEbnf2(it, supportActions)»«getArgumentList(isPassCurrentIntoFragment, !supportActions)» - { - $current.merge(«localVar»); - } - { - afterParserOrEnumRuleCall(); - } - ''' - TerminalRule: ''' - «localVar»=«super._dataTypeEbnf2(it, supportActions)» - { - $current.merge(«localVar»); - } - { - «newLeafNode(localVar)» - } - ''' - default: - super._dataTypeEbnf2(it, supportActions) - } - else - super._dataTypeEbnf2(it, supportActions) - } - - /** - * Inserts validating predicate only. Gated predicates will be inserted in alternatives if needed. - */ - protected override String compileEBNF(AbstractRule it, AntlrOptions options) ''' - // Rule «originalElement.name» - «ruleName»«compileInit(options)»: - «IF it instanceof ParserRule && originalElement.datatypeRule» - «IF hasValidatingPredicate»«generateValidatingPredicate»«ENDIF» - «dataTypeEbnf(alternatives, true)» - «ELSE» - «ebnf(alternatives, options, true)» - «ENDIF» - ; - «compileFinally(options)» - ''' - - protected override String dataTypeEbnf(AbstractElement it, boolean supportActions) ''' - «IF mustBeParenthesized»( - «IF hasNoBacktrackAnnotation» - // Enclosing rule was annotated with @NoBacktrack - options { backtrack=false; }: - «ENDIF» - «dataTypeEbnfPredicate»«dataTypeEbnf2(supportActions)» - )«ELSE»«dataTypeEbnf2(supportActions)»«ENDIF»«cardinality» - ''' - - protected override String ebnf(AbstractElement it, AntlrOptions options, boolean supportActions) ''' - «IF mustBeParenthesized»( - «IF hasNoBacktrackAnnotation» - // Enclosing rule was annotated with @NoBacktrack - options { backtrack=false; }: - «ENDIF» - «ebnfPredicate(options)»«ebnf2(options, supportActions)» - )«ELSE»«ebnf2(options, supportActions)»«ENDIF»«cardinality» - ''' - - - - /** - * Add gated predicate, if necessary, before the action preceding the assignment. - */ - protected override String _assignmentEbnf(RuleCall it, Assignment assignment, AntlrOptions options, boolean supportActions) { - if (supportActions) - switch rule { - EnumRule, - ParserRule: ''' - «IF isGatedPredicateRequired»«generateGatedPredicate»«ENDIF» - { - «newCompositeNode» - } - «assignment.localVar(it)»=«super._assignmentEbnf(it, assignment, options, supportActions)» - { - if ($current==null) { - $current = «assignment.createModelElementForParent»; - } - «assignment.setOrAdd»( - $current, - "«assignment.feature»", - «assignment.localVar(it)»«IF assignment.isBooleanAssignment» != null«ENDIF», - «toStringLiteral»); - afterParserOrEnumRuleCall(); - } - ''' - TerminalRule: ''' - «assignment.localVar(it)»=«super._assignmentEbnf(it, assignment, options, supportActions)» - { - «newLeafNode(assignment.localVar(it))» - } - { - if ($current==null) { - $current = «assignment.createModelElement»; - } - «assignment.setOrAdd»WithLastConsumed( - $current, - "«assignment.feature»", - «assignment.localVar(it)»«IF assignment.isBooleanAssignment» != null«ENDIF», - «toStringLiteral»); - } - ''' - default: - throw new IllegalStateException("assignmentEbnf is not supported for " + it) - } - else - super._assignmentEbnf(it, assignment, options, supportActions) - } - - /** - * Add gated predicate, if necessary, before the action preceding the cross reference. - */ - protected override _assignmentEbnf(CrossReference it, Assignment assignment, AntlrOptions options, boolean supportActions) { - if (supportActions) ''' - «IF isGatedPredicateRequired»«generateGatedPredicate»«ENDIF» - «IF options.backtrack» - { - /* */ - } - «ENDIF» - { - if ($current==null) { - $current = «assignment.createModelElement»; - } - } - «super._assignmentEbnf(it, assignment, options, supportActions)»''' - else - super._assignmentEbnf(it, assignment, options, supportActions) - } - - /** - * Add gated predicate, if necessary, before the action preceding the rule call. - */ - protected override String _ebnf2(RuleCall it, AntlrOptions options, boolean supportActions) { - if (!supportActions) - super._ebnf2(it, options, supportActions) - else - switch rule : rule { - EnumRule case assigned, - ParserRule case assigned: - super._ebnf2(it, options, supportActions) - EnumRule, - ParserRule case rule.originalElement.datatypeRule: ''' - «IF isGatedPredicateRequired»«generateGatedPredicate»«ENDIF» - «IF options.backtrack» - { - /* */ - } - «ENDIF» - { - «newCompositeNode» - } - «super._ebnf2(it, options, supportActions)» - { - afterParserOrEnumRuleCall(); - } - ''' - ParserRule: ''' - «IF isGatedPredicateRequired»«generateGatedPredicate»«ENDIF» - «IF options.backtrack» - { - /* */ - } - «ENDIF» - { - «IF isEObjectFragmentRuleCall» - if ($current==null) { - $current = «it.createModelElement»; - } - «ENDIF» - «newCompositeNode» - } - «localVar»=«super._ebnf2(it, options, supportActions)» - { - $current = $«localVar».current; - afterParserOrEnumRuleCall(); - } - ''' - TerminalRule: ''' - «localVar»=«super._ebnf2(it, options, supportActions)» - { - «newLeafNode(localVar)» - } - ''' - default: - super._ebnf2(it, options, supportActions) - } - } - - protected override compileLexerImports(Grammar it, AntlrOptions options) ''' - - // Hack: Use our own Lexer superclass by means of import. - // Currently there is no other way to specify the superclass for the lexer. - «IF !lexerSuperClassName.empty» - import «lexerSuperClassName»; - «ELSE» - import «grammarNaming.getLexerSuperClass(it)»; - «ENDIF» - ''' -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareXtextAntlrGeneratorFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareXtextAntlrGeneratorFragment2.java new file mode 100644 index 0000000000..ec73fe6090 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareXtextAntlrGeneratorFragment2.java @@ -0,0 +1,1065 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ +package com.avaloq.tools.ddk.xtext.generator.parser.antlr; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.antlr.runtime.CharStream; +import org.antlr.runtime.Token; +import org.antlr.runtime.TokenSource; +import org.eclipse.xtend2.lib.StringConcatenation; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.parser.antlr.AntlrTokenDefProvider; +import org.eclipse.xtext.parser.antlr.ITokenDefProvider; +import org.eclipse.xtext.parser.antlr.Lexer; +import org.eclipse.xtext.parser.antlr.LexerProvider; +import org.eclipse.xtext.parser.antlr.XtextTokenStream; +import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.eclipse.xtext.xtext.FlattenedGrammarAccess; +import org.eclipse.xtext.xtext.RuleFilter; +import org.eclipse.xtext.xtext.RuleNames; +import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.GeneratedJavaFileAccess; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.IXtextGeneratorFileSystemAccess; +import org.eclipse.xtext.xtext.generator.model.JavaFileAccess; +import org.eclipse.xtext.xtext.generator.model.ManifestAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil; +import org.eclipse.xtext.xtext.generator.parser.antlr.ContentAssistGrammarNaming; +import org.eclipse.xtext.xtext.generator.parser.antlr.GrammarNaming; +import org.eclipse.xtext.xtext.generator.parser.antlr.XtextAntlrGeneratorFragment2; + +import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations; +import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations.SemanticPredicate; +import com.avaloq.tools.ddk.xtext.generator.parser.common.PredicatesNaming; +import com.avaloq.tools.ddk.xtext.parser.ISemanticPredicates; +import com.avaloq.tools.ddk.xtext.parser.antlr.AbstractContextualAntlrParser; +import com.avaloq.tools.ddk.xtext.parser.antlr.ParserContext; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Names; + + +// CHECKSTYLE:OFF +@SuppressWarnings({"PMD.AvoidDuplicateLiterals", "PMD.TooManyMethods", "PMD.LocalVariableNamingConventions", "nls"}) +public class AnnotationAwareXtextAntlrGeneratorFragment2 extends XtextAntlrGeneratorFragment2 { + + private static final String ADDITIONAL_CA_REQUIRED_BUNDLE = "com.avaloq.tools.ddk.xtext"; + + @Inject + private AnnotationAwareAntlrGrammarGenerator productionGenerator; + + @Inject + private AnnotationAwareAntlrContentAssistGrammarGenerator contentAssistGenerator; + + @Inject + private GrammarNaming productionNaming; + + @Inject + private FileAccessFactory fileFactory; + + @Inject + private ContentAssistGrammarNaming contentAssistNaming; + + @Inject + private PredicatesNaming predicatesNaming; + + @Inject + private GrammarAccessExtensions grammarUtil; + + @Inject + private GrammarRuleAnnotations annotations; + + private boolean generateContentAssistIfIdeMissing; + + private boolean removeBacktrackingGuards; + + private int lookaheadThreshold; + + private boolean partialParsing; + + private Set reservedWords = ImmutableSet.of(); + + private Set keywords = ImmutableSet.of(); + + private Set identifierRules = ImmutableSet.of(); + + private String lexerSuperClassName = ""; + + /** + * Suffix used in the naming convention for the classes responsible for semantic predicates. + */ + private static final String CLASS_SUFFIX = "SemanticPredicates"; + + @Override + public void setRemoveBacktrackingGuards(final boolean removeBacktrackingGuards) { + this.removeBacktrackingGuards = removeBacktrackingGuards; + super.setRemoveBacktrackingGuards(removeBacktrackingGuards); + } + + @Override + public void setLookaheadThreshold(final String lookaheadThreshold) { + this.lookaheadThreshold = Integer.parseInt(lookaheadThreshold); + super.setLookaheadThreshold(lookaheadThreshold); + } + + @Override + public void setPartialParsing(final boolean partialParsing) { + this.partialParsing = partialParsing; + super.setPartialParsing(partialParsing); + } + + public void setReservedWords(final String words) { + this.reservedWords = toSet(words); + } + + public void setIdentifierRules(final String rules) { + this.identifierRules = toSet(rules); + } + + public void setKeywords(final String words) { + this.keywords = toSet(words); + } + + public void setLexerSuperClassName(final String className) { + this.lexerSuperClassName = className; + } + + private Set toSet(final String words) { + return Arrays.stream(words.split(",")).map(String::trim).filter(str -> !str.isEmpty()).collect(Collectors.toSet()); + } + + public boolean isGenerateContentAssistIfIdeMissing() { + return this.generateContentAssistIfIdeMissing; + } + + public void setGenerateContentAssistIfIdeMissing(final boolean generateContentAssistIfIdeMissing) { + this.generateContentAssistIfIdeMissing = generateContentAssistIfIdeMissing; + } + + @Override + protected void checkGrammar() { + super.checkGrammar(); + this.annotations.annotateGrammar(getGrammar()); + } + + @Override + protected void doGenerate() { + super.doGenerate(); + // if there is no ide plugin, write the content assist parser to the ui plugin. + if (this.generateContentAssistIfIdeMissing && getProjectConfig().getGenericIde().getSrcGen() == null) { + generateUiContentAssistGrammar(); + generateContentAssistParser().writeTo(getProjectConfig().getEclipsePlugin().getSrcGen()); + if (hasSyntheticTerminalRule()) { + generateContentAssistTokenSource().writeTo(getProjectConfig().getEclipsePlugin().getSrc()); + } + addIdeUiBindingsAndImports(); + } + generateAbstractSemanticPredicate().writeTo(getProjectConfig().getRuntime().getSrcGen()); + } + + @Override + protected void addRuntimeBindingsAndImports() { + super.addRuntimeBindingsAndImports(); + ManifestAccess manifest = getProjectConfig().getRuntime().getManifest(); + if (manifest != null) { + Set exportedPackages = manifest.getExportedPackages(); + String semanticPredicatesPackageName = this.predicatesNaming.getSemanticPredicatesPackageName(getGrammar()); + exportedPackages.add(semanticPredicatesPackageName); + } + } + + @Override + protected void generateContentAssistGrammar() { + generateContentAssistGrammar(getProjectConfig().getGenericIde().getSrcGen()); + } + + protected void generateContentAssistGrammar(final IXtextGeneratorFileSystemAccess fsa) { + final ContentAssistGrammarNaming naming = this.contentAssistNaming; + this.contentAssistGenerator.generate(getGrammar(), getOptions(), fsa); + runAntlr(naming.getParserGrammar(getGrammar()), naming.getLexerGrammar(getGrammar()), fsa); + simplifyUnorderedGroupPredicatesIfRequired(getGrammar(), fsa, naming.getInternalParserClass(getGrammar())); + splitParserAndLexerIfEnabled(fsa, naming.getInternalParserClass(getGrammar()), naming.getLexerClass(getGrammar())); + normalizeTokens(fsa, naming.getLexerGrammar(getGrammar()).getTokensFileName()); + suppressWarnings(fsa, naming.getInternalParserClass(getGrammar()), naming.getLexerClass(getGrammar())); + normalizeLineDelimiters(fsa, naming.getLexerClass(getGrammar()), naming.getInternalParserClass(getGrammar())); + if (this.removeBacktrackingGuards) { + removeBackTrackingGuards(fsa, naming.getInternalParserClass(getGrammar()), this.lookaheadThreshold); + } + } + + @Override + protected void addIdeBindingsAndImports() { + super.addIdeBindingsAndImports(); + ManifestAccess manifest = getProjectConfig().getGenericIde().getManifest(); + if (manifest != null) { + Set requiredBundles = manifest.getRequiredBundles(); + requiredBundles.add(ADDITIONAL_CA_REQUIRED_BUNDLE); + } + } + + protected void generateUiContentAssistGrammar() { + generateContentAssistGrammar(getProjectConfig().getEclipsePlugin().getSrcGen()); + } + + protected void addIdeUiBindingsAndImports() { + final ContentAssistGrammarNaming naming = this.contentAssistNaming; + ManifestAccess manifest = getProjectConfig().getEclipsePlugin().getManifest(); + if (manifest != null) { + Set exportedPackages = manifest.getExportedPackages(); + Iterables.addAll(exportedPackages, List.of( + naming.getLexerClass(getGrammar()).getPackageName(), + naming.getParserClass(getGrammar()).getPackageName(), + naming.getInternalParserClass(getGrammar()).getPackageName())); + Set requiredBundles = manifest.getRequiredBundles(); + Iterables.addAll(requiredBundles, List.of( + "org.antlr.runtime;bundle-version=\"[3.2.0,3.2.1)\"", + ADDITIONAL_CA_REQUIRED_BUNDLE)); + } + GuiceModuleAccess.BindingFactory ideBindings = new GuiceModuleAccess.BindingFactory() + .addConfiguredBinding("ContentAssistLexer", new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("binder.bind("); + TypeReference _lexerSuperClass = naming.getLexerSuperClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_lexerSuperClass); + builder.append(".class)"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".annotatedWith("); + builder.append(Names.class, " "); + builder.append(".named("); + TypeReference _typeRef = TypeReference.typeRef("org.eclipse.xtext.ide.LexerIdeBindings"); + builder.append(_typeRef, " "); + builder.append(".CONTENT_ASSIST))"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".to("); + TypeReference _lexerClass = naming.getLexerClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_lexerClass, " "); + builder.append(".class);"); + builder.newLineIfNotEmpty(); + } + }) + .addTypeToType(TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.antlr.IContentAssistParser"), naming.getParserClass(getGrammar())) + .addConfiguredBinding("IProposalConflictHelper", new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("binder.bind(org.eclipse.xtext.ide.editor.contentassist.IProposalConflictHelper.class)"); + builder.newLine(); + builder.append(" "); + builder.append(".to(org.eclipse.xtext.ide.editor.contentassist.antlr.AntlrProposalConflictHelper.class);"); + builder.newLine(); + } + }); + if (this.partialParsing) { + ideBindings.addTypeToType( + TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.antlr.ContentAssistContextFactory"), + TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.antlr.PartialContentAssistContextFactory")); + } + if (hasSyntheticTerminalRule()) { + ideBindings.addTypeToType( + TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.CompletionPrefixProvider"), + TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.IndentationAwareCompletionPrefixProvider")); + } + ideBindings.contributeTo(getLanguage().getEclipsePluginGenModule()); + } + + @Override + public JavaFileAccess generateContentAssistParser() { + final ContentAssistGrammarNaming naming = this.contentAssistNaming; + final GeneratedJavaFileAccess file = this.fileFactory.createGeneratedJavaFile(naming.getParserClass(getGrammar())); + StringConcatenationClient client = new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("public class "); + String _simpleName = naming.getParserClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()).getSimpleName(); + builder.append(_simpleName); + builder.append(" extends "); + TypeReference _parserSuperClass = naming.getParserSuperClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar(), AnnotationAwareXtextAntlrGeneratorFragment2.this.partialParsing); + builder.append(_parserSuperClass); + builder.append(" {"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + StringConcatenationClient _initNameMappings = AnnotationAwareXtextAntlrGeneratorFragment2.this.initNameMappings(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_initNameMappings, " "); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("@"); + builder.append(Inject.class, " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("private "); + TypeReference _grammarAccess = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.getGrammarAccess(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_grammarAccess, " "); + builder.append(" grammarAccess;"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("@"); + builder.append(Inject.class, " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("private "); + TypeReference _typeRef = TypeReference.typeRef(ISemanticPredicates.class); + builder.append(_typeRef, " "); + builder.append(" predicates;"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append("* Creates compilation context."); + builder.newLine(); + builder.append(" "); + builder.append("*"); + builder.newLine(); + builder.append(" "); + builder.append("* @param Input"); + builder.newLine(); + builder.append(" "); + builder.append("* Stream"); + builder.newLine(); + builder.append(" "); + builder.append("* @return Compilation context"); + builder.newLine(); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.append(" "); + builder.append("protected "); + TypeReference _typeRef_1 = TypeReference.typeRef(ParserContext.class); + builder.append(_typeRef_1, " "); + builder.append(" createParserContext() {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("return new ParserContext();"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected "); + TypeReference _internalParserClass = naming.getInternalParserClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_internalParserClass, " "); + builder.append(" createParser() {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + TypeReference _internalParserClass_1 = naming.getInternalParserClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_internalParserClass_1, " "); + builder.append(" result = new "); + TypeReference _internalParserClass_2 = naming.getInternalParserClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_internalParserClass_2, " "); + builder.append("(null);"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("result.setGrammarAccess(grammarAccess);"); + builder.newLine(); + builder.append(" "); + builder.append("result.setParserContext(createParserContext());"); + builder.newLine(); + builder.append(" "); + builder.append("result.setPredicates(("); + TypeReference _typeRef_2 = TypeReference.typeRef(AnnotationAwareXtextAntlrGeneratorFragment2.this.predicatesNaming.getSemanticPredicatesFullName(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar())); + builder.append(_typeRef_2, " "); + builder.append(")predicates);"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("return result;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + { + boolean _hasSyntheticTerminalRule = AnnotationAwareXtextAntlrGeneratorFragment2.this.hasSyntheticTerminalRule(); + if (_hasSyntheticTerminalRule) { + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected "); + builder.append(TokenSource.class, " "); + builder.append(" createLexer("); + builder.append(CharStream.class, " "); + builder.append(" stream) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append("return new "); + TypeReference _tokenSourceClass = naming.getTokenSourceClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_tokenSourceClass, " "); + builder.append("(super.createLexer(stream));"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + } + } + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected String getRuleName("); + builder.append(AbstractElement.class, " "); + builder.append(" element) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("return nameMappings.getRuleName(element);"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected String[] getInitialHiddenTokens() {"); + builder.newLine(); + builder.append(" "); + builder.append("return new String[] { "); + { + List _initialHiddenTokens = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.initialHiddenTokens(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + boolean _hasElements = false; + for (final String hidden : _initialHiddenTokens) { + if (!_hasElements) { + _hasElements = true; + } else { + builder.appendImmediate(", ", " "); + } + builder.append("\""); + builder.append(hidden, " "); + builder.append("\""); + } + } + builder.append(" };"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("public "); + TypeReference _grammarAccess_1 = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.getGrammarAccess(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_grammarAccess_1, " "); + builder.append(" getGrammarAccess() {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("return this.grammarAccess;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("public void setGrammarAccess("); + TypeReference _grammarAccess_2 = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.getGrammarAccess(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_grammarAccess_2, " "); + builder.append(" grammarAccess) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("this.grammarAccess = grammarAccess;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("public NameMappings getNameMappings() {"); + builder.newLine(); + builder.append(" "); + builder.append("return nameMappings;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("public void setNameMappings(NameMappings nameMappings) {"); + builder.newLine(); + builder.append(" "); + builder.append("this.nameMappings = nameMappings;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.append("}"); + builder.newLine(); + } + }; + file.setContent(client); + return file; + } + + @Override + protected void generateProductionGrammar() { + final GrammarNaming naming = this.productionNaming; + final IXtextGeneratorFileSystemAccess fsa = getProjectConfig().getRuntime().getSrcGen(); + this.productionGenerator.setLexerSuperClassName(this.lexerSuperClassName); + this.productionGenerator.generate(getGrammar(), getOptions(), fsa); + runAntlr(naming.getParserGrammar(getGrammar()), naming.getLexerGrammar(getGrammar()), fsa); + simplifyUnorderedGroupPredicatesIfRequired(getGrammar(), fsa, naming.getInternalParserClass(getGrammar())); + splitParserAndLexerIfEnabled(fsa, naming.getInternalParserClass(getGrammar()), naming.getLexerClass(getGrammar())); + normalizeTokens(fsa, naming.getLexerGrammar(getGrammar()).getTokensFileName()); + suppressWarnings(fsa, naming.getInternalParserClass(getGrammar()), naming.getLexerClass(getGrammar())); + normalizeLineDelimiters(fsa, naming.getInternalParserClass(getGrammar()), naming.getLexerClass(getGrammar())); + + /* filter and ruleNames for flattened grammar */ + final RuleFilter filter = new RuleFilter(); + filter.setDiscardUnreachableRules(true); + filter.setDiscardTerminalRules(false); + final RuleNames ruleNames = RuleNames.getRuleNames(getGrammar(), true); + + String grammarFileName = this.productionGenerator.getGrammarNaming().getParserGrammar(getGrammar()).getGrammarFileName(); + Grammar flattenedGrammar = new FlattenedGrammarAccess(ruleNames, filter).getFlattenedGrammar(); + final KeywordAnalysisHelper keywordAnalysisHelper = new KeywordAnalysisHelper(grammarFileName, flattenedGrammar, this.identifierRules, this.reservedWords, this.keywords, this.grammarUtil); + keywordAnalysisHelper.printReport(fsa.getPath()); + keywordAnalysisHelper.printViolations(fsa.getPath()); + } + + public JavaFileAccess generateAbstractSemanticPredicate() { + final GeneratedJavaFileAccess file = this.fileFactory.createGeneratedJavaFile(TypeReference.typeRef(this.predicatesNaming.getSemanticPredicatesFullName(getGrammar()))); + file.importType(TypeReference.typeRef(this.predicatesNaming.getSemanticPredicatesFullName(getGrammar()))); + file.importType(TypeReference.typeRef(ISemanticPredicates.AbstractSemanticPredicates.class)); + if (!this.annotations.predicates(getGrammar()).isEmpty()) { + file.importType(TypeReference.typeRef(ParserContext.class)); + file.importType(TypeReference.typeRef(Token.class)); + for (final Grammar inheritedGrammar : this.annotations.allInheritedGrammars(getGrammar())) { + file.importType(TypeReference.typeRef(GrammarUtil.getNamespace(inheritedGrammar) + ".grammar." + GrammarUtil.getSimpleName(inheritedGrammar) + CLASS_SUFFIX)); + } + } + file.importType(TypeReference.typeRef(Singleton.class)); + StringConcatenationClient client = new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append("* Provides semantic predicates as specified in the grammar. Language may need to override"); + builder.newLine(); + builder.append(" "); + builder.append("* this class in order to provide concrete implementations for predicates."); + builder.newLine(); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.append("@Singleton"); + builder.newLine(); + builder.append("public class "); + String _semanticPredicatesSimpleName = AnnotationAwareXtextAntlrGeneratorFragment2.this.predicatesNaming.getSemanticPredicatesSimpleName(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_semanticPredicatesSimpleName); + builder.append(" extends AbstractSemanticPredicates {"); + builder.newLineIfNotEmpty(); + { + List _predicates = AnnotationAwareXtextAntlrGeneratorFragment2.this.annotations.predicates(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + for (final GrammarRuleAnnotations.SemanticPredicate element : _predicates) { + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* Predicate for grammar rule "); + String _name = element.getName(); + builder.append(_name, " "); + builder.append("."); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append("*"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* @param input"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* Input from Lexer"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* @return {@code true} if the grammar rule is enabled, {@code false} otherwise"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.append(" "); + builder.append("public boolean "); + String _name_1 = element.getName(); + builder.append(_name_1, " "); + builder.append("(ParserContext parserContext) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + String _rulePredicateCondition = AnnotationAwareXtextAntlrGeneratorFragment2.this.getRulePredicateCondition(element); + builder.append(_rulePredicateCondition, " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + } + } + builder.newLine(); + { + List _predicates_1 = AnnotationAwareXtextAntlrGeneratorFragment2.this.annotations.predicates(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + for (final GrammarRuleAnnotations.SemanticPredicate element_1 : _predicates_1) { + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* Message for "); + String _name_2 = element_1.getName(); + builder.append(_name_2, " "); + builder.append(" predicate."); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append("*"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* @param input"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* Input from Lexer"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* @return {@code true} if the grammar rule is enabled, {@code false} otherwise"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("public String "); + String _message = element_1.getMessage(); + builder.append(_message, " "); + builder.append("(Token token) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + String _rulePredicateMessage = AnnotationAwareXtextAntlrGeneratorFragment2.this.getRulePredicateMessage(element_1); + builder.append(_rulePredicateMessage, " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append("}"); + builder.newLine(); + } + } + builder.append("}"); + builder.newLine(); + } + }; + file.setContent(client); + return file; + } + + /** + * Returns name for the predicate message method. + * + * @param predicate + * Semantic predicate, must not be {@code null} + * @return Content for the predicate message method + */ + public String getRulePredicateMessage(final SemanticPredicate predicate) { + if (predicate.getKeywords() != null) { + StringConcatenation msgBuilder = new StringConcatenation(); + msgBuilder.append("return \"Unexpected: \" + token.getText() + \". Expected: \'"); + String join = Joiner.on("\', \'").join(predicate.getKeywords()); + msgBuilder.append(join); + msgBuilder.append("\'\";"); + msgBuilder.newLineIfNotEmpty(); + return msgBuilder.toString(); + } else { + StringConcatenation defaultBuilder = new StringConcatenation(); + defaultBuilder.append("/* Default message. Intended to be overridden. */"); + defaultBuilder.newLine(); + defaultBuilder.append("return \"Condition "); + String name = predicate.getName(); + defaultBuilder.append(name); + defaultBuilder.append(" is not fullfilled \";"); + defaultBuilder.newLineIfNotEmpty(); + return defaultBuilder.toString(); + } + } + + /** + * Returns predicate condition (default condition). + * + * @param predicate + * Semantic predicate, must not be {@code null} + * @return A string containing the condition for the semantic predicate or {@code null} + */ + public String getRulePredicateCondition(final SemanticPredicate predicate) { + if (predicate.getKeywords() != null) { + final String condition = predicate.getKeywords().stream().map(s -> { + StringConcatenation kwBuilder = new StringConcatenation(); + kwBuilder.append("\""); + kwBuilder.append(s); + kwBuilder.append("\".equalsIgnoreCase(text)"); + return kwBuilder.toString(); + }).collect(Collectors.joining(" || ")); + StringConcatenation condBuilder = new StringConcatenation(); + condBuilder.append("String text = parserContext.getInput().LT(1).getText();"); + condBuilder.newLine(); + condBuilder.append("return "); + condBuilder.append(condition); + condBuilder.append(";"); + condBuilder.newLineIfNotEmpty(); + return condBuilder.toString(); + } else { + return "return " + predicate.getGrammar() + CLASS_SUFFIX + "." + predicate.getName() + "(parserContext);\n"; + } + } + + @Override + public JavaFileAccess generateProductionParser() { + final GrammarNaming naming = this.productionNaming; + final GeneratedJavaFileAccess file = this.fileFactory.createGeneratedJavaFile(naming.getParserClass(getGrammar())); + file.importType(TypeReference.typeRef(this.predicatesNaming.getSemanticPredicatesFullName(getGrammar()))); + file.importType(TypeReference.typeRef(ISemanticPredicates.class)); + StringConcatenationClient client = new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("public class "); + String _simpleName = naming.getParserClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()).getSimpleName(); + builder.append(_simpleName); + builder.append(" extends "); + builder.append(AbstractContextualAntlrParser.class); + builder.append(" {"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("@"); + builder.append(Inject.class, " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("private "); + TypeReference _grammarAccess = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.getGrammarAccess(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_grammarAccess, " "); + builder.append(" grammarAccess;"); + builder.newLineIfNotEmpty(); + builder.newLine(); + builder.append(" "); + builder.append("@"); + builder.append(Inject.class, " "); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("private ISemanticPredicates predicates;"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected void setInitialHiddenTokens("); + builder.append(XtextTokenStream.class, " "); + builder.append(" tokenStream) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("tokenStream.setInitialHiddenTokens("); + { + List _initialHiddenTokens = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.initialHiddenTokens(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + boolean _hasElements = false; + for (final String hidden : _initialHiddenTokens) { + if (!_hasElements) { + _hasElements = true; + } else { + builder.appendImmediate(", ", " "); + } + builder.append("\""); + builder.append(hidden, " "); + builder.append("\""); + } + } + builder.append(");"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + { + boolean _hasSyntheticTerminalRule = AnnotationAwareXtextAntlrGeneratorFragment2.this.hasSyntheticTerminalRule(); + if (_hasSyntheticTerminalRule) { + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected "); + builder.append(TokenSource.class, " "); + builder.append(" createLexer("); + builder.append(CharStream.class, " "); + builder.append(" stream) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(" "); + builder.append("return new "); + TypeReference _tokenSourceClass = naming.getTokenSourceClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_tokenSourceClass, " "); + builder.append("(super.createLexer(stream));"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("/**"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* Indentation aware languages do not support partial parsing since the lexer is inherently stateful."); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("* Override and return {@code true} if your terminal splitting is stateless."); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("*/"); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected boolean isReparseSupported() {"); + builder.newLine(); + builder.append(" "); + builder.append(" "); + builder.append("return false;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + } + } + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected "); + TypeReference _internalParserClass = naming.getInternalParserClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_internalParserClass, " "); + builder.append(" createParser("); + builder.append(XtextTokenStream.class, " "); + builder.append(" stream) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("return new "); + TypeReference _internalParserClass_1 = naming.getInternalParserClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_internalParserClass_1, " "); + builder.append("(stream, getGrammarAccess(), createParserContext(), ("); + String _semanticPredicatesSimpleName = AnnotationAwareXtextAntlrGeneratorFragment2.this.predicatesNaming.getSemanticPredicatesSimpleName(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_semanticPredicatesSimpleName, " "); + builder.append(") predicates);"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("@Override"); + builder.newLine(); + builder.append(" "); + builder.append("protected String getDefaultRuleName() {"); + builder.newLine(); + builder.append(" "); + builder.append("return \""); + String _name = AntlrGrammarGenUtil.getOriginalElement(IterableExtensions.head(GrammarUtil.allParserRules(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()))).getName(); + builder.append(_name, " "); + builder.append("\";"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("public "); + TypeReference _grammarAccess_1 = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.getGrammarAccess(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_grammarAccess_1, " "); + builder.append(" getGrammarAccess() {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("return this.grammarAccess;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.newLine(); + builder.append(" "); + builder.append("public void setGrammarAccess("); + TypeReference _grammarAccess_2 = AnnotationAwareXtextAntlrGeneratorFragment2.this.grammarUtil.getGrammarAccess(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_grammarAccess_2, " "); + builder.append(" grammarAccess) {"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append("this.grammarAccess = grammarAccess;"); + builder.newLine(); + builder.append(" "); + builder.append("}"); + builder.newLine(); + builder.append("}"); + builder.newLine(); + } + }; + file.setContent(client); + return file; + } + + @Override + protected void addUiBindingsAndImports() { + /* Overridden to remove the binding for IContentAssistParser, which we bind at a different place. + * If we would register it here, we would have conflicting bindings for IContentAssistParser. + */ + final ContentAssistGrammarNaming naming = this.contentAssistNaming; + final TypeReference caLexerClass = naming.getLexerClass(getGrammar()); + + ManifestAccess manifest = getProjectConfig().getGenericIde().getManifest(); + if (manifest != null) { + Set exportedPackages = manifest.getExportedPackages(); + Iterables.addAll(exportedPackages, List.of( + caLexerClass.getPackageName(), + naming.getParserClass(getGrammar()).getPackageName(), + naming.getInternalParserClass(getGrammar()).getPackageName())); + } + GuiceModuleAccess.BindingFactory uiBindings = new GuiceModuleAccess.BindingFactory() + .addTypeToType( + TypeReference.typeRef("org.eclipse.xtext.ui.editor.contentassist.IProposalConflictHelper"), + TypeReference.typeRef("org.eclipse.xtext.ui.editor.contentassist.antlr.AntlrProposalConflictHelper")) + .addConfiguredBinding("ContentAssistLexer", new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("binder.bind("); + TypeReference _lexerSuperClass = naming.getLexerSuperClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_lexerSuperClass); + builder.append(".class)"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".annotatedWith("); + builder.append(Names.class, " "); + builder.append(".named("); + TypeReference _typeRef = TypeReference.typeRef("org.eclipse.xtext.ide.LexerIdeBindings"); + builder.append(_typeRef, " "); + builder.append(".CONTENT_ASSIST))"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".to("); + builder.append(caLexerClass, " "); + builder.append(".class);"); + builder.newLineIfNotEmpty(); + } + }) + // registration of the 'ContentAssistLexer' is put in front of the 'HighlightingLexer' + // in order to let 'caLexerClass' get added to the imports, since it is referenced + // several times and the lexer classes' simple names are usually identical + .addConfiguredBinding("HighlightingLexer", new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("binder.bind("); + builder.append(Lexer.class); + builder.append(".class)"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".annotatedWith("); + builder.append(Names.class, " "); + builder.append(".named("); + TypeReference _typeRef = TypeReference.typeRef("org.eclipse.xtext.ide.LexerIdeBindings"); + builder.append(_typeRef, " "); + builder.append(".HIGHLIGHTING))"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".to("); + TypeReference _lexerClass = AnnotationAwareXtextAntlrGeneratorFragment2.this.productionNaming.getLexerClass(AnnotationAwareXtextAntlrGeneratorFragment2.this.getGrammar()); + builder.append(_lexerClass, " "); + builder.append(".class);"); + builder.newLineIfNotEmpty(); + } + }) + .addConfiguredBinding("HighlightingTokenDefProvider", new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("binder.bind("); + builder.append(ITokenDefProvider.class); + builder.append(".class)"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".annotatedWith("); + builder.append(Names.class, " "); + builder.append(".named("); + TypeReference _typeRef = TypeReference.typeRef("org.eclipse.xtext.ide.LexerIdeBindings"); + builder.append(_typeRef, " "); + builder.append(".HIGHLIGHTING))"); + builder.newLineIfNotEmpty(); + builder.append(" "); + builder.append(".to("); + builder.append(AntlrTokenDefProvider.class, " "); + builder.append(".class);"); + builder.newLineIfNotEmpty(); + } + }) + .addTypeToType( + new TypeReference("org.eclipse.xtext.ui.editor.contentassist", "ContentAssistContext.Factory"), + TypeReference.typeRef("org.eclipse.xtext.ui.editor.contentassist.antlr.DelegatingContentAssistContextFactory")) + .addConfiguredBinding("ContentAssistLexerProvider", new StringConcatenationClient() { + @Override + protected void appendTo(final StringConcatenationClient.TargetStringConcatenation builder) { + builder.append("binder.bind("); + builder.append(caLexerClass); + builder.append(".class).toProvider("); + builder.append(LexerProvider.class); + builder.append(".create("); + builder.append(caLexerClass); + builder.append(".class));"); + builder.newLineIfNotEmpty(); + } + }); + + if (getProjectConfig().getGenericIde().getSrcGen() != null) { + uiBindings.addTypeToType(TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.antlr.IContentAssistParser"), naming.getParserClass(getGrammar())); + } + + if (hasSyntheticTerminalRule()) { + uiBindings.addTypeToType( + TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.CompletionPrefixProvider"), + TypeReference.typeRef("org.eclipse.xtext.ide.editor.contentassist.IndentationAwareCompletionPrefixProvider")); + } + uiBindings.contributeTo(getLanguage().getEclipsePluginGenModule()); + } +} +// CHECKSTYLE:ON diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareXtextAntlrGeneratorFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareXtextAntlrGeneratorFragment2.xtend deleted file mode 100644 index f295d4cbf7..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/antlr/AnnotationAwareXtextAntlrGeneratorFragment2.xtend +++ /dev/null @@ -1,530 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.parser.antlr - -import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations -import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations.SemanticPredicate -import com.avaloq.tools.ddk.xtext.generator.parser.common.PredicatesNaming -import com.avaloq.tools.ddk.xtext.parser.ISemanticPredicates -import com.avaloq.tools.ddk.xtext.parser.antlr.AbstractContextualAntlrParser -import com.avaloq.tools.ddk.xtext.parser.antlr.ParserContext -import com.google.common.base.Joiner -import com.google.common.collect.ImmutableSet -import com.google.inject.Inject -import com.google.inject.Singleton -import com.google.inject.name.Names -import java.util.Arrays -import java.util.Set -import java.util.stream.Collectors -import org.antlr.runtime.CharStream -import org.antlr.runtime.Token -import org.antlr.runtime.TokenSource -import org.eclipse.xtext.AbstractElement -import org.eclipse.xtext.GrammarUtil -import org.eclipse.xtext.parser.antlr.AntlrTokenDefProvider -import org.eclipse.xtext.parser.antlr.ITokenDefProvider -import org.eclipse.xtext.parser.antlr.Lexer -import org.eclipse.xtext.parser.antlr.LexerProvider -import org.eclipse.xtext.parser.antlr.XtextTokenStream -import org.eclipse.xtext.xtext.FlattenedGrammarAccess -import org.eclipse.xtext.xtext.RuleFilter -import org.eclipse.xtext.xtext.RuleNames -import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess -import org.eclipse.xtext.xtext.generator.model.IXtextGeneratorFileSystemAccess -import org.eclipse.xtext.xtext.generator.model.JavaFileAccess -import org.eclipse.xtext.xtext.generator.model.TypeReference -import org.eclipse.xtext.xtext.generator.parser.antlr.ContentAssistGrammarNaming -import org.eclipse.xtext.xtext.generator.parser.antlr.GrammarNaming -import org.eclipse.xtext.xtext.generator.parser.antlr.XtextAntlrGeneratorFragment2 - -import static extension org.eclipse.xtext.GrammarUtil.* -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* -import static extension org.eclipse.xtext.xtext.generator.parser.antlr.AntlrGrammarGenUtil.* -import org.eclipse.xtend.lib.annotations.Accessors - -class AnnotationAwareXtextAntlrGeneratorFragment2 extends XtextAntlrGeneratorFragment2 { - - static val ADDITIONAL_CA_REQUIRED_BUNDLE = "com.avaloq.tools.ddk.xtext" - - @Inject AnnotationAwareAntlrGrammarGenerator productionGenerator - @Inject AnnotationAwareAntlrContentAssistGrammarGenerator contentAssistGenerator - @Inject GrammarNaming productionNaming - @Inject FileAccessFactory fileFactory - @Inject ContentAssistGrammarNaming contentAssistNaming - - @Inject extension PredicatesNaming predicatesNaming - @Inject extension GrammarAccessExtensions grammarUtil - @Inject extension GrammarRuleAnnotations annotations - - @Accessors boolean generateContentAssistIfIdeMissing - - boolean removeBacktrackingGuards - int lookaheadThreshold - boolean partialParsing - - Set reservedWords = ImmutableSet.of(); - Set keywords = ImmutableSet.of(); - Set identifierRules = ImmutableSet.of(); - String lexerSuperClassName = ""; - - /** - * Suffix used in the naming convention for the classes responsible for semantic predicates. - */ - static val CLASS_SUFFIX = "SemanticPredicates"; - - override setRemoveBacktrackingGuards(boolean removeBacktrackingGuards) { - this.removeBacktrackingGuards = removeBacktrackingGuards - super.setRemoveBacktrackingGuards(removeBacktrackingGuards) - } - - override setLookaheadThreshold(String lookaheadThreshold) { - this.lookaheadThreshold = Integer.parseInt(lookaheadThreshold) - super.setLookaheadThreshold(lookaheadThreshold) - } - - override setPartialParsing(boolean partialParsing) { - this.partialParsing = partialParsing - super.setPartialParsing(partialParsing) - } - - def setReservedWords(String words) { - reservedWords = toSet(words); - } - - def setIdentifierRules(String rules) { - identifierRules = toSet(rules); - } - - def setKeywords(String words) { - keywords = toSet(words); - } - - def setLexerSuperClassName (String className) { - lexerSuperClassName = className - } - - def private Set toSet(String words) { - Arrays.stream(words.split(",")).map(str | str.trim()).filter(str | !str.isEmpty()).collect(Collectors.toSet()); - } - - protected override void checkGrammar() { - super.checkGrammar(); - getGrammar().annotateGrammar - } - - protected override doGenerate() { - super.doGenerate() - // if there is no ide plugin, write the content assist parser to the ui plugin. - if (generateContentAssistIfIdeMissing && projectConfig.genericIde.srcGen === null) { - generateUiContentAssistGrammar() - generateContentAssistParser().writeTo(projectConfig.eclipsePlugin.srcGen) - if (hasSyntheticTerminalRule()) { - generateContentAssistTokenSource().writeTo(projectConfig.eclipsePlugin.src) - } - addIdeUiBindingsAndImports() - } - - generateAbstractSemanticPredicate().writeTo(projectConfig.runtime.srcGen) - } - - override protected addRuntimeBindingsAndImports() { - super.addRuntimeBindingsAndImports - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest=>[ - exportedPackages += #[ - grammar.semanticPredicatesPackageName - ] - ] - } - } - - protected override generateContentAssistGrammar() { - generateContentAssistGrammar(projectConfig.genericIde.srcGen) - } - - protected def generateContentAssistGrammar(IXtextGeneratorFileSystemAccess fsa) { - val extension naming = contentAssistNaming - - contentAssistGenerator.generate(grammar, options, fsa) - - runAntlr(grammar.parserGrammar, grammar.lexerGrammar, fsa) - - simplifyUnorderedGroupPredicatesIfRequired(grammar, fsa, grammar.internalParserClass) - splitParserAndLexerIfEnabled(fsa, grammar.internalParserClass, grammar.lexerClass) - normalizeTokens(fsa, grammar.lexerGrammar.tokensFileName) - suppressWarnings(fsa, grammar.internalParserClass, grammar.lexerClass) - normalizeLineDelimiters(fsa, grammar.lexerClass, grammar.internalParserClass) - if (removeBacktrackingGuards) { - removeBackTrackingGuards(fsa, grammar.internalParserClass, lookaheadThreshold) - } - } - - override protected void addIdeBindingsAndImports() { - super.addIdeBindingsAndImports - if (projectConfig.genericIde.manifest !== null) { - projectConfig.genericIde.manifest=>[ - requiredBundles += ADDITIONAL_CA_REQUIRED_BUNDLE - ] - } - } - - protected def generateUiContentAssistGrammar() { - generateContentAssistGrammar(projectConfig.eclipsePlugin.srcGen) - } - - def protected void addIdeUiBindingsAndImports() { - val extension naming = contentAssistNaming - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.eclipsePlugin.manifest=>[ - exportedPackages += #[ - grammar.lexerClass.packageName, - grammar.parserClass.packageName, - grammar.internalParserClass.packageName - ] - requiredBundles += #[ - "org.antlr.runtime;bundle-version=\"[3.2.0,3.2.1)\"", - ADDITIONAL_CA_REQUIRED_BUNDLE - ] - ] - } - val ideBindings = new GuiceModuleAccess.BindingFactory() - .addConfiguredBinding("ContentAssistLexer", ''' - binder.bind(«grammar.lexerSuperClass».class) - .annotatedWith(«Names».named(«"org.eclipse.xtext.ide.LexerIdeBindings".typeRef».CONTENT_ASSIST)) - .to(«grammar.lexerClass».class); - ''') - .addTypeToType('org.eclipse.xtext.ide.editor.contentassist.antlr.IContentAssistParser'.typeRef, grammar.parserClass) - .addConfiguredBinding("IProposalConflictHelper", ''' - binder.bind(org.eclipse.xtext.ide.editor.contentassist.IProposalConflictHelper.class) - .to(org.eclipse.xtext.ide.editor.contentassist.antlr.AntlrProposalConflictHelper.class); - ''') - if (partialParsing) { - ideBindings.addTypeToType( - "org.eclipse.xtext.ide.editor.contentassist.antlr.ContentAssistContextFactory".typeRef, - "org.eclipse.xtext.ide.editor.contentassist.antlr.PartialContentAssistContextFactory".typeRef - ) - } - if (hasSyntheticTerminalRule) { - ideBindings.addTypeToType( - "org.eclipse.xtext.ide.editor.contentassist.CompletionPrefixProvider".typeRef, - "org.eclipse.xtext.ide.editor.contentassist.IndentationAwareCompletionPrefixProvider".typeRef - ) - } - ideBindings.contributeTo(language.eclipsePluginGenModule) - } - - override JavaFileAccess generateContentAssistParser() { - val extension naming = contentAssistNaming - val file = fileFactory.createGeneratedJavaFile(grammar.parserClass) - file.content = ''' - public class «grammar.parserClass.simpleName» extends «grammar.getParserSuperClass(partialParsing)» { - - «grammar.initNameMappings()» - - @«Inject» - private «grammar.grammarAccess» grammarAccess; - - @«Inject» - private «ISemanticPredicates.typeRef» predicates; - - /** - * Creates compilation context. - * - * @param Input - * Stream - * @return Compilation context - */ - protected «ParserContext.typeRef» createParserContext() { - return new ParserContext(); - } - - @Override - protected «grammar.internalParserClass» createParser() { - «grammar.internalParserClass» result = new «grammar.internalParserClass»(null); - result.setGrammarAccess(grammarAccess); - result.setParserContext(createParserContext()); - result.setPredicates((«grammar.semanticPredicatesFullName.typeRef»)predicates); - return result; - } - - «IF hasSyntheticTerminalRule» - @Override - protected «TokenSource» createLexer(«CharStream» stream) { - return new «grammar.tokenSourceClass»(super.createLexer(stream)); - } - - «ENDIF» - @Override - protected String getRuleName(«AbstractElement» element) { - return nameMappings.getRuleName(element); - } - - @Override - protected String[] getInitialHiddenTokens() { - return new String[] { «FOR hidden : grammar.initialHiddenTokens SEPARATOR ", "»"«hidden»"«ENDFOR» }; - } - - public «grammar.grammarAccess» getGrammarAccess() { - return this.grammarAccess; - } - - public void setGrammarAccess(«grammar.grammarAccess» grammarAccess) { - this.grammarAccess = grammarAccess; - } - - public NameMappings getNameMappings() { - return nameMappings; - } - - public void setNameMappings(NameMappings nameMappings) { - this.nameMappings = nameMappings; - } - } - ''' - return file - } - - protected override generateProductionGrammar() { - val extension naming = productionNaming - val fsa = projectConfig.runtime.srcGen - productionGenerator.lexerSuperClassName = lexerSuperClassName; - productionGenerator.generate(grammar, options, fsa) - - runAntlr(grammar.parserGrammar, grammar.lexerGrammar, fsa) - - simplifyUnorderedGroupPredicatesIfRequired(grammar, fsa, grammar.internalParserClass) - splitParserAndLexerIfEnabled(fsa, grammar.internalParserClass, grammar.lexerClass) - normalizeTokens(fsa, grammar.lexerGrammar.tokensFileName) - suppressWarnings(fsa, grammar.internalParserClass, grammar.lexerClass) - normalizeLineDelimiters(fsa, grammar.internalParserClass, grammar.lexerClass) - - /* filter and ruleNames for flattened grammar */ - val RuleFilter filter = new RuleFilter(); - filter.discardUnreachableRules = true - filter.discardTerminalRules = false - val RuleNames ruleNames = RuleNames.getRuleNames(grammar, true); - - val keywordAnalysisHelper = new KeywordAnalysisHelper(productionGenerator.grammarNaming.getParserGrammar(grammar).grammarFileName, new FlattenedGrammarAccess(ruleNames, filter).getFlattenedGrammar(), identifierRules, reservedWords, keywords, grammarUtil) - keywordAnalysisHelper.printReport(fsa.path); - keywordAnalysisHelper.printViolations(fsa.path); - } - - def JavaFileAccess generateAbstractSemanticPredicate() { - val file = fileFactory.createGeneratedJavaFile(TypeReference.typeRef(grammar.getSemanticPredicatesFullName)) - file.importType(TypeReference.typeRef(grammar.semanticPredicatesFullName)) - file.importType(TypeReference.typeRef(ISemanticPredicates.AbstractSemanticPredicates)) - if(!getGrammar().predicates.isEmpty){ - file.importType(TypeReference.typeRef(ParserContext)) - file.importType(TypeReference.typeRef(Token)) - for (inheritedGrammar : annotations.allInheritedGrammars(getGrammar())) { - file.importType(TypeReference.typeRef(GrammarUtil.getNamespace(inheritedGrammar) + ".grammar." + GrammarUtil.getSimpleName(inheritedGrammar) + CLASS_SUFFIX)) - } - } - file.importType(TypeReference.typeRef(Singleton)) - file.content = ''' - /** - * Provides semantic predicates as specified in the grammar. Language may need to override - * this class in order to provide concrete implementations for predicates. - */ - @Singleton - public class «grammar.getSemanticPredicatesSimpleName()» extends AbstractSemanticPredicates { - «FOR element : getGrammar().predicates» - - /** - * Predicate for grammar rule «element.name». - * - * @param input - * Input from Lexer - * @return {@code true} if the grammar rule is enabled, {@code false} otherwise - */ - public boolean «element.name»(ParserContext parserContext) { - «element.getRulePredicateCondition» - } - «ENDFOR» - - «FOR element : getGrammar().predicates» - - /** - * Message for «element.name» predicate. - * - * @param input - * Input from Lexer - * @return {@code true} if the grammar rule is enabled, {@code false} otherwise - */ - public String «element.message»(Token token) { - «element.getRulePredicateMessage» - } - «ENDFOR» - } - ''' - file - } - - /** - * Returns name for the predicate message method. - * - * @param predicate - * Semantic predicate, must not be {@code null} - * @return Content for the predicate message method - */ - def String getRulePredicateMessage(SemanticPredicate predicate){ - if(predicate.keywords!==null) - ''' - return "Unexpected: " + token.getText() + ". Expected: '«Joiner.on("', '").join(predicate.keywords)»'"; - ''' - else - ''' - /* Default message. Intended to be overridden. */ - return "Condition «predicate.name» is not fullfilled "; - ''' - } - - /** - * Returns predicate condition (default condition). - * - * @param predicate - * Semantic predicate, must not be {@code null} - * @return A string containing the condition for the semantic predicate or {@code null} - */ - def String getRulePredicateCondition(SemanticPredicate predicate) { - if (predicate.keywords !== null) { - val condition = predicate.keywords.stream(). - map([s | '''"«s»".equalsIgnoreCase(text)''']).collect(Collectors.joining(" || ")) - ''' - String text = parserContext.getInput().LT(1).getText(); - return «condition»; - ''' - } else { - return "return " + predicate.grammar + CLASS_SUFFIX + "." + predicate.name + "(parserContext);\n" - } - } - - override JavaFileAccess generateProductionParser() { - val extension naming = productionNaming - val file = fileFactory.createGeneratedJavaFile(grammar.parserClass) - file.importType(TypeReference.typeRef(grammar.semanticPredicatesFullName)) - file.importType(TypeReference.typeRef(ISemanticPredicates)) - - file.content = ''' - public class «grammar.parserClass.simpleName» extends «AbstractContextualAntlrParser» { - - @«Inject» - private «grammar.grammarAccess» grammarAccess; - - @«Inject» - private ISemanticPredicates predicates; - - @Override - protected void setInitialHiddenTokens(«XtextTokenStream» tokenStream) { - tokenStream.setInitialHiddenTokens(«FOR hidden : grammar.initialHiddenTokens SEPARATOR ", "»"«hidden»"«ENDFOR»); - } - - «IF hasSyntheticTerminalRule» - @Override - protected «TokenSource» createLexer(«CharStream» stream) { - return new «grammar.tokenSourceClass»(super.createLexer(stream)); - } - - /** - * Indentation aware languages do not support partial parsing since the lexer is inherently stateful. - * Override and return {@code true} if your terminal splitting is stateless. - */ - @Override - protected boolean isReparseSupported() { - return false; - } - «ENDIF» - - @Override - protected «grammar.internalParserClass» createParser(«XtextTokenStream» stream) { - return new «grammar.internalParserClass»(stream, getGrammarAccess(), createParserContext(), («grammar.semanticPredicatesSimpleName») predicates); - } - - @Override - protected String getDefaultRuleName() { - return "«grammar.allParserRules.head.originalElement.name»"; - } - - public «grammar.grammarAccess» getGrammarAccess() { - return this.grammarAccess; - } - - public void setGrammarAccess(«grammar.grammarAccess» grammarAccess) { - this.grammarAccess = grammarAccess; - } - } - ''' - file - } - - override protected addUiBindingsAndImports() { - /* Overridden to remove the binding for IContentAssistParser, which we bind at a different place. - * If we would register it here, we would have conflicting bindings for IContentAssistParser. - */ - val extension naming = contentAssistNaming - val caLexerClass = grammar.lexerClass - - if (projectConfig.genericIde.manifest !== null) { - projectConfig.genericIde.manifest=>[ - exportedPackages += #[ - caLexerClass.packageName, - grammar.parserClass.packageName, - grammar.internalParserClass.packageName - ] - ] - } - val uiBindings = new GuiceModuleAccess.BindingFactory() - .addTypeToType( - "org.eclipse.xtext.ui.editor.contentassist.IProposalConflictHelper".typeRef, - "org.eclipse.xtext.ui.editor.contentassist.antlr.AntlrProposalConflictHelper".typeRef - ) - .addConfiguredBinding("ContentAssistLexer", ''' - binder.bind(«grammar.lexerSuperClass».class) - .annotatedWith(«Names».named(«"org.eclipse.xtext.ide.LexerIdeBindings".typeRef».CONTENT_ASSIST)) - .to(«caLexerClass».class); - ''') - // registration of the 'ContentAssistLexer' is put in front of the 'HighlightingLexer' - // in order to let 'caLexerClass' get added to the imports, since it is referenced - // several times and the lexer classes' simple names are usually identical - .addConfiguredBinding("HighlightingLexer", ''' - binder.bind(«Lexer».class) - .annotatedWith(«Names».named(«"org.eclipse.xtext.ide.LexerIdeBindings".typeRef».HIGHLIGHTING)) - .to(«productionNaming.getLexerClass(grammar)».class); - ''') - .addConfiguredBinding("HighlightingTokenDefProvider", ''' - binder.bind(«ITokenDefProvider».class) - .annotatedWith(«Names».named(«"org.eclipse.xtext.ide.LexerIdeBindings".typeRef».HIGHLIGHTING)) - .to(«AntlrTokenDefProvider».class); - ''') - .addTypeToType( - new TypeReference("org.eclipse.xtext.ui.editor.contentassist", "ContentAssistContext.Factory"), - "org.eclipse.xtext.ui.editor.contentassist.antlr.DelegatingContentAssistContextFactory".typeRef - ) - .addConfiguredBinding("ContentAssistLexerProvider", ''' - binder.bind(«caLexerClass».class).toProvider(«LexerProvider».create(«caLexerClass».class)); - ''') - - if (projectConfig.genericIde.srcGen !== null) { - uiBindings.addTypeToType("org.eclipse.xtext.ide.editor.contentassist.antlr.IContentAssistParser".typeRef, grammar.parserClass) - } - - if (hasSyntheticTerminalRule) { - uiBindings.addTypeToType( - "org.eclipse.xtext.ide.editor.contentassist.CompletionPrefixProvider".typeRef, - "org.eclipse.xtext.ide.editor.contentassist.IndentationAwareCompletionPrefixProvider".typeRef - ) - } - uiBindings.contributeTo(language.eclipsePluginGenModule) - } -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/GrammarRuleAnnotations.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/GrammarRuleAnnotations.java new file mode 100644 index 0000000000..325ae56639 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/GrammarRuleAnnotations.java @@ -0,0 +1,751 @@ +/** + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * Contributors: + * Avaloq Group AG - initial API and implementation + */ +package com.avaloq.tools.ddk.xtext.generator.parser.common; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.notify.impl.AdapterImpl; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Action; +import org.eclipse.xtext.Alternatives; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.CrossReference; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Group; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.nodemodel.ICompositeNode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.util.internal.EmfAdaptable; +import org.eclipse.xtext.xbase.lib.Pure; +import org.eclipse.xtext.xbase.lib.util.ToStringBuilder; +import org.eclipse.xtext.xtext.RuleWithParameterValues; +import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions; + +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.inject.Inject; + +// CHECKSTYLE:OFF MagicNumber +@SuppressWarnings("nls") +public class GrammarRuleAnnotations { + + @EmfAdaptable + public static class NoBacktrack { + public static class NoBacktrackAdapter extends AdapterImpl { + private final GrammarRuleAnnotations.NoBacktrack element; + + public NoBacktrackAdapter(final GrammarRuleAnnotations.NoBacktrack element) { + this.element = element; + } + + public GrammarRuleAnnotations.NoBacktrack get() { + return this.element; + } + + @Override + public boolean isAdapterForType(final Object object) { + return object == GrammarRuleAnnotations.NoBacktrack.class; + } + } + + public static GrammarRuleAnnotations.NoBacktrack findInEmfObject(final Notifier emfObject) { + for (Adapter adapter : emfObject.eAdapters()) { + if (adapter instanceof GrammarRuleAnnotations.NoBacktrack.NoBacktrackAdapter) { + return ((GrammarRuleAnnotations.NoBacktrack.NoBacktrackAdapter) adapter).get(); + } + } + return null; + } + + @SuppressWarnings("PMD.ForLoopVariableCount") + public static GrammarRuleAnnotations.NoBacktrack removeFromEmfObject(final Notifier emfObject) { + List adapters = emfObject.eAdapters(); + for (int i = 0, max = adapters.size(); i < max; i++) { + Adapter adapter = adapters.get(i); + if (adapter instanceof GrammarRuleAnnotations.NoBacktrack.NoBacktrackAdapter) { + emfObject.eAdapters().remove(i); + return ((GrammarRuleAnnotations.NoBacktrack.NoBacktrackAdapter) adapter).get(); + } + } + return null; + } + + public void attachToEmfObject(final Notifier emfObject) { + NoBacktrack result = findInEmfObject(emfObject); + if (result != null) { + throw new IllegalStateException("The given EMF object already contains an adapter for NoBacktrack"); + } + GrammarRuleAnnotations.NoBacktrack.NoBacktrackAdapter adapter = new GrammarRuleAnnotations.NoBacktrack.NoBacktrackAdapter(this); + emfObject.eAdapters().add(adapter); + } + + @Override + @Pure + public int hashCode() { + return 1; + } + + @Override + @Pure + public boolean equals(final Object obj) { + return this == obj || (obj != null && getClass() == obj.getClass()); + } + + @Override + @Pure + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + return b.toString(); + } + } + + /** + * Gated predicate: {condition}?=> + * Validating predicate: {condition message}? + */ + @EmfAdaptable + public static class SemanticPredicate { + public static class SemanticPredicateAdapter extends AdapterImpl { + private final GrammarRuleAnnotations.SemanticPredicate element; + + public SemanticPredicateAdapter(final GrammarRuleAnnotations.SemanticPredicate element) { + this.element = element; + } + + public GrammarRuleAnnotations.SemanticPredicate get() { + return this.element; + } + + @Override + public boolean isAdapterForType(final Object object) { + return object == GrammarRuleAnnotations.SemanticPredicate.class; + } + } + + private final String name; + + private final String message; + + private final String grammar; + + private final List keywords; + + public static GrammarRuleAnnotations.SemanticPredicate findInEmfObject(final Notifier emfObject) { + for (Adapter adapter : emfObject.eAdapters()) { + if (adapter instanceof GrammarRuleAnnotations.SemanticPredicate.SemanticPredicateAdapter) { + return ((GrammarRuleAnnotations.SemanticPredicate.SemanticPredicateAdapter) adapter).get(); + } + } + return null; + } + + @SuppressWarnings("PMD.ForLoopVariableCount") + public static GrammarRuleAnnotations.SemanticPredicate removeFromEmfObject(final Notifier emfObject) { + List adapters = emfObject.eAdapters(); + for (int i = 0, max = adapters.size(); i < max; i++) { + Adapter adapter = adapters.get(i); + if (adapter instanceof GrammarRuleAnnotations.SemanticPredicate.SemanticPredicateAdapter) { + emfObject.eAdapters().remove(i); + return ((GrammarRuleAnnotations.SemanticPredicate.SemanticPredicateAdapter) adapter).get(); + } + } + return null; + } + + public void attachToEmfObject(final Notifier emfObject) { + SemanticPredicate result = findInEmfObject(emfObject); + if (result != null) { + throw new IllegalStateException("The given EMF object already contains an adapter for SemanticPredicate"); + } + GrammarRuleAnnotations.SemanticPredicate.SemanticPredicateAdapter adapter = new GrammarRuleAnnotations.SemanticPredicate.SemanticPredicateAdapter(this); + emfObject.eAdapters().add(adapter); + } + + public SemanticPredicate(final String name, final String message, final String grammar, final List keywords) { + this.name = name; + this.message = message; + this.grammar = grammar; + this.keywords = keywords; + } + + @Override + @Pure + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.name == null) ? 0 : this.name.hashCode()); + result = prime * result + ((this.message == null) ? 0 : this.message.hashCode()); + result = prime * result + ((this.grammar == null) ? 0 : this.grammar.hashCode()); + return prime * result + ((this.keywords == null) ? 0 : this.keywords.hashCode()); + } + + @Override + @Pure + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GrammarRuleAnnotations.SemanticPredicate other = (GrammarRuleAnnotations.SemanticPredicate) obj; + if (this.name == null) { + if (other.name != null) { + return false; + } + } else if (!this.name.equals(other.name)) { + return false; + } + if (this.message == null) { + if (other.message != null) { + return false; + } + } else if (!this.message.equals(other.message)) { + return false; + } + if (this.grammar == null) { + if (other.grammar != null) { + return false; + } + } else if (!this.grammar.equals(other.grammar)) { + return false; + } + if (this.keywords == null) { + if (other.keywords != null) { + return false; + } + } else if (!this.keywords.equals(other.keywords)) { + return false; + } + return true; + } + + @Override + @Pure + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("name", this.name); + b.add("message", this.message); + b.add("grammar", this.grammar); + b.add("keywords", this.keywords); + return b.toString(); + } + + @Pure + public String getName() { + return this.name; + } + + @Pure + public String getMessage() { + return this.message; + } + + @Pure + public String getGrammar() { + return this.grammar; + } + + @Pure + public List getKeywords() { + return this.keywords; + } + } + + @EmfAdaptable + public static class GrammarAnnotations { + public static class GrammarAnnotationsAdapter extends AdapterImpl { + private final GrammarRuleAnnotations.GrammarAnnotations element; + + public GrammarAnnotationsAdapter(final GrammarRuleAnnotations.GrammarAnnotations element) { + this.element = element; + } + + public GrammarRuleAnnotations.GrammarAnnotations get() { + return this.element; + } + + @Override + public boolean isAdapterForType(final Object object) { + return object == GrammarRuleAnnotations.GrammarAnnotations.class; + } + } + + private final List predicates; + + public static GrammarRuleAnnotations.GrammarAnnotations findInEmfObject(final Notifier emfObject) { + for (Adapter adapter : emfObject.eAdapters()) { + if (adapter instanceof GrammarRuleAnnotations.GrammarAnnotations.GrammarAnnotationsAdapter) { + return ((GrammarRuleAnnotations.GrammarAnnotations.GrammarAnnotationsAdapter) adapter).get(); + } + } + return null; + } + + @SuppressWarnings("PMD.ForLoopVariableCount") + public static GrammarRuleAnnotations.GrammarAnnotations removeFromEmfObject(final Notifier emfObject) { + List adapters = emfObject.eAdapters(); + for (int i = 0, max = adapters.size(); i < max; i++) { + Adapter adapter = adapters.get(i); + if (adapter instanceof GrammarRuleAnnotations.GrammarAnnotations.GrammarAnnotationsAdapter) { + emfObject.eAdapters().remove(i); + return ((GrammarRuleAnnotations.GrammarAnnotations.GrammarAnnotationsAdapter) adapter).get(); + } + } + return null; + } + + public void attachToEmfObject(final Notifier emfObject) { + GrammarAnnotations result = findInEmfObject(emfObject); + if (result != null) { + throw new IllegalStateException("The given EMF object already contains an adapter for GrammarAnnotations"); + } + GrammarRuleAnnotations.GrammarAnnotations.GrammarAnnotationsAdapter adapter = new GrammarRuleAnnotations.GrammarAnnotations.GrammarAnnotationsAdapter(this); + emfObject.eAdapters().add(adapter); + } + + public GrammarAnnotations(final List predicates) { + this.predicates = predicates; + } + + @Override + @Pure + public int hashCode() { + return 31 * 1 + ((this.predicates == null) ? 0 : this.predicates.hashCode()); + } + + @Override + @Pure + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GrammarRuleAnnotations.GrammarAnnotations other = (GrammarRuleAnnotations.GrammarAnnotations) obj; + if (this.predicates == null) { + if (other.predicates != null) { + return false; + } + } else if (!this.predicates.equals(other.predicates)) { + return false; + } + return true; + } + + @Override + @Pure + public String toString() { + ToStringBuilder b = new ToStringBuilder(this); + b.add("predicates", this.predicates); + return b.toString(); + } + + @Pure + public List getPredicates() { + return this.predicates; + } + } + + /** + * Pattern to deactivate backtracking for single rule. + */ + private static final Pattern NO_BACKTRACK_ANNOTATION_PATTERN = Pattern.compile("@NoBacktrack", Pattern.MULTILINE); // $NON-NLS-1$ + + /** + * Pattern to search for keyword rule annotations and extracting list of comma-separated keywords. + */ + private static final Pattern KEYWORD_RULE_ANNOTATION_PATTERN = Pattern.compile("@KeywordRule\\(([\\w\\s,]+)\\)", + Pattern.MULTILINE); // $NON-NLS-1$ + + /** + * Pattern to search for semantic predicate rule annotation that enables the given rule. + */ + private static final Pattern SEMANTIC_PREDICATE_PATTERN = Pattern.compile("@SemanticPredicate\\(([\\s]*[\\w]+)[\\s]*\\)", + Pattern.MULTILINE); // $NON-NLS-1$ + + /** + * Splits comma separated list of keywords. + */ + private static final Splitter KEYWORDS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); + + @Inject + private GrammarAccessExtensions grammarExtensions; + + public boolean hasNoBacktrackAnnotation(final AbstractRule rule) { + return getNoBacktrackAnnotation(rule) != null; + } + + /** + * Checks if the given element is contained in a rule with a NoBacktrack annotation. + * + * @param element + * Grammar element + * @return {@code true} if there is an annotation on the enclosing rule, {@code false} otherwise + */ + public boolean hasNoBacktrackAnnotation(final AbstractElement element) { + return hasNoBacktrackAnnotation(GrammarUtil.containingRule(element)); + } + + public boolean hasSemanticPredicate(final AbstractElement element) { + return findPredicate(element) != null; + } + + public boolean hasValidatingPredicate(final AbstractRule rule) { + return getSemanticPredicateAnnotation(rule) != null; + } + + /** + * Returns disambiguating/validating semantic predicate. + * + * @param element + * Xtext grammar element + * @return A string containing the semantic predicate or an empty string + */ + public String generateValidatingPredicate(final AbstractRule rule) { + final SemanticPredicate predicate = getSemanticPredicateAnnotation(rule); + if (predicate != null) { + return generateValidatingPredicate(predicate); + } + return ""; + } + + public boolean isGatedPredicateRequired(final AbstractElement element) { + return isStartingWithPredicatedRule(element) && isAlternativeAvailable(element); + } + + /** + * Returns gated semantic predicate. + * + * @param element + * Xtext grammar element + * @return A string containing the semantic predicate or an empty string + */ + public String generateGatedPredicate(final AbstractElement element) { + final SemanticPredicate predicate = findPredicate(element); + if (predicate != null) { + return generateGatedPredicate(predicate); + } + return ""; + } + + public GrammarAnnotations annotateGrammar(final Grammar grammar) { + GrammarAnnotations annotations = GrammarAnnotations.findInEmfObject(grammar); + if (annotations == null) { + List collected = grammar.getRules().stream() + .map(r -> annotateRule(r)) + .filter(a -> a != null) + .collect(Collectors.toList()); + annotations = new GrammarAnnotations(collected); + annotations.attachToEmfObject(grammar); + + Set inheritedGrammars = allInheritedGrammars(grammar); + for (Grammar inheritedGrammar : inheritedGrammars) { + GrammarAnnotations inheritedAnnotations = GrammarAnnotations.findInEmfObject(inheritedGrammar); + if (inheritedAnnotations == null) { + List inheritedCollected = inheritedGrammar.getRules().stream() + .map(r -> annotateRule(r)) + .filter(a -> a != null) + .collect(Collectors.toList()); + inheritedAnnotations = new GrammarAnnotations(inheritedCollected); + inheritedAnnotations.attachToEmfObject(inheritedGrammar); + } + annotations.predicates.addAll(inheritedAnnotations.predicates); + } + } + return annotations; + } + + public Set allInheritedGrammars(final Grammar grammar) { + Set result = Sets.newHashSet(); + List allParserRules = GrammarUtil.allParserRules(grammar); + for (AbstractRule rule : allParserRules) { + Grammar ruleGrammar = GrammarUtil.getGrammar(rule); + if (!ruleGrammar.equals(grammar) && this.isSemanticPredicate(rule)) { + result.add(ruleGrammar); + } + } + return new HashSet<>(result); + } + + public List predicates(final Grammar grammar) { + return annotateGrammar(grammar).predicates; + } + + /** + * Checks whether this abstract element leads directly to a keyword rule. + * + * @param element + * Element call + * @return {@code true} if the rule leads to a keyword rule + */ + public boolean isStartingWithPredicatedRule(final AbstractElement element) { + if (element instanceof CrossReference) { + return findPredicate(((CrossReference) element).getTerminal()) != null; + } + return findPredicate(element) != null; + } + + /** + * Finds the predicated rule the given element leads to. + * + * @param element + * Current element + * @return Keyword rule or {@code null} if the given element does not lead to a keyword rule + */ + public SemanticPredicate findPredicate(final AbstractElement element) { + if (element instanceof RuleCall) { + final SemanticPredicate predicate = getSemanticPredicateAnnotation(((RuleCall) element).getRule()); + if (predicate != null) { + return predicate; + } + return findPredicate(((RuleCall) element).getRule().getAlternatives()); + } + if (GrammarUtil.isOptionalCardinality(element)) { + return null; + } + if (element instanceof CrossReference) { + return findPredicate(((CrossReference) element).getTerminal()); + } + if (element instanceof Group) { + final AbstractElement firstNonActionElement = getFirstNonActionElement((Group) element); + if (firstNonActionElement != null) { + return findPredicate(firstNonActionElement); + } + } + if (element instanceof Assignment) { + return findPredicate(((Assignment) element).getTerminal()); + } + return null; + } + + public boolean isRuleWithPredicate(final AbstractRule rule) { + return getSemanticPredicateAnnotation(rule) != null; + } + + public AbstractElement getFirstNonActionElement(final Group group) { + for (AbstractElement groupElement : group.getElements()) { + if (!(groupElement instanceof Action)) { + return groupElement; + } + } + return null; + } + + /** + * Checks whether there is a viable alternative (i.e. not potentially gated by another gated semantic predicate) for the current element. + * If there is no alternative we should not insert gated semantic predicates, but validating semantic predicates, so we get a better error recovery and + * a correct message. We should not also propagate validating predicates in the callers. If we do so this will damage error recovery in the callers. + * + * @param element + * Grammar element + * @return {@code true} if there is an alternative available and the gated semantic predicate can be inserted + */ + public boolean isAlternativeAvailable(final AbstractElement element) { + if (GrammarUtil.isOptionalCardinality(element)) { + return true; + } + final EObject container = element.eContainer(); + if (container instanceof Assignment) { + return isAlternativeAvailable((AbstractElement) container); + } else if (container instanceof CrossReference) { + return isAlternativeAvailable((AbstractElement) container); + } else if (container instanceof Group) { + if (isFirstNonActionElement((Group) container, element)) { + return isAlternativeAvailable((AbstractElement) container); + } + } else if (container instanceof Alternatives) { + for (AbstractElement alternative : ((Alternatives) container).getElements()) { + if (!Objects.equals(alternative, element)) { + return true; + } + } + } + return false; + } + + /** + * Checks if the given grammar element is the first non action element in the given group. + * + * @param group + * where the position is checked, must not be {@code null} + * @param element + * to check, must not be {@code null} + * @return {@code true}, if is first non action element is the given element, {@code false} otherwise + */ + public boolean isFirstNonActionElement(final Group group, final AbstractElement element) { + return Objects.equals(element, getFirstNonActionElement(group)); + } + + public String generateGatedPredicate(final SemanticPredicate predicate) { + return "{predicates." + predicate.name + "(parserContext)}?=>"; + } + + public String generateValidatingPredicate(final SemanticPredicate predicate) { + return "{predicates." + predicate.name + "(parserContext) /* @ErrorMessage(" + predicate.message + ") */}?"; + } + + /** + * Translate annotations in comments into predicate annotations. + * + *

+ * Gated vs. Disambiguating predicates On the first look a disambiguating semantic predicate + * would be a right choice. However inserting them would be much more difficult. The reason is that we need + * not only insert them in the beginning of the current rule, but also propagate before actions into alternatives + * that call this rule. We have to do the same for gated predicated, however there is one important difference. + * If we have an alternative like {@code rule: (Keyword1 | Keyword2) Keyword3 ;} then we need to insert the predicate + * only before the first alternative (Keyword1). If we insert disambiguating semantic predicate before both + * the exception the predicate may throw can destroy recovery for the entire rule i.e. will not try going to Keyword3. + * We, however, need validating predicate in the beginning of each rule Keyword2, Keyword3. This requires an + * extra analysis which might be a next step. Gated predicates we can safely insert before Keyword1 and Keyword2. + * This will have no negative impact. We still need validating predicates in Keyword rules themselves. + *

+ */ + public SemanticPredicate annotateRule(final AbstractRule rule) { + final String text = getText(rule); + SemanticPredicate predicate = null; + if (text != null) { + final String ruleGrammarName = GrammarUtil.getSimpleName(GrammarUtil.getGrammar(rule)); + predicate = getKeywordRulePredicate(text, rule.getName(), ruleGrammarName); + if (predicate != null) { + predicate.attachToEmfObject(rule); + } + final SemanticPredicate semanticPredicate = getSemanticPredicate(text, ruleGrammarName); + if (semanticPredicate != null) { + if (predicate != null) { + throw new IllegalArgumentException("You may not combine keyword annotations with semantic predicate annotations on one rule: " + rule.getName()); + } + semanticPredicate.attachToEmfObject(rule); + predicate = semanticPredicate; + } + final NoBacktrack noBacktrack = getNoBacktrack(text); + if (noBacktrack != null) { + noBacktrack.attachToEmfObject(rule); + } + } + return predicate; + } + + public SemanticPredicate getSemanticPredicateAnnotation(final AbstractRule rule) { + if (rule != null) { + RuleWithParameterValues ruleWithParams = RuleWithParameterValues.findInEmfObject(rule); + AbstractRule original = null; + if (ruleWithParams != null) { + original = ruleWithParams.getOriginal(); + } + if (original == null) { + original = rule; + } + return SemanticPredicate.findInEmfObject(original); + } + return null; + } + + public NoBacktrack getNoBacktrackAnnotation(final AbstractRule rule) { + if (rule != null) { + AbstractRule original = RuleWithParameterValues.findInEmfObject(rule).getOriginal(); + if (original == null) { + original = rule; + } + return NoBacktrack.findInEmfObject(original); + } + return null; + } + + /** + * Checks if the given rule contains {@code @KeywordRule(kw1,kw2)} annotation. + */ + public SemanticPredicate getKeywordRulePredicate(final String text, final String ruleName, final String grammar) { + final Matcher matcher = KEYWORD_RULE_ANNOTATION_PATTERN.matcher(text); + if (matcher.find()) { + List keywordsList = Lists.newArrayList(KEYWORDS_SPLITTER.split(matcher.group(1))); + return new SemanticPredicate( + "is" + ruleName + "Enabled", + "get" + ruleName + "EnabledMessage", + grammar, + keywordsList); + } + return null; + } + + /** + * Checks if the given rule contains {@code @SemanticPredicate(condition)} annotation. + */ + public SemanticPredicate getSemanticPredicate(final String text, final String grammar) { + final Matcher matcher = SEMANTIC_PREDICATE_PATTERN.matcher(text); + if (matcher.find()) { + return new SemanticPredicate( + matcher.group(1), + "get" + matcher.group(1) + "Message", + grammar, + null); + } + return null; + } + + /** + * Checks if the given rule contains a NoBacktrack annotation. + */ + public NoBacktrack getNoBacktrack(final String text) { + final Matcher matcher = NO_BACKTRACK_ANNOTATION_PATTERN.matcher(text); + if (matcher.find()) { + return new NoBacktrack(); + } + return null; + } + + public String getText(final AbstractRule rule) { + final ICompositeNode node = NodeModelUtils.getNode(rule); + if (node != null) { + return node.getText(); + } else { + return grammarExtensions.grammarFragmentToString(rule, ""); + } + } + + /** + * Checks if the given rule contains a keyword rule annotation. + * + * @param rule + * Grammar rule + * @return {@code true} if there is an annotation, {@code false} otherwise + */ + public boolean isSemanticPredicate(final AbstractRule rule) { + String text = getText(rule); + if (text != null) { + final Matcher matcher = SEMANTIC_PREDICATE_PATTERN.matcher(text); + if (matcher.find()) { + return true; + } + } + return false; + } +} +// CHECKSTYLE:ON diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/GrammarRuleAnnotations.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/GrammarRuleAnnotations.xtend deleted file mode 100644 index 13097b749b..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/GrammarRuleAnnotations.xtend +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * Contributors: - * Avaloq Group AG - initial API and implementation - */ - -package com.avaloq.tools.ddk.xtext.generator.parser.common - -import com.google.common.base.Splitter -import com.google.common.collect.Lists -import java.util.List -import java.util.regex.Pattern -import org.eclipse.xtend.lib.annotations.Data -import org.eclipse.xtext.AbstractElement -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.Action -import org.eclipse.xtext.Alternatives -import org.eclipse.xtext.Assignment -import org.eclipse.xtext.CrossReference -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.GrammarUtil -import org.eclipse.xtext.Group -import org.eclipse.xtext.RuleCall -import org.eclipse.xtext.nodemodel.util.NodeModelUtils -import org.eclipse.xtext.util.internal.EmfAdaptable -import org.eclipse.xtext.xtext.RuleWithParameterValues -import java.util.stream.Collectors -import java.util.Set -import com.google.common.collect.Sets -import org.eclipse.xtext.xtext.generator.grammarAccess.GrammarAccessExtensions -import com.google.inject.Inject - -class GrammarRuleAnnotations { - - /** - * Pattern to deactivate backtracking for single rule. - */ - static final Pattern NO_BACKTRACK_ANNOTATION_PATTERN = Pattern.compile("@NoBacktrack", Pattern.MULTILINE); // $NON-NLS-1$ - /** - * Pattern to search for keyword rule annotations and extracting list of comma-separated keywords. - */ - static final Pattern KEYWORD_RULE_ANNOTATION_PATTERN = Pattern.compile("@KeywordRule\\(([\\w\\s,]+)\\)", - Pattern.MULTILINE); // $NON-NLS-1$ - /** - * Pattern to search for semantic predicate rule annotation that enables the given rule. - */ - static final Pattern SEMANTIC_PREDICATE_PATTERN = Pattern.compile("@SemanticPredicate\\(([\\s]*[\\w]+)[\\s]*\\)", - Pattern.MULTILINE); // $NON-NLS-1$ - /** - * Splits comma separated list of keywords. - */ - static final Splitter KEYWORDS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings(); - - @Inject - GrammarAccessExtensions grammarExtensions; - - @EmfAdaptable - @Data - static class NoBacktrack { - } - -/** - * Gated predicate: {condition}?=> - * Validating predicate: {condition message}? - */ - @EmfAdaptable - @Data - static class SemanticPredicate { - val String name - val String message - val String grammar - val List keywords - } - - @EmfAdaptable - @Data - static class GrammarAnnotations { - val List predicates - } - - def boolean hasNoBacktrackAnnotation(AbstractRule rule){ - return getNoBacktrackAnnotation(rule) !== null - } - - /** - * Checks if the given element is contained in a rule with a NoBacktrack annotation. - * - * @param element - * Grammar element - * @return {@code true} if there is an annotation on the enclosing rule, {@code false} otherwise - */ - def boolean hasNoBacktrackAnnotation(AbstractElement element){ - return hasNoBacktrackAnnotation(GrammarUtil.containingRule(element)) - } - - def boolean hasSemanticPredicate(AbstractElement element){ - return findPredicate(element) !== null - } - - def boolean hasValidatingPredicate(AbstractRule rule){ - return getSemanticPredicateAnnotation(rule) !== null - } - - /** - * Returns disambiguating/validating semantic predicate. - * - * @param element - * Xtext grammar element - * @return A string containing the semantic predicate or an empty string - */ - def String generateValidatingPredicate(AbstractRule rule){ - val predicate = getSemanticPredicateAnnotation(rule) - if(predicate !== null) return generateValidatingPredicate(predicate) - return ""; - } - - def boolean isGatedPredicateRequired(AbstractElement element){ - return isStartingWithPredicatedRule(element) && isAlternativeAvailable(element); - } - - /** - * Returns gated semantic predicate. - * - * @param element - * Xtext grammar element - * @return A string containing the semantic predicate or an empty string - */ - def String generateGatedPredicate(AbstractElement element){ - val predicate = findPredicate(element) - if(predicate !== null) return generateGatedPredicate(predicate) - return ""; - } - - def GrammarAnnotations annotateGrammar(Grammar grammar){ - var annotations = GrammarAnnotations.findInEmfObject(grammar) - if (annotations === null) { - annotations = new GrammarAnnotations(grammar.rules.stream().map[r | annotateRule(r)].filter([a | a !== null]).collect(Collectors.toList)) - annotations.attachToEmfObject(grammar) - - var inheritedGrammars = allInheritedGrammars(grammar) - for (inheritedGrammar : inheritedGrammars) { - var inheritedAnnotations = GrammarAnnotations.findInEmfObject(inheritedGrammar) - if (inheritedAnnotations === null) { - inheritedAnnotations = new GrammarAnnotations(inheritedGrammar.rules.stream().map[r | annotateRule(r)].filter([a | a !== null]).collect(Collectors.toList)) - inheritedAnnotations.attachToEmfObject(inheritedGrammar) - } - annotations.predicates.addAll(inheritedAnnotations.predicates) - } - } - - return annotations - } - - def Set allInheritedGrammars(Grammar grammar) { - var result = Sets.newHashSet(); - for (AbstractRule rule : GrammarUtil.allParserRules(grammar)) { - var ruleGrammar = GrammarUtil.getGrammar(rule); - if (!ruleGrammar.equals(grammar) && this.isSemanticPredicate(rule)) { - result.addAll(ruleGrammar) - } - } - return result.toSet; - } - - def List predicates(Grammar grammar){ - return grammar.annotateGrammar.predicates - } - - /** - * Checks whether this abstract element leads directly to a keyword rule. - * - * @param element - * Element call - * @return {@code true} if the rule leads to a keyword rule - */ - def boolean isStartingWithPredicatedRule(AbstractElement element) { - if (element instanceof CrossReference) { - return findPredicate(element.getTerminal()) !== null; - } - return findPredicate(element) !== null; - } - - /** - * Finds the predicated rule the given element leads to. - * - * @param element - * Current element - * @return Keyword rule or {@code null} if the given element does not lead to a keyword rule - */ - def SemanticPredicate findPredicate(AbstractElement element) { - if (element instanceof RuleCall) { - val predicate = getSemanticPredicateAnnotation(element.getRule()) - if (predicate !== null) { - return predicate; - } - return findPredicate(element.getRule().getAlternatives()); - } - if (GrammarUtil.isOptionalCardinality(element)) { - return null; - } - if (element instanceof CrossReference) { - return findPredicate(element.getTerminal()); - } - if (element instanceof Group) { - val firstNonActionElement = getFirstNonActionElement(element); - if (firstNonActionElement !== null) { - return findPredicate(firstNonActionElement); - } - } - if (element instanceof Assignment) { - return findPredicate((element).getTerminal()); - } - return null; - } - - def boolean isRuleWithPredicate(AbstractRule rule){ - return getSemanticPredicateAnnotation(rule) !== null - } - - def AbstractElement getFirstNonActionElement(Group group){ - for (AbstractElement groupElement : group.getElements()) { - if (!(groupElement instanceof Action)) { - return groupElement; - } - } - return null; - } - - /** - * Checks whether there is a viable alternative (i.e. not potentially gated by another gated semantic predicate) for the current element. - * If there is no alternative we should not insert gated semantic predicates, but validating semantic predicates, so we get a better error recovery and - * a correct message. We should not also propagate validating predicates in the callers. If we do so this will damage error recovery in the callers. - * - * @param element - * Grammar element - * @return {@code true} if there is an alternative available and the gated semantic predicate can be inserted - */ - def boolean isAlternativeAvailable(AbstractElement element) { - if (GrammarUtil.isOptionalCardinality(element)) { - return true; - } - val container = element.eContainer(); - if (container instanceof Assignment) { - return isAlternativeAvailable(container); - } else if (container instanceof CrossReference) { - return isAlternativeAvailable(container); - } else if (container instanceof Group) { - if (isFirstNonActionElement(container, element)) { - return isAlternativeAvailable(container); - } - } else if (container instanceof Alternatives) { - for (AbstractElement alternative : container.getElements()) { - if (alternative != element /*&& !isGated(alternative)*/) { - return true; - } - } - } - return false; - } - - /** - * Checks if the given grammar element is the first non action element in the given group. - * - * @param group - * where the position is checked, must not be {@code null} - * @param element - * to check, must not be {@code null} - * @return {@code true}, if is first non action element is the given element, {@code false} otherwise - */ - def boolean isFirstNonActionElement(Group group, AbstractElement element) { - return element == getFirstNonActionElement(group); - } - - - def String generateGatedPredicate(SemanticPredicate predicate) - '''{predicates.«predicate.name»(parserContext)}?=>''' - - def String generateValidatingPredicate(SemanticPredicate predicate) - '''{predicates.«predicate.name»(parserContext) /* @ErrorMessage(«predicate.message») */}?''' - - - /** - * Translate annotations in comments into predicate annotations. - * - *

- * Gated vs. Disambiguating predicates On the first look a disambiguating semantic predicate - * would be a right choice. However inserting them would be much more difficult. The reason is that we need - * not only insert them in the beginning of the current rule, but also propagate before actions into alternatives - * that call this rule. We have to do the same for gated predicated, however there is one important difference. - * If we have an alternative like {@code rule: (Keyword1 | Keyword2) Keyword3 ;} then we need to insert the predicate - * only before the first alternative (Keyword1). If we insert disambiguating semantic predicate before both - * the exception the predicate may throw can destroy recovery for the entire rule i.e. will not try going to Keyword3. - * We, however, need validating predicate in the beginning of each rule Keyword2, Keyword3. This requires an - * extra analysis which might be a next step. Gated predicates we can safely insert before Keyword1 and Keyword2. - * This will have no negative impact. We still need validating predicates in Keyword rules themselves. - *

- */ - def SemanticPredicate annotateRule(AbstractRule rule) { - val text = getText(rule) - var SemanticPredicate predicate = null - if (text !== null) { - val ruleGrammarName = GrammarUtil.getSimpleName(GrammarUtil.getGrammar(rule)) - predicate = getKeywordRulePredicate(text, rule.getName(), ruleGrammarName) - if(predicate !== null){ - predicate.attachToEmfObject(rule) - } - val semanticPredicate = getSemanticPredicate(text, ruleGrammarName) - if(semanticPredicate !== null){ - if(predicate !== null) - throw new IllegalArgumentException("You may not combine keyword annotations with semantic predicate annotations on one rule: " + rule.name) - semanticPredicate.attachToEmfObject(rule) - predicate = semanticPredicate - } - val noBacktrack = getNoBacktrack(text) - noBacktrack?.attachToEmfObject(rule) - } - return predicate - } - - def SemanticPredicate getSemanticPredicateAnnotation(AbstractRule rule) { - if (rule !== null) { - var original = RuleWithParameterValues.findInEmfObject(rule)?.getOriginal() - if (original === null) { - original = rule - } - return SemanticPredicate.findInEmfObject(original) - } - } - - def NoBacktrack getNoBacktrackAnnotation(AbstractRule rule) { - if (rule !== null) { - var original = RuleWithParameterValues.findInEmfObject(rule).getOriginal() - if (original === null) { - original = rule - } - return NoBacktrack.findInEmfObject(original) - } - } - - /** - * Checks if the given rule contains {@code @KeywordRule(kw1,kw2)} annotation. - */ - def SemanticPredicate getKeywordRulePredicate(String text, String ruleName, String grammar){ - val matcher = KEYWORD_RULE_ANNOTATION_PATTERN.matcher(text); - if (matcher.find()) { - return new SemanticPredicate( - "is" + ruleName + "Enabled", - "get" + ruleName + "EnabledMessage", - grammar, - Lists.newArrayList(KEYWORDS_SPLITTER.split(matcher.group(1))) - ) - } - } - - /** - * Checks if the given rule contains {@code @SemanticPredicate(condition)} annotation. - */ - def SemanticPredicate getSemanticPredicate(String text, String grammar){ - val matcher = SEMANTIC_PREDICATE_PATTERN.matcher(text); - if (matcher.find()) { - return new SemanticPredicate( - matcher.group(1), - "get" + matcher.group(1) + "Message", - grammar, - null - ) - } - } - - /** - * Checks if the given rule contains a NoBacktrack annotation. - */ - def NoBacktrack getNoBacktrack(String text){ - val matcher = NO_BACKTRACK_ANNOTATION_PATTERN.matcher(text); - if (matcher.find()) { - return new NoBacktrack - } - } - - def String getText(AbstractRule rule) { - val node = NodeModelUtils.getNode(rule); - return if(node !== null) node.getText() else grammarExtensions.grammarFragmentToString(rule,""); - } - - /** - * Checks if the given rule contains a keyword rule annotation. - * - * @param rule - * Grammar rule - * @return {@code true} if there is an annotation, {@code false} otherwise - */ - def boolean isSemanticPredicate(AbstractRule rule) { - var text = getText(rule); - if (text !== null) { - val matcher = SEMANTIC_PREDICATE_PATTERN.matcher(text); - if (matcher.find()) { - return true; - } - } - return false; - } -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/PredicatesNaming.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/PredicatesNaming.java similarity index 53% rename from com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/PredicatesNaming.xtend rename to com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/PredicatesNaming.java index b52d998ec6..0ea44b7478 100644 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/PredicatesNaming.xtend +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/parser/common/PredicatesNaming.java @@ -9,26 +9,28 @@ * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.generator.parser.common +package com.avaloq.tools.ddk.xtext.generator.parser.common; -import com.google.inject.Inject -import org.eclipse.xtext.Grammar -import org.eclipse.xtext.GrammarUtil -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming +import com.google.inject.Inject; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; -class PredicatesNaming { +@SuppressWarnings("nls") +public class PredicatesNaming { - @Inject extension XtextGeneratorNaming naming + @Inject + private XtextGeneratorNaming naming; - def String getSemanticPredicatesFullName(Grammar grammar) { + public String getSemanticPredicatesFullName(final Grammar grammar) { return getSemanticPredicatesPackageName(grammar) + "." + getSemanticPredicatesSimpleName(grammar); } - def String getSemanticPredicatesPackageName(Grammar grammar) { - return naming.getRuntimeBasePackage(grammar) + ".grammar" + public String getSemanticPredicatesPackageName(final Grammar grammar) { + return naming.getRuntimeBasePackage(grammar) + ".grammar"; } - def String getSemanticPredicatesSimpleName(Grammar grammar) { + public String getSemanticPredicatesSimpleName(final Grammar grammar) { return "Abstract" + GrammarUtil.getSimpleName(grammar) + "SemanticPredicates"; } diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/resourceFactory/ResourceFactoryFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/resourceFactory/ResourceFactoryFragment2.java new file mode 100644 index 0000000000..3fd534c3a8 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/resourceFactory/ResourceFactoryFragment2.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) Avaloq Group AG + * Schwerzistrasse 6, 8807 Freienbach, Switzerland, http://www.avaloq.com + * All Rights Reserved. + * + * This software is the confidential and proprietary information of Avaloq Group AG. + * You shall not disclose whole or parts of it and shall use it only in accordance with the terms of the + * licence agreement you entered into with Avaloq Group AG. + */ + +package com.avaloq.tools.ddk.xtext.generator.resourceFactory; // NOPMD PackageCase + +import java.util.Arrays; +import java.util.List; + +import com.google.inject.Inject; +import org.eclipse.xtext.resource.IResourceFactory; +import org.eclipse.xtext.resource.IResourceServiceProvider; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtend2.lib.StringConcatenationClient; + +/** + * Implementation that allows the fileExtensions for the fragment to be distinct from language.fileExtensions. + */ +@SuppressWarnings({"PMD.PackageCase", "nls"}) +public class ResourceFactoryFragment2 extends AbstractXtextGeneratorFragment { + + private List fileExtensions; + + @Inject + private XtextGeneratorNaming xtextGeneratorNaming; + + protected List getFileExtensions() { + return fileExtensions; + } + + /** + * Either a single file extension or a comma-separated list of extensions for which the language + * shall be registered. + * + * @param fileExtensions + * comma-separated file extension list + */ + public void setFileExtensions(final String fileExtensions) { + this.fileExtensions = Arrays.asList(fileExtensions.trim().split("\\s*,\\s*")); + } + + // CHECKSTYLE:CONSTANTS-OFF + // This is ResourceFactoryFragment2#generate with language.fileExtensions replaced by getFileExtensions + @Override + public void generate() { + + getLanguage().getRuntimeGenSetup().getRegistrations().add(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append(TypeReference.typeRef(IResourceFactory.class)); + target.append(" resourceFactory = injector.getInstance("); + target.append(TypeReference.typeRef(IResourceFactory.class)); + target.append(".class);"); + target.newLineIfNotEmpty(); + target.append(TypeReference.typeRef(IResourceServiceProvider.class)); + target.append(" serviceProvider = injector.getInstance("); + target.append(TypeReference.typeRef(IResourceServiceProvider.class)); + target.append(".class);"); + target.newLineIfNotEmpty(); + target.newLineIfNotEmpty(); + for (final String fileExtension : getFileExtensions()) { + target.append(TypeReference.typeRef(org.eclipse.emf.ecore.resource.Resource.class)); + target.append(".Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(\""); + target.append(fileExtension); + target.append("\", resourceFactory);"); + target.newLineIfNotEmpty(); + target.append(TypeReference.typeRef(IResourceServiceProvider.class)); + target.append(".Registry.INSTANCE.getExtensionToFactoryMap().put(\""); + target.append(fileExtension); + target.append("\", serviceProvider);"); + target.newLineIfNotEmpty(); + } + } + }); + + if (getProjectConfig().getEclipsePlugin() != null && getProjectConfig().getEclipsePlugin().getPluginXml() != null) { + final StringBuilder builder = new StringBuilder(512); + builder.append("\n"); + for (final String fileExtension : getFileExtensions()) { + builder.append("\n"); + builder.append(" \n"); + builder.append(" \n"); + builder.append("\n"); + builder.append("\n"); + builder.append(" \n"); + builder.append(" \n"); + builder.append("\n"); + } + getProjectConfig().getEclipsePlugin().getPluginXml().getEntries().add(builder.toString()); + } + } + // CHECKSTYLE:CONSTANTS-ON + +} + +/* Copyright (c) Avaloq Group AG */ diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/resourceFactory/ResourceFactoryFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/resourceFactory/ResourceFactoryFragment2.xtend deleted file mode 100644 index 8b7306a7b0..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/resourceFactory/ResourceFactoryFragment2.xtend +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) Avaloq Group AG - * Schwerzistrasse 6, 8807 Freienbach, Switzerland, http://www.avaloq.com - * All Rights Reserved. - * - * This software is the confidential and proprietary information of Avaloq Group AG. - * You shall not disclose whole or parts of it and shall use it only in accordance with the terms of the - * licence agreement you entered into with Avaloq Group AG. - */ - -package com.avaloq.tools.ddk.xtext.generator.resourceFactory - -import com.google.inject.Inject -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.resource.IResourceFactory -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.resource.IResourceServiceProvider -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtend.lib.annotations.Accessors -import java.util.List - -/** - * Implementation that allows the fileExtensions for the fragment to be distinct from language.fileExtensions - */ -class ResourceFactoryFragment2 extends AbstractXtextGeneratorFragment { - - @Accessors(PROTECTED_GETTER) List fileExtensions - - @Inject - extension XtextGeneratorNaming - - /** - * Either a single file extension or a comma-separated list of extensions for which the language - * shall be registered. - */ - def void setFileExtensions(String fileExtensions) { - this.fileExtensions = fileExtensions.trim.split('\\s*,\\s*').toList - } - - // This is ResourceFactoryFragment2#generate with language.fileExtensions replaced by getFileExtensions - override generate() { - - language.runtimeGenSetup.registrations.add(''' - «IResourceFactory» resourceFactory = injector.getInstance(«IResourceFactory».class); - «IResourceServiceProvider» serviceProvider = injector.getInstance(«IResourceServiceProvider».class); - - «FOR fileExtension : getFileExtensions» - «Resource».Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("«fileExtension»", resourceFactory); - «IResourceServiceProvider».Registry.INSTANCE.getExtensionToFactoryMap().put("«fileExtension»", serviceProvider); - «ENDFOR» - ''') - - if (projectConfig.eclipsePlugin?.pluginXml !== null) { - projectConfig.eclipsePlugin.pluginXml.entries += ''' - - «FOR fileExtension : getFileExtensions» - - - - - - - - - «ENDFOR» - ''' - } - } - -} - -/* Copyright (c) Avaloq Group AG */ \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/compare/CompareFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/compare/CompareFragment2.java new file mode 100644 index 0000000000..2f41b915e6 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/compare/CompareFragment2.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.ui.compare; + +import com.google.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.compare.IViewerCreator; +import org.eclipse.emf.mwe2.runtime.Mandatory; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtext.xtext.generator.resourceFactory.ResourceFactoryFragment2; + +@SuppressWarnings("nls") +public class CompareFragment2 extends ResourceFactoryFragment2 { + + @Inject + private XtextGeneratorNaming xtextGeneratorNaming; + + private static final Logger LOGGER = LogManager.getLogger(CompareFragment2.class); + + private String viewCreator; + private String bundleName; + + /** + * Sets the view creator. + * + * @param viewCreator + * the new view creator + */ + @Mandatory + public void setViewCreator(final String viewCreator) { + this.viewCreator = viewCreator; + } + + /** + * Sets bundle where the view creator is located. + * + * @param bundleName + * the new bundle name + */ + @Mandatory + public void setBundleName(final String bundleName) { + this.bundleName = bundleName; + } + + @Override + public void generate() { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("generating Compare Framework infrastructure"); + } + + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(IViewerCreator.class), new TypeReference(viewCreator)) + .contributeTo(getLanguage().getEclipsePluginGenModule()); + if (getProjectConfig().getEclipsePlugin().getManifest() != null) { + getProjectConfig().getEclipsePlugin().getManifest().getRequiredBundles().add(bundleName); + } + if (getProjectConfig().getEclipsePlugin().getPluginXml().getEntries() != null) { + getProjectConfig().getEclipsePlugin().getPluginXml().getEntries().add(eclipsePluginXmlContribution()); + } + } + + // CHECKSTYLE:CONSTANTS-OFF + public CharSequence eclipsePluginXmlContribution() { + final TypeReference executableExtensionFactory = xtextGeneratorNaming.getEclipsePluginExecutableExtensionFactory(getGrammar()); + final String grammarName = getGrammar().getName(); + final String fileExtensions = String.join(",", getLanguage().getFileExtensions()); + final String simpleName = GrammarUtil.getSimpleName(getGrammar()); + return String.format(""" + + + + + + + + + + + + + + """, TypeReference.typeRef(CompareFragment2.class), grammarName, executableExtensionFactory, fileExtensions, simpleName); + } + // CHECKSTYLE:CONSTANTS-ON +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/compare/CompareFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/compare/CompareFragment2.xtend deleted file mode 100644 index 69797104a4..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/compare/CompareFragment2.xtend +++ /dev/null @@ -1,99 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.ui.compare - -import com.google.inject.Inject -import org.apache.logging.log4j.Logger -import org.apache.logging.log4j.LogManager; -import org.eclipse.compare.IViewerCreator -import org.eclipse.emf.mwe2.runtime.Mandatory -import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess -import org.eclipse.xtext.xtext.generator.model.TypeReference -import org.eclipse.xtext.xtext.generator.resourceFactory.ResourceFactoryFragment2 - -import static extension org.eclipse.xtext.GrammarUtil.* -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* - -class CompareFragment2 extends ResourceFactoryFragment2 { - - @Inject - extension XtextGeneratorNaming - - val static Logger LOGGER = LogManager.getLogger(CompareFragment2); - - var String viewCreator; - var String bundleName; - - /** - * Sets the view creator. - * - * @param viewCreator - * the new view creator - */ - @Mandatory - def void setViewCreator(String viewCreator) { - this.viewCreator = viewCreator; - } - - /** - * Sets bundle where the view creator is located. - * - * @param bundleName - * the new bundle name - */ - @Mandatory - def void setBundleName(String bundleName) { - this.bundleName = bundleName; - } - - override void generate() { - if (LOGGER.isInfoEnabled()) { - LOGGER.info("generating Compare Framework infrastructure"); - } - - new GuiceModuleAccess.BindingFactory() - .addTypeToType(IViewerCreator.typeRef, new TypeReference(viewCreator)) - .contributeTo(language.eclipsePluginGenModule) - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.eclipsePlugin.manifest.requiredBundles += bundleName - } - if (projectConfig.eclipsePlugin.pluginXml.entries !== null) { - projectConfig.eclipsePlugin.pluginXml.entries += eclipsePluginXmlContribution() - } - } - - def eclipsePluginXmlContribution() { - val executableExtensionFactory = grammar.eclipsePluginExecutableExtensionFactory - ''' - - - - - - - - - - - - - - ''' - } -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/contentAssist/AnnotationAwareContentAssistFragment2.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/contentAssist/AnnotationAwareContentAssistFragment2.java new file mode 100644 index 0000000000..45b7187760 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/contentAssist/AnnotationAwareContentAssistFragment2.java @@ -0,0 +1,405 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.generator.ui.contentAssist; // NOPMD PackageCase + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtend2.lib.StringConcatenationClient; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Alternatives; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.CrossReference; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory; +import org.eclipse.xtext.xtext.generator.model.GeneratedJavaFileAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; +import org.eclipse.xtext.xtext.generator.ui.contentAssist.ContentAssistFragment2; + +import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations; +import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations.SemanticPredicate; +import com.google.inject.Inject; + + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "PMD.PackageCase", "nls"}) +public class AnnotationAwareContentAssistFragment2 extends ContentAssistFragment2 { + + /** + * Whether the Proposal Provider extensions should be generated - default is true. + */ + private boolean generateProposalProvider = true; + + @Inject + private FileAccessFactory fileAccessFactory; + + @Inject + private GrammarRuleAnnotations annotations; + + public boolean isGenerateProposalProvider() { + return generateProposalProvider; + } + + public void setGenerateProposalProvider(final boolean generateProposalProvider) { + this.generateProposalProvider = generateProposalProvider; + } + + @Override + public void generate() { + if (generateProposalProvider) { + super.generate(); + } + } + + // CHECKSTYLE:CONSTANTS-OFF + // generation of the 'Abstract...ProposalProvider' + + @Override + protected void generateGenJavaProposalProvider() { + Grammar grammar = getGrammar(); + // excluded features are those that stem from inherited grammars, + // they are handled by the super grammars' proposal provider + final Set excludedFqnFeatureNames = getFQFeatureNamesToExclude(grammar); + final Set processedNames = new HashSet<>(); + annotations.annotateGrammar(grammar); + // determine all assignments within the grammar that are not excluded and not handled yet + final List assignments = new ArrayList<>(); + for (Assignment assignment : GrammarUtil.containedAssignments(grammar)) { + String fqFeatureName = getFQFeatureName(assignment); + if (!processedNames.contains(fqFeatureName) && !excludedFqnFeatureNames.contains(fqFeatureName)) { + processedNames.add(fqFeatureName); + assignments.add(assignment); + } + } + + // determine keyword rules + final List keywordRules = new ArrayList<>(); + for (AbstractRule rule : grammar.getRules()) { + String fqFeatureName = getFQFeatureName(rule); + SemanticPredicate predAnnotation = annotations.getSemanticPredicateAnnotation(rule); + if (!processedNames.contains(fqFeatureName) && !excludedFqnFeatureNames.contains(fqFeatureName) + && predAnnotation != null && predAnnotation.getKeywords() != null + ) { + processedNames.add(fqFeatureName); + keywordRules.add(rule); + } + } + + // determine the (remaining) rules that are not excluded and not handled yet + final List remainingRules = new ArrayList<>(); + for (AbstractRule rule : grammar.getRules()) { + String fqnFeatureName = getFQFeatureName(rule); + if (!processedNames.contains(fqnFeatureName) && !excludedFqnFeatureNames.contains(fqnFeatureName)) { + processedNames.add(fqnFeatureName); + remainingRules.add(rule); + } + } + + // take the non-abstract class signature for the src-gen class in case of !generateStub + // as proposalProviders of sub languages refer to 'grammar.proposalProviderClass', + // see 'getGenProposalProviderSuperClass(...)' + final TypeReference genClass = + isGenerateStub() ? getGenProposalProviderClass(grammar) : getProposalProviderClass(grammar); + + GeneratedJavaFileAccess javaFile = fileAccessFactory.createGeneratedJavaFile(genClass); + final TypeReference superClass = getGenProposalProviderSuperClass(grammar); + + javaFile.setTypeComment(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("/**"); + target.newLine(); + target.append(" * Represents a generated, default implementation of superclass {@link "); + target.append(superClass); + target.append("}."); + target.newLine(); + target.append(" * Methods are dynamically dispatched on the first parameter, i.e., you can override them"); + target.newLine(); + target.append(" * with a more concrete subtype."); + target.newLine(); + target.append(" */"); + target.newLine(); + } + }); + + javaFile.setContent(new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("public "); + if (isGenerateStub()) { + target.append("abstract "); + } + target.append("class "); + target.append(genClass.getSimpleName()); + target.append(" extends "); + target.append(superClass); + target.append(" {"); + target.newLineIfNotEmpty(); + target.newLine(); + + if (!assignments.isEmpty()) { + for (Assignment assignment : assignments) { + target.append(handleAssignment(assignment)); + } + target.newLine(); + } + if (!keywordRules.isEmpty()) { + for (AbstractRule rule : keywordRules) { + target.append(" public void complete"); + target.append(getFQFeatureName(rule)); + target.append("("); + target.append(EObject.class); + target.append(" model, "); + target.append(RuleCall.class); + target.append(" ruleCall, "); + target.append(getContentAssistContextClass()); + target.append(" context, "); + target.append(getICompletionProposalAcceptorClass()); + target.append(" acceptor) {"); + target.newLineIfNotEmpty(); + target.append(handleKeywordRule(rule)); + target.append(" }"); + target.newLineIfNotEmpty(); + } + target.newLine(); + } + for (AbstractRule rule : remainingRules) { + target.append(" public void complete"); + target.append(getFQFeatureName(rule)); + target.append("("); + target.append(EObject.class); + target.append(" model, "); + target.append(RuleCall.class); + target.append(" ruleCall, "); + target.append(getContentAssistContextClass()); + target.append(" context, "); + target.append(getICompletionProposalAcceptorClass()); + target.append(" acceptor) {"); + target.newLineIfNotEmpty(); + target.append(" // subclasses may override"); + target.newLineIfNotEmpty(); + target.append(" }"); + target.newLineIfNotEmpty(); + } + target.append("}"); + target.newLineIfNotEmpty(); + } + }); + javaFile.writeTo(getProjectConfig().getEclipsePlugin().getSrcGen()); + } + + private StringConcatenationClient handleKeywordRule(final AbstractRule rule) { + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + SemanticPredicate predAnnotation = annotations.getSemanticPredicateAnnotation(rule); + if (predAnnotation != null && predAnnotation.getKeywords() != null) { + for (String keyword : predAnnotation.getKeywords()) { + target.append(" acceptor.accept(createCompletionProposal(\""); + target.append(keyword); + target.append("\", context));"); + target.newLineIfNotEmpty(); + } + } + } + }; + } + + private StringConcatenationClient handleAssignment(final Assignment assignment) { + // determine all assignments within 'assignment's containing parser rule + // assigning the same feature, obtain their expected terminals, ... + final List terminals = new ArrayList<>(); + for (Assignment a : GrammarUtil.containedAssignments(GrammarUtil.containingParserRule(assignment))) { + if (a.getFeature().equals(assignment.getFeature())) { + terminals.add(a.getTerminal()); + } + } + + // ... and determine the types of those terminals + final Set terminalTypes = new HashSet<>(); + for (AbstractElement terminal : terminals) { + terminalTypes.add(terminal.eClass()); + } + + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append(" public void complete"); + target.append(getFQFeatureName(assignment)); + target.append("("); + target.append(EObject.class); + target.append(" model, "); + target.append(Assignment.class); + target.append(" assignment, "); + target.append(getContentAssistContextClass()); + target.append(" context, "); + target.append(getICompletionProposalAcceptorClass()); + target.append(" acceptor) {"); + target.newLineIfNotEmpty(); + if (terminalTypes.size() > 1) { + target.append(handleAssignmentOptions(terminals)); + } else { + target.append(" "); + target.append(assignmentTerminal(assignment.getTerminal(), new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("assignment.getTerminal()"); + } + })); + } + target.append(" }"); + target.newLineIfNotEmpty(); + } + }; + } + + private StringConcatenationClient handleAssignmentOptions(final Iterable terminals) { + final Set processedTerminals = new HashSet<>(); + + // for each type of terminal occurring in 'terminals' ... + final List candidates = new ArrayList<>(); + for (AbstractElement terminal : terminals) { + if (!processedTerminals.contains(terminal.eClass())) { + processedTerminals.add(terminal.eClass()); + candidates.add(terminal); + } + } + + // ... generate an 'instanceof' clause + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + for (AbstractElement terminal : candidates) { + target.append(" if (assignment.getTerminal() instanceof "); + target.append(terminal.eClass().getInstanceClass()); + target.append(") {"); + target.newLineIfNotEmpty(); + target.append(" "); + target.append(assignmentTerminal(terminal, new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("assignment.getTerminal()"); + } + })); + target.append(" }"); + target.newLineIfNotEmpty(); + } + } + }; + } + + // dispatch methods for assignmentTerminal + + private StringConcatenationClient assignmentTerminal(final AbstractElement element, final StringConcatenationClient accessor) { + if (element instanceof Alternatives) { + return _assignmentTerminal((Alternatives) element, accessor); + } else if (element instanceof CrossReference) { + return _assignmentTerminal((CrossReference) element, accessor); + } else if (element instanceof RuleCall) { + return _assignmentTerminal((RuleCall) element, accessor); + } else { + return _assignmentTerminal(element, accessor); + } + } + + private StringConcatenationClient _assignmentTerminal(final AbstractElement element, final StringConcatenationClient accessor) { + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("// subclasses may override"); + target.newLineIfNotEmpty(); + } + }; + } + + private StringConcatenationClient _assignmentTerminal(final CrossReference element, final StringConcatenationClient accessor) { + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("lookupCrossReference((("); + target.append(CrossReference.class); + target.append(")"); + target.append(accessor); + target.append("), context, acceptor);"); + target.newLineIfNotEmpty(); + } + }; + } + + private StringConcatenationClient _assignmentTerminal(final RuleCall element, final StringConcatenationClient accessor) { + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("completeRuleCall((("); + target.append(RuleCall.class); + target.append(")"); + target.append(accessor); + target.append("), context, acceptor);"); + target.newLineIfNotEmpty(); + } + }; + } + + private StringConcatenationClient _assignmentTerminal(final Alternatives alternatives, final StringConcatenationClient accessor) { + return new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + List elements = alternatives.getElements(); + for (int i = 0; i < elements.size(); i++) { + final int index = i; + target.append(assignmentTerminal(elements.get(i), new StringConcatenationClient() { + @Override + protected void appendTo(final TargetStringConcatenation target) { + target.append("(("); + target.append(Alternatives.class); + target.append(")"); + target.append(accessor); + target.append(").getElements().get("); + target.append(Integer.toString(index)); + target.append(")"); + } + })); + } + } + }; + } + + // CHECKSTYLE:CONSTANTS-ON + // helper methods + + private TypeReference getContentAssistContextClass() { + return new TypeReference("org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext"); + } + + private TypeReference getICompletionProposalAcceptorClass() { + return new TypeReference("org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor"); + } + + private String getFQFeatureName(final AbstractRule r) { + return "_" + r.getName(); + } + + private String getFQFeatureName(final Assignment a) { + String ruleName = GrammarUtil.containingParserRule(a).getName(); + String firstUpper = ruleName.substring(0, 1).toUpperCase() + ruleName.substring(1); + String featureName = a.getFeature(); + String featureFirstUpper = featureName.substring(0, 1).toUpperCase() + featureName.substring(1); + return firstUpper + "_" + featureFirstUpper; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/contentAssist/AnnotationAwareContentAssistFragment2.xtend b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/contentAssist/AnnotationAwareContentAssistFragment2.xtend deleted file mode 100644 index 815c3ea14e..0000000000 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/ui/contentAssist/AnnotationAwareContentAssistFragment2.xtend +++ /dev/null @@ -1,226 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.generator.ui.contentAssist - -import com.google.inject.Inject -import org.eclipse.emf.ecore.EObject -import org.eclipse.xtend2.lib.StringConcatenationClient -import org.eclipse.xtext.AbstractElement -import org.eclipse.xtext.AbstractRule -import org.eclipse.xtext.Alternatives -import org.eclipse.xtext.Assignment -import org.eclipse.xtext.CrossReference -import org.eclipse.xtext.RuleCall -import org.eclipse.xtext.xtext.generator.model.FileAccessFactory -import org.eclipse.xtext.xtext.generator.model.TypeReference -import org.eclipse.xtext.xtext.generator.ui.contentAssist.ContentAssistFragment2 - -import static extension org.eclipse.xtext.GrammarUtil.* -import com.avaloq.tools.ddk.xtext.generator.parser.common.GrammarRuleAnnotations -import org.eclipse.xtend.lib.annotations.Accessors - -class AnnotationAwareContentAssistFragment2 extends ContentAssistFragment2 { - - /** - * Whether the Proposal Provider extensions should be generated - default is true. - */ - @Accessors boolean generateProposalProvider = true; - - @Inject - FileAccessFactory fileAccessFactory - - @Inject - extension GrammarRuleAnnotations annotations - - override generate() { - if (generateProposalProvider) - super.generate - } - - // generation of the 'Abstract...ProposalProvider' - - protected override generateGenJavaProposalProvider() { - // excluded features are those that stem from inherited grammars, - // they are handled by the super grammars' proposal provider - val excludedFqnFeatureNames = grammar.getFQFeatureNamesToExclude - val processedNames = newHashSet() - grammar.annotateGrammar - // determine all assignments within the grammar that are not excluded and not handled yet - val assignments = grammar.containedAssignments().fold(newArrayList()) [ candidates, assignment | - val fqFeatureName = assignment.FQFeatureName - if (!processedNames.contains(fqFeatureName) && !excludedFqnFeatureNames.contains(fqFeatureName)) { - processedNames += fqFeatureName; - candidates += assignment; - } - candidates - ] - - // determine keyword rules - val keywordRules = grammar.rules.fold(newArrayList()) [ candidates, rule | - val fqFeatureName = rule.FQFeatureName - if (!processedNames.contains(fqFeatureName) && !excludedFqnFeatureNames.contains(fqFeatureName) - && rule.semanticPredicateAnnotation?.keywords !== null - ) { - processedNames += fqFeatureName; - candidates += rule; - } - candidates - ] - - // determine the (remaining) rules that are not excluded and not handled yet - val remainingRules = grammar.rules.fold(newArrayList()) [candidates, rule | - val fqnFeatureName = rule.FQFeatureName - if (!processedNames.contains(fqnFeatureName) && !excludedFqnFeatureNames.contains(fqnFeatureName)) { - processedNames += fqnFeatureName - candidates += rule - } - candidates - ] - - // take the non-abstract class signature for the src-gen class in case of !generateStub - // as proposalProviders of sub languages refer to 'grammar.proposalProviderClass', - // see 'getGenProposalProviderSuperClass(...)' - val genClass = - if (isGenerateStub) grammar.genProposalProviderClass else grammar.proposalProviderClass; - - fileAccessFactory.createGeneratedJavaFile(genClass) => [ - val superClass = grammar.genProposalProviderSuperClass - - typeComment = ''' - /** - * Represents a generated, default implementation of superclass {@link «superClass»}. - * Methods are dynamically dispatched on the first parameter, i.e., you can override them - * with a more concrete subtype. - */ - ''' - - content = ''' - public «IF isGenerateStub»abstract «ENDIF»class «genClass.simpleName» extends «superClass» { - - «IF !assignments.empty» - «FOR assignment : assignments» - «assignment.handleAssignment» - «ENDFOR» - - «ENDIF» - «IF !keywordRules.empty» - «FOR rule : keywordRules» - public void complete«rule.FQFeatureName»(«EObject» model, «RuleCall» ruleCall, « - contentAssistContextClass» context, «ICompletionProposalAcceptorClass» acceptor) { - «rule.handleKeywordRule» - } - «ENDFOR» - - «ENDIF» - «FOR rule : remainingRules» - public void complete«rule.FQFeatureName»(«EObject» model, «RuleCall» ruleCall, « - contentAssistContextClass» context, «ICompletionProposalAcceptorClass» acceptor) { - // subclasses may override - } - «ENDFOR» - } - ''' - writeTo(projectConfig.eclipsePlugin.srcGen) - ] - } - - private def StringConcatenationClient handleKeywordRule(AbstractRule rule) { - ''' - «FOR keyword : rule.semanticPredicateAnnotation.keywords» - acceptor.accept(createCompletionProposal("«keyword»", context)); - «ENDFOR» - ''' - } - - private def StringConcatenationClient handleAssignment(Assignment assignment) { - // determine all assignment within 'assignment's containing parser rule - // assigning the same feature, obtain their expected terminals, ... - val terminals = assignment.containingParserRule.containedAssignments().filter[ - it.feature == assignment.feature - ].map[ - terminal - ].toList - - // ... and determine the types of those terminals - val terminalTypes = terminals.map[ eClass ].toSet; - - ''' - public void complete«assignment.FQFeatureName»(«EObject» model, «Assignment» assignment, « - contentAssistContextClass» context, «ICompletionProposalAcceptorClass» acceptor) { - «IF terminalTypes.size > 1» - «terminals.handleAssignmentOptions» - «ELSE» - «assignment.terminal.assignmentTerminal('''assignment.getTerminal()''')» - «ENDIF» - } - ''' - } - - private def StringConcatenationClient handleAssignmentOptions(Iterable terminals) { - val processedTerminals = newHashSet(); - - // for each type of terminal occurring in 'terminals' ... - val candidates = terminals.fold(newArrayList()) [ candidates, terminal | - if (!processedTerminals.contains(terminal.eClass)) { - processedTerminals += terminal.eClass - candidates += terminal - } - candidates - ] - - // ... generate an 'instanceof' clause - ''' - «FOR terminal : candidates» - if (assignment.getTerminal() instanceof «terminal.eClass.instanceClass») { - «terminal.assignmentTerminal('''assignment.getTerminal()''')» - } - «ENDFOR» - ''' - } - - private def dispatch StringConcatenationClient assignmentTerminal(AbstractElement element, StringConcatenationClient accessor) ''' - // subclasses may override - ''' - - private def dispatch StringConcatenationClient assignmentTerminal(CrossReference element, StringConcatenationClient accessor) ''' - lookupCrossReference(((«CrossReference»)«accessor»), context, acceptor); - ''' - - private def dispatch StringConcatenationClient assignmentTerminal(RuleCall element, StringConcatenationClient accessor) ''' - completeRuleCall(((«RuleCall»)«accessor»), context, acceptor); - ''' - - private def dispatch StringConcatenationClient assignmentTerminal(Alternatives alternatives, StringConcatenationClient accessor) ''' - «FOR pair : alternatives.elements.indexed» - «pair.value.assignmentTerminal('''((«Alternatives»)«accessor»).getElements().get(«pair.key»)''')» - «ENDFOR» - ''' - - // helper methods - - private def getContentAssistContextClass() { - new TypeReference("org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext") - } - - private def getICompletionProposalAcceptorClass() { - new TypeReference("org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor") - } - - def private getFQFeatureName(AbstractRule r) { - "_" + r.name; - } - - def private getFQFeatureName(Assignment a) { - a.containingParserRule().name.toFirstUpper() + "_" + a.feature.toFirstUpper(); - } - -} diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/AcfKeywordHelper.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/AcfKeywordHelper.java index bbe4a0e00a..fdf9430e6c 100644 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/AcfKeywordHelper.java +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/AcfKeywordHelper.java @@ -51,6 +51,8 @@ */ public class AcfKeywordHelper implements Adapter { + private static final int INITIAL_BUFFER_CAPACITY = 32; + private final BiMap keywordValueToToken; private final boolean ignoreCase; @@ -221,7 +223,7 @@ public int compare(final String o1, final String o2) { * @return Rule name */ private String createKeywordName(final int count, final String value) { - StringBuilder name = new StringBuilder(); + StringBuilder name = new StringBuilder(INITIAL_BUFFER_CAPACITY); name.append("KEYWORD_"); //$NON-NLS-1$ name.append(count); name.append('_'); diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/CustomClassAwareEcoreGenerator.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/CustomClassAwareEcoreGenerator.java index 103987ec51..d3f96ca3b1 100644 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/CustomClassAwareEcoreGenerator.java +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/CustomClassAwareEcoreGenerator.java @@ -51,8 +51,8 @@ public class CustomClassAwareEcoreGenerator extends EcoreGenerator { // CHECKSTYLE:OFF private boolean generateModel = true; - private boolean generateEdit = false; - private boolean generateEditor = false; + private boolean generateEdit; + private boolean generateEditor; // CHECKSTYLE:ON private ResourceSet resourceSet; diff --git a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/StandaloneSetup.java b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/StandaloneSetup.java index 6967be2173..c2530461fc 100644 --- a/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/StandaloneSetup.java +++ b/com.avaloq.tools.ddk.xtext.generator/src/com/avaloq/tools/ddk/xtext/generator/util/StandaloneSetup.java @@ -50,7 +50,7 @@ private List splitCommaSeparatedString(final String input) { return Collections. emptyList(); } String trimmed = input.trim(); - if (trimmed.length() == 0) { + if (trimmed.isEmpty()) { return Collections. emptyList(); } List result = Lists.newArrayList(); diff --git a/com.avaloq.tools.ddk.xtext.generator/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.generator/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.scope.generator/.classpath b/com.avaloq.tools.ddk.xtext.scope.generator/.classpath index f70b2088b4..f46b5bd15d 100644 --- a/com.avaloq.tools.ddk.xtext.scope.generator/.classpath +++ b/com.avaloq.tools.ddk.xtext.scope.generator/.classpath @@ -1,7 +1,6 @@ - diff --git a/com.avaloq.tools.ddk.xtext.scope.generator/build.properties b/com.avaloq.tools.ddk.xtext.scope.generator/build.properties index 7edf8a4ebf..6b7927735f 100644 --- a/com.avaloq.tools.ddk.xtext.scope.generator/build.properties +++ b/com.avaloq.tools.ddk.xtext.scope.generator/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.scope.generator/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopingFragment2.java b/com.avaloq.tools.ddk.xtext.scope.generator/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopingFragment2.java new file mode 100644 index 0000000000..286fb23d60 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.scope.generator/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopingFragment2.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.scope.generator; + +import com.avaloq.tools.ddk.xtext.linking.LinkingService; +import com.avaloq.tools.ddk.xtext.scoping.IScopeNameProvider; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.linking.ILinkingService; +import org.eclipse.xtext.scoping.IScopeProvider; +import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment; +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess; +import org.eclipse.xtext.xtext.generator.model.TypeReference; + +@SuppressWarnings("nls") +public class ScopingFragment2 extends AbstractXtextGeneratorFragment { + + private static final String RUNTIME_PLUGIN = "com.avaloq.tools.ddk.xtext"; + + @Override + public void generate() { + final String prefix = GrammarUtil.getNamespace(getGrammar()) + ".scoping." + GrammarUtil.getSimpleName(getGrammar()); + new GuiceModuleAccess.BindingFactory() + .addTypeToType(TypeReference.typeRef(IScopeProvider.class), new TypeReference(prefix + "ScopeProvider")) + .addTypeToType(TypeReference.typeRef(IScopeNameProvider.class), new TypeReference(prefix + "ScopeNameProvider")) + .addTypeToType(TypeReference.typeRef(ILinkingService.class), TypeReference.typeRef(LinkingService.class)) + .contributeTo(getLanguage().getRuntimeGenModule()); + + if (getProjectConfig().getRuntime().getManifest() != null) { + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add("org.eclipse.emf.ecore"); + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add(RUNTIME_PLUGIN); + getProjectConfig().getRuntime().getManifest().getExportedPackages().add(GrammarUtil.getNamespace(getGrammar()) + ".scoping"); + getProjectConfig().getRuntime().getManifest().getImportedPackages().add("org.apache.logging.log4j"); + } + + if (getProjectConfig().getEclipsePlugin().getManifest() != null) { + getProjectConfig().getRuntime().getManifest().getRequiredBundles().add(RUNTIME_PLUGIN); + } + } +} diff --git a/com.avaloq.tools.ddk.xtext.scope.generator/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopingFragment2.xtend b/com.avaloq.tools.ddk.xtext.scope.generator/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopingFragment2.xtend deleted file mode 100644 index 495c668d98..0000000000 --- a/com.avaloq.tools.ddk.xtext.scope.generator/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopingFragment2.xtend +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.scope.generator - -import org.eclipse.xtext.xtext.generator.AbstractXtextGeneratorFragment -import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess - -import static extension org.eclipse.xtext.xtext.generator.model.TypeReference.* -import static extension org.eclipse.xtext.GrammarUtil.* -import org.eclipse.xtext.scoping.IScopeProvider -import com.avaloq.tools.ddk.xtext.scoping.IScopeNameProvider -import org.eclipse.xtext.linking.ILinkingService -import org.eclipse.xtext.xtext.generator.model.TypeReference -import com.avaloq.tools.ddk.xtext.linking.LinkingService - -class ScopingFragment2 extends AbstractXtextGeneratorFragment { - - static val RUNTIME_PLUGIN = "com.avaloq.tools.ddk.xtext" - - override generate() { - val prefix = grammar.namespace + ".scoping." + grammar.simpleName - new GuiceModuleAccess.BindingFactory() - .addTypeToType(IScopeProvider.typeRef, new TypeReference(prefix + "ScopeProvider")) - .addTypeToType(IScopeNameProvider.typeRef, new TypeReference(prefix + "ScopeNameProvider")) - .addTypeToType(ILinkingService.typeRef, LinkingService.typeRef) - .contributeTo(language.runtimeGenModule) - - if (projectConfig.runtime.manifest !== null) { - projectConfig.runtime.manifest.requiredBundles += "org.eclipse.emf.ecore" - projectConfig.runtime.manifest.requiredBundles += RUNTIME_PLUGIN - projectConfig.runtime.manifest.exportedPackages += grammar.namespace + ".scoping" - projectConfig.runtime.manifest.importedPackages += "org.apache.logging.log4j" - } - - if (projectConfig.eclipsePlugin.manifest !== null) { - projectConfig.runtime.manifest.requiredBundles += RUNTIME_PLUGIN - } - } -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.scope.generator/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.scope.generator/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.scope.ide/.classpath b/com.avaloq.tools.ddk.xtext.scope.ide/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.xtext.scope.ide/.classpath +++ b/com.avaloq.tools.ddk.xtext.scope.ide/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.scope.ide/.project b/com.avaloq.tools.ddk.xtext.scope.ide/.project index e62b705b8d..345a56ba6d 100644 --- a/com.avaloq.tools.ddk.xtext.scope.ide/.project +++ b/com.avaloq.tools.ddk.xtext.scope.ide/.project @@ -65,35 +65,5 @@ 1 PARENT-1-PROJECT_LOC/ddk-configuration/.pmd - - .settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - - - .settings/org.eclipse.core.resources.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.resources.prefs - - - .settings/org.eclipse.core.runtime.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.runtime.prefs - - - .settings/org.eclipse.jdt.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.core.prefs - - - .settings/org.eclipse.jdt.ui.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.ui.prefs - - - .settings/org.eclipse.pde.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.pde.core.prefs - diff --git a/com.avaloq.tools.ddk.xtext.scope.ide/build.properties b/com.avaloq.tools.ddk.xtext.scope.ide/build.properties index 3f5513de7b..2d6ac57da2 100644 --- a/com.avaloq.tools.ddk.xtext.scope.ide/build.properties +++ b/com.avaloq.tools.ddk.xtext.scope.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.scope.ide/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.scope.ide/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.scope/.classpath b/com.avaloq.tools.ddk.xtext.scope/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.xtext.scope/.classpath +++ b/com.avaloq.tools.ddk.xtext.scope/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.scope/build.properties b/com.avaloq.tools.ddk.xtext.scope/build.properties index 5839f7a848..b533d62244 100644 --- a/com.avaloq.tools.ddk.xtext.scope/build.properties +++ b/com.avaloq.tools.ddk.xtext.scope/build.properties @@ -1,6 +1,5 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ .,\ plugin.xml,\ diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeGenerator.java b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeGenerator.java new file mode 100644 index 0000000000..d734582192 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeGenerator.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.scope.generator; + +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorSupport; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel; +import com.google.inject.Inject; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.core.resources.IResource; +import org.eclipse.xtext.generator.IFileSystemAccess; +import org.eclipse.xtext.generator.IFileSystemAccess2; +import org.eclipse.xtext.generator.IGenerator2; +import org.eclipse.xtext.generator.IGeneratorContext; +import org.eclipse.xtext.scoping.IScopeProvider; + +/** + * Scope generator generating the {@link IScopeProvider} implementation for a given scope file. + */ +@SuppressWarnings("nls") +public class ScopeGenerator implements IGenerator2 { + + @Inject + private Naming naming; + + @Inject + private ScopeProviderGenerator scopeProvider; + + @Inject + private ScopeNameProviderGenerator nameProvider; + + @Inject + private GenModelUtilX genModelUtil; + + @Inject + private GeneratorSupport generatorSupport; + + private CompilationContext compilationContext; + + @Override + public void doGenerate(final Resource input, final IFileSystemAccess2 fsa, final IGeneratorContext context) { + if (input == null || input.getContents().isEmpty() || !(input.getContents().get(0) instanceof ScopeModel)) { + return; + } + final ScopeModel model = (ScopeModel) input.getContents().get(0); + genModelUtil.setResource(model.eResource()); + IProject project = null; + if (input.getURI().isPlatformResource()) { + final IResource res = ResourcesPlugin.getWorkspace().getRoot().findMember(input.getURI().toPlatformString(true)); + if (res != null) { + project = res.getProject(); + } + } + + generatorSupport.executeWithProjectResourceLoader(project, () -> { + compilationContext = ScopingGeneratorUtil.getCompilationContext(model, genModelUtil); + + generateScopeNameProvider(model, fsa); + generateScopeProvider(model, fsa); + }); + } + + public void generateScopeProvider(final ScopeModel model, final IFileSystemAccess fsa) { + final String fileName = (naming.toJavaPackage(model.getName()) + ".scoping.").replace('.', '/') + naming.toSimpleName(model.getName()) + "ScopeProvider.java"; + fsa.generateFile(fileName, scopeProvider.generate(model, nameProvider, compilationContext, genModelUtil)); + } + + public void generateScopeNameProvider(final ScopeModel model, final IFileSystemAccess fsa) { + final String fileName = (naming.toJavaPackage(model.getName()) + ".scoping.").replace('.', '/') + naming.toSimpleName(model.getName()) + "ScopeNameProvider.java"; + fsa.generateFile(fileName, nameProvider.generate(model, compilationContext, genModelUtil)); + } + + @Override + public void afterGenerate(final Resource input, final IFileSystemAccess2 fsa, final IGeneratorContext context) { + } + + @Override + public void beforeGenerate(final Resource input, final IFileSystemAccess2 fsa, final IGeneratorContext context) { + } + +} diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeGenerator.xtend b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeGenerator.xtend deleted file mode 100644 index 25ebeff4bc..0000000000 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeGenerator.xtend +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.scope.generator - -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorSupport -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel -import com.google.inject.Inject -import org.eclipse.core.resources.IProject -import org.eclipse.core.resources.ResourcesPlugin -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.IFileSystemAccess -import org.eclipse.xtext.scoping.IScopeProvider -import org.eclipse.xtext.generator.IGenerator2 -import org.eclipse.xtext.generator.IFileSystemAccess2 -import org.eclipse.xtext.generator.IGeneratorContext - -/** - * Scope generator generating the {@link IScopeProvider} implementation for a given scope file. - */ -class ScopeGenerator implements IGenerator2 { - - @Inject - extension Naming - @Inject - ScopeProviderGenerator scopeProvider - @Inject - ScopeNameProviderGenerator nameProvider - @Inject - GenModelUtilX genModelUtil - @Inject - GeneratorSupport generatorSupport - - CompilationContext compilationContext - - override doGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) { - if (input === null || input.contents.empty || !(input.contents.head instanceof ScopeModel)) { - return - } - val model = input.contents.head as ScopeModel - genModelUtil.resource = model.eResource - var IProject project = null - if (input.URI.isPlatformResource) { - val res = ResourcesPlugin.workspace.root.findMember(input.URI.toPlatformString(true)) - if (res !== null) { - project = res.project - } - } - - generatorSupport.executeWithProjectResourceLoader(project, [ - compilationContext = ScopingGeneratorUtil.getCompilationContext(model, genModelUtil) - - generateScopeNameProvider(model, fsa) - generateScopeProvider(model, fsa) - ]) - } - - def generateScopeProvider(ScopeModel model, IFileSystemAccess fsa) { - val fileName = (model.name.toJavaPackage + ".scoping.").replace('.', '/') + model.name.toSimpleName + "ScopeProvider.java"; - fsa.generateFile(fileName, scopeProvider.generate(model, nameProvider, compilationContext, genModelUtil)) - } - - def generateScopeNameProvider(ScopeModel model, IFileSystemAccess fsa) { - val fileName = (model.name.toJavaPackage + ".scoping.").replace('.', '/') + model.name.toSimpleName + "ScopeNameProvider.java"; - fsa.generateFile(fileName, nameProvider.generate(model, compilationContext, genModelUtil)) - } - - override afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {} - - override beforeGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context) {} - -} diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.java b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.java new file mode 100644 index 0000000000..9f0e4fafe3 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.java @@ -0,0 +1,267 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.scope.generator; + +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall; +import com.avaloq.tools.ddk.xtext.expression.expression.IntegerLiteral; +import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall; +import com.avaloq.tools.ddk.xtext.expression.expression.StringLiteral; +import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.ExpressionExtensionsX; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.avaloq.tools.ddk.xtext.scope.scope.NamingDefinition; +import com.avaloq.tools.ddk.xtext.scope.scope.NamingExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel; +import com.google.inject.Inject; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; + + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class ScopeNameProviderGenerator { + + @Inject + private CodeGenerationX codeGenerationX; + + @Inject + private ExpressionExtensionsX expressionExtensionsX; + + @Inject + private Naming naming; + + @Inject + private GeneratorUtilX generatorUtilX; + + @Inject + private ScopeProviderX scopeProviderX; + + private GenModelUtilX genModelUtil; + + private CompilationContext compilationContext; + + // CHECKSTYLE:CONSTANTS-OFF + /** + * Generates the scope name provider class. + * + * @param it + * the scope model + * @param compilationContext + * the compilation context + * @param genModelUtil + * the gen model utility + * @return the generated source code + */ + // CHECKSTYLE:CHECK-OFF HiddenField + public CharSequence generate(final ScopeModel it, final CompilationContext compilationContext, final GenModelUtilX genModelUtil) { + // CHECKSTYLE:CHECK-ON HiddenField + this.compilationContext = compilationContext; + this.genModelUtil = genModelUtil; + final StringBuilder builder = new StringBuilder(2048); + builder.append("package ").append(naming.toJavaPackage(scopeProviderX.getScopeNameProvider(it))).append(";\n"); + builder.append('\n'); + builder.append("import java.util.Arrays;\n"); + builder.append('\n'); + builder.append("import org.eclipse.emf.ecore.EClass;\n"); + builder.append('\n'); + builder.append("import org.eclipse.xtext.naming.QualifiedName;\n"); + builder.append('\n'); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.AbstractScopeNameProvider;\n"); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.INameFunction;\n"); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.NameFunctions;\n"); + builder.append('\n'); + builder.append("import com.google.common.base.Function;\n"); + builder.append("import com.google.inject.Singleton;\n"); + builder.append('\n'); + builder.append("@SuppressWarnings(\"all\")\n"); + builder.append("@Singleton\n"); + builder.append("public class ").append(naming.toSimpleName(scopeProviderX.getScopeNameProvider(it))).append(" extends AbstractScopeNameProvider {\n"); + builder.append('\n'); + builder.append(" @Override\n"); + builder.append(" public Iterable internalGetNameFunctions(final EClass eClass) {\n"); + if (it.getNaming() != null) { + final Set packages = it.getNaming().getNamings().stream() + .map(nd -> nd.getType().getEPackage()) + .collect(Collectors.toSet()); + for (final EPackage p : packages) { + builder.append(" if (").append(genModelUtil.qualifiedPackageInterfaceName(p)).append(".eINSTANCE == eClass.getEPackage()) {\n"); + builder.append(" switch (eClass.getClassifierID()) {\n"); + builder.append('\n'); + + for (final NamingDefinition n : it.getNaming().getNamings()) { + if (Objects.equals(n.getType().getEPackage(), p)) { + builder.append(" case ").append(genModelUtil.classifierIdLiteral(n.getType())).append(":\n"); + builder.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(n))).append('\n'); + builder.append(" return ").append(nameFunctions(n.getNaming(), it)).append(";\n"); + } + } + builder.append('\n'); + builder.append(" default:\n"); + builder.append(" return !eClass.getESuperTypes().isEmpty() ? getNameFunctions(eClass.getESuperTypes().get(0)) : null;\n"); + builder.append(" }\n"); + builder.append(" }\n"); + } + } + builder.append(" return !eClass.getESuperTypes().isEmpty() ? getNameFunctions(eClass.getESuperTypes().get(0)) : null;\n"); + builder.append(" }\n"); + builder.append('\n'); + builder.append("}\n"); + return builder; + } + // CHECKSTYLE:CONSTANTS-ON + + /** + * Generates name functions for a naming definition. + * + * @param it + * the naming definition + * @param model + * the scope model + * @return the generated name functions + */ + public CharSequence nameFunctions(final com.avaloq.tools.ddk.xtext.scope.scope.Naming it, final ScopeModel model) { + return nameFunctions(it, model, null, null); + } + + /** + * Generates name functions for a naming definition with context. + * + * @param it + * the naming definition + * @param model + * the scope model + * @param contextName + * the context name + * @param contextType + * the context type + * @return the generated name functions + */ + public CharSequence nameFunctions(final com.avaloq.tools.ddk.xtext.scope.scope.Naming it, final ScopeModel model, final String contextName, final EClass contextType) { + final StringBuilder builder = new StringBuilder(512); + builder.append("Arrays.asList("); + boolean first = true; + for (final NamingExpression n : it.getNames()) { + if (!first) { + builder.append(", "); + } + first = false; + builder.append(nameFunction(n, model, contextName, contextType)); + } + builder.append(')'); + return builder; + } + + // CHECKSTYLE:CONSTANTS-OFF + protected String _nameFunction(final NamingExpression it, final ScopeModel model, final String contextName, final EClass contextType) { + if (it.isFactory()) { + if (contextName == null || contextType == null) { + return codeGenerationX.javaExpression(it.getExpression(), compilationContext.clone("UNEXPECTED_THIS")); + } else { + return codeGenerationX.javaExpression(it.getExpression(), compilationContext.clone("UNEXPECTED_THIS", null, contextName, contextType)); + } + } else if (it.isExport()) { + return "NameFunctions.exportNameFunction()"; + } else { + return nameFunction(it.getExpression(), model, contextName, contextType); + } + } + + protected String _nameFunction(final Expression it, final ScopeModel model, final String contextName, final EClass contextType) { + return "EXPRESSION_NOT_SUPPORTED(\"" + expressionExtensionsX.serialize(it) + "\")"; + } + + protected String _nameFunction(final StringLiteral it, final ScopeModel model, final String contextName, final EClass contextType) { + return "NameFunctions.fromConstant(\"" + it.getVal() + "\")"; + } + + protected String _nameFunction(final IntegerLiteral it, final ScopeModel model, final String contextName, final EClass contextType) { + return "NameFunctions.fromConstant(String.valueOf(" + it.getVal() + "))"; + } + + protected String _nameFunction(final FeatureCall it, final ScopeModel model, final String contextName, final EClass contextType) { + final CompilationContext currentContext = (contextName == null) + ? compilationContext.clone("obj", scopeProviderX.scopeType(it)) + : compilationContext.clone("obj", scopeProviderX.scopeType(it), "ctx", contextType); + final StringBuilder builder = new StringBuilder(512); + if ((it.getTarget() == null || codeGenerationX.isThisCall(it.getTarget())) && codeGenerationX.isSimpleFeatureCall(it, currentContext)) { + builder.append("NameFunctions.fromFeature(").append(genModelUtil.literalIdentifier(scopeProviderX.feature(it))).append(')'); + } else if (codeGenerationX.isSimpleNavigation(it, currentContext)) { + builder.append('\n'); + builder.append("object -> {\n"); + builder.append(" final ").append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it))).append(" obj = (").append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it))).append(") object;\n"); + builder.append(" return toQualifiedName(").append(codeGenerationX.javaExpression(it, currentContext)).append(");\n"); + builder.append(" }\n"); + } else { + builder.append("EXPRESSION_NOT_SUPPORTED(\"").append(expressionExtensionsX.serialize(it)).append("\")"); + } + return builder.toString(); + } + + protected String _nameFunction(final OperationCall it, final ScopeModel model, final String contextName, final EClass contextType) { + final CompilationContext currentContext = (contextName == null) + ? compilationContext.clone("obj", scopeProviderX.scopeType(it)) + : compilationContext.clone("obj", scopeProviderX.scopeType(it), "ctx", contextType); + final StringBuilder builder = new StringBuilder(512); + if (codeGenerationX.isCompilable(it, currentContext)) { + builder.append("object -> {\n"); + builder.append(" final ").append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it))).append(" obj = (").append(genModelUtil.instanceClassName(scopeProviderX.scopeType(it))).append(") object;\n"); + builder.append(" return toQualifiedName(").append(codeGenerationX.javaExpression(it, currentContext)).append(");\n"); + builder.append(" }\n"); + } else { + builder.append("EXPRESSION_NOT_SUPPORTED(\"").append(expressionExtensionsX.serialize(it)).append("\")"); + } + return builder.toString(); + } + // CHECKSTYLE:CONSTANTS-ON + + /** + * Dispatches to the appropriate name function generator based on the type of the given object. + * + * @param it + * the object to generate a name function for + * @param model + * the scope model + * @param contextName + * the context name + * @param contextType + * the context type + * @return the generated name function code + * @throws IllegalArgumentException + * if the parameter types are not handled + */ + public String nameFunction(final EObject it, final ScopeModel model, final String contextName, final EClass contextType) { + if (it instanceof IntegerLiteral integerLiteral) { + return _nameFunction(integerLiteral, model, contextName, contextType); + } else if (it instanceof OperationCall operationCall) { + return _nameFunction(operationCall, model, contextName, contextType); + } else if (it instanceof StringLiteral stringLiteral) { + return _nameFunction(stringLiteral, model, contextName, contextType); + } else if (it instanceof FeatureCall featureCall) { + return _nameFunction(featureCall, model, contextName, contextType); + } else if (it instanceof Expression expression) { + return _nameFunction(expression, model, contextName, contextType); + } else if (it instanceof NamingExpression namingExpression) { + return _nameFunction(namingExpression, model, contextName, contextType); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(it, model, contextName, contextType).toString()); + } + } +} diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.xtend b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.xtend deleted file mode 100644 index cd11358fcf..0000000000 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeNameProviderGenerator.xtend +++ /dev/null @@ -1,136 +0,0 @@ -package com.avaloq.tools.ddk.xtext.scope.generator - -import com.avaloq.tools.ddk.xtext.expression.expression.Expression -import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall -import com.avaloq.tools.ddk.xtext.expression.expression.IntegerLiteral -import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall -import com.avaloq.tools.ddk.xtext.expression.expression.StringLiteral -import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.ExpressionExtensionsX -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX -import com.avaloq.tools.ddk.xtext.scope.scope.Naming -import com.avaloq.tools.ddk.xtext.scope.scope.NamingExpression -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel -import com.google.inject.Inject -import org.eclipse.emf.ecore.EClass - -class ScopeNameProviderGenerator { - - @Inject extension CodeGenerationX - @Inject extension ExpressionExtensionsX - @Inject extension com.avaloq.tools.ddk.xtext.expression.generator.Naming - @Inject extension GeneratorUtilX - @Inject extension ScopeProviderX - - extension GenModelUtilX genModelUtil - CompilationContext compilationContext - - def generate(ScopeModel it, CompilationContext compilationContext, GenModelUtilX genModelUtil) { - this.compilationContext = compilationContext - this.genModelUtil = genModelUtil - ''' - package «getScopeNameProvider().toJavaPackage()»; - - import java.util.Arrays; - - import org.eclipse.emf.ecore.EClass; - - import org.eclipse.xtext.naming.QualifiedName; - - import com.avaloq.tools.ddk.xtext.scoping.AbstractScopeNameProvider; - import com.avaloq.tools.ddk.xtext.scoping.INameFunction; - import com.avaloq.tools.ddk.xtext.scoping.NameFunctions; - - import com.google.common.base.Function; - import com.google.inject.Singleton; - - @SuppressWarnings("all") - @Singleton - public class «getScopeNameProvider().toSimpleName()» extends AbstractScopeNameProvider { - - @Override - public Iterable internalGetNameFunctions(final EClass eClass) { - «IF it.naming !== null» - «FOR p : it.naming.namings.map[type.EPackage].toSet()» - if («p.qualifiedPackageInterfaceName()».eINSTANCE == eClass.getEPackage()) { - switch (eClass.getClassifierID()) { - - «FOR n : it.naming.namings.filter(n|n.type.EPackage == p)» - case «n.type.classifierIdLiteral()»: - «javaContributorComment(n.location())» - return «nameFunctions(n.naming, it)»; - «ENDFOR» - - default: - return !eClass.getESuperTypes().isEmpty() ? getNameFunctions(eClass.getESuperTypes().get(0)) : null; - } - } - «ENDFOR» - «ENDIF» - return !eClass.getESuperTypes().isEmpty() ? getNameFunctions(eClass.getESuperTypes().get(0)) : null; - } - - } - ''' - } - - def nameFunctions(Naming it, ScopeModel model) { - nameFunctions(it, model, null, null) - } - - def nameFunctions(Naming it, ScopeModel model, String contextName, EClass contextType) { - '''Arrays.asList(«FOR n : names SEPARATOR ", "»«nameFunction(n, model, contextName, contextType)»«ENDFOR»)''' - } - - def dispatch String nameFunction(NamingExpression it, ScopeModel model, String contextName, EClass contextType) { - if (factory) { - if (contextName === null || contextType === null) { - expression.javaExpression(compilationContext.clone('UNEXPECTED_THIS')) - } else { - expression.javaExpression(compilationContext.clone('UNEXPECTED_THIS', null, contextName, contextType)) - } - } else if (export) { - 'NameFunctions.exportNameFunction()' - } else { - nameFunction(expression, model, contextName, contextType) - } - } - - def dispatch String nameFunction(Expression it, ScopeModel model, String contextName, EClass contextType) { - 'EXPRESSION_NOT_SUPPORTED("' + serialize() + '")' - } - - def dispatch String nameFunction(StringLiteral it, ScopeModel model, String contextName, EClass contextType) { - 'NameFunctions.fromConstant("' + ^val + '")' - } - - def dispatch String nameFunction(IntegerLiteral it, ScopeModel model, String contextName, EClass contextType) { - 'NameFunctions.fromConstant(String.valueOf(' + ^val + '))' - } - - def dispatch String nameFunction(FeatureCall it, ScopeModel model, String contextName, EClass contextType) ''' - «val currentContext = if (contextName === null) compilationContext.clone('obj', scopeType()) else compilationContext.clone('obj', scopeType(), 'ctx', contextType)» - «IF (target === null || target.isThisCall()) && isSimpleFeatureCall(currentContext)»NameFunctions.fromFeature(«literalIdentifier(feature())»)« - ELSEIF isSimpleNavigation(currentContext)» - object -> { - final «scopeType().instanceClassName()» obj = («scopeType().instanceClassName()») object; - return toQualifiedName(«javaExpression(currentContext)»); - } - « - ELSE»EXPRESSION_NOT_SUPPORTED("«serialize()»")«ENDIF - »''' - - def dispatch String nameFunction(OperationCall it, ScopeModel model, String contextName, EClass contextType) ''' - «val currentContext = if (contextName === null) compilationContext.clone('obj', scopeType()) else compilationContext.clone('obj', scopeType(), 'ctx', contextType)» - «IF isCompilable(currentContext)» - object -> { - final «scopeType().instanceClassName()» obj = («scopeType().instanceClassName()») object; - return toQualifiedName(«javaExpression(currentContext)»); - } - « - ELSE»EXPRESSION_NOT_SUPPORTED("«serialize()»")«ENDIF - »''' - -} diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.java b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.java new file mode 100644 index 0000000000..65e5cb8c85 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.java @@ -0,0 +1,783 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.scope.generator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall; +import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX; +import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext; +import com.avaloq.tools.ddk.xtext.expression.generator.EClassComparator; +import com.avaloq.tools.ddk.xtext.expression.generator.ExpressionExtensionsX; +import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.avaloq.tools.ddk.xtext.scope.scope.FactoryExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.GlobalScopeExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.LambdaDataExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.MatchDataExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.NamedScopeExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeDefinition; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeDelegation; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeRule; +import com.avaloq.tools.ddk.xtext.scope.scope.SimpleScopeExpression; +import com.google.common.collect.Lists; +import com.google.inject.Inject; + +import org.eclipse.emf.ecore.EClass; + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class ScopeProviderGenerator { + + @Inject + private CodeGenerationX codeGenerationX; + @Inject + private ExpressionExtensionsX expressionExtensionsX; + @Inject + private GeneratorUtilX generatorUtilX; + @Inject + private Naming naming; + @Inject + private ScopeProviderX scopeProviderX; + + private ScopeNameProviderGenerator nameProviderGenerator; + private CompilationContext compilationContext; + private GenModelUtilX genModelUtil; + + // CHECKSTYLE:CONSTANTS-OFF + + /** + * Generates the scope provider class source code. + * + * @param it + * the scope model + * @param nameProviderGenerator + * the name provider generator + * @param compilationContext + * the compilation context + * @param genModelUtil + * the gen model utility + * @return the generated source code + */ + // CHECKSTYLE:CHECK-OFF HiddenField + public CharSequence generate(final ScopeModel it, final ScopeNameProviderGenerator nameProviderGenerator, final CompilationContext compilationContext, final GenModelUtilX genModelUtil) { + // CHECKSTYLE:CHECK-ON HiddenField + this.nameProviderGenerator = nameProviderGenerator; + this.compilationContext = compilationContext; + this.genModelUtil = genModelUtil; + final StringBuilder builder = new StringBuilder(4096); + builder.append("package ").append(naming.toJavaPackage(scopeProviderX.getScopeProvider(it))).append(";\n"); + builder.append('\n'); + builder.append("import java.util.Arrays;\n"); + builder.append('\n'); + builder.append("import org.apache.logging.log4j.Logger;\n"); + builder.append("import org.apache.logging.log4j.LogManager;\n"); + builder.append("import org.eclipse.emf.ecore.EClass;\n"); + builder.append("import org.eclipse.emf.ecore.EObject;\n"); + builder.append("import org.eclipse.emf.ecore.EPackage;\n"); + builder.append("import org.eclipse.emf.ecore.EReference;\n"); + builder.append("import org.eclipse.emf.ecore.resource.Resource;\n"); + builder.append('\n'); + builder.append("import org.eclipse.xtext.naming.QualifiedName;\n"); + builder.append("import org.eclipse.xtext.resource.IEObjectDescription;\n"); + builder.append("import org.eclipse.xtext.scoping.IScope;\n"); + builder.append('\n'); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.AbstractNameFunction;\n"); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.AbstractPolymorphicScopeProvider;\n"); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.IContextSupplier;\n"); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.INameFunction;\n"); + builder.append("import com.avaloq.tools.ddk.xtext.scoping.NameFunctions;\n"); + builder.append("import com.avaloq.tools.ddk.xtext.util.EObjectUtil;\n"); + builder.append('\n'); + builder.append("import com.google.common.base.Predicate;\n"); + if (!scopeProviderX.allInjections(it).isEmpty()) { + builder.append("import com.google.inject.Inject;\n"); + } + builder.append('\n'); + builder.append("@SuppressWarnings(\"all\")\n"); + builder.append("public class ").append(naming.toSimpleName(scopeProviderX.getScopeProvider(it))).append(" extends AbstractPolymorphicScopeProvider {\n"); + builder.append('\n'); + builder.append(" /** Class-wide logger. */\n"); + builder.append(" private static final Logger LOGGER = LogManager.getLogger(").append(naming.toSimpleName(scopeProviderX.getScopeProvider(it))).append(".class);\n"); + if (!scopeProviderX.allInjections(it).isEmpty()) { + for (final com.avaloq.tools.ddk.xtext.scope.scope.Injection i : scopeProviderX.allInjections(it)) { + builder.append(" @Inject\n"); + builder.append(" private ").append(i.getType()).append(' ').append(i.getName()).append(";\n"); + } + } + builder.append('\n'); + builder.append(scopeMethods(it, naming.toSimpleName(it.getName()))); + builder.append('\n'); + builder.append("}\n"); + return builder; + } + + /** + * Generates scope methods for all scope definitions. + * + * @param it + * the scope model + * @param baseName + * the base name + * @return the generated scope methods + * @throws IllegalStateException + * if more than one global rule is found + */ + public CharSequence scopeMethods(final ScopeModel it, final String baseName) { + final StringBuilder builder = new StringBuilder(4096); + + // doGetScope with EReference + builder.append(" @Override\n"); + builder.append(" protected IScope doGetScope(final EObject context, final EReference reference, final String scopeName, final Resource originalResource) {\n"); + final List refScopes = scopeProviderX.allScopes(it).stream().filter(s -> s.getReference() != null).toList(); + if (!refScopes.isEmpty()) { + builder.append(" if (scopeName == null) {\n"); + builder.append(" return null;\n"); + builder.append(" }\n"); + builder.append('\n'); + builder.append(" switch (scopeName) {\n"); + final Set refScopeNames = refScopes.stream().map(s -> scopeProviderX.getScopeName(s)).collect(Collectors.toCollection(java.util.LinkedHashSet::new)); + for (final String refScopeName : refScopeNames) { + builder.append(" case \"").append(refScopeName).append("\":\n"); + for (final ScopeDefinition scope : refScopes.stream().filter(s -> scopeProviderX.getScopeName(s).equals(refScopeName)).toList()) { + builder.append(" if (reference == ").append(genModelUtil.literalIdentifier(scope.getReference())).append(") return ").append(scopeProviderX.scopeMethodName(scope)).append("(context, reference, originalResource);\n"); + } + builder.append(" break;\n"); + } + builder.append(" default: break;\n"); + builder.append(" }\n"); + } + builder.append(" return null;\n"); + builder.append(" }\n"); + builder.append('\n'); + + // doGetScope with EClass + builder.append(" @Override\n"); + builder.append(" protected IScope doGetScope(final EObject context, final EClass type, final String scopeName, final Resource originalResource) {\n"); + final List typeScopes = scopeProviderX.allScopes(it).stream().filter(s -> s.getReference() == null).toList(); + if (!typeScopes.isEmpty()) { + builder.append(" if (scopeName == null) {\n"); + builder.append(" return null;\n"); + builder.append(" }\n"); + builder.append('\n'); + builder.append(" switch (scopeName) {\n"); + final Set typeScopeNames = typeScopes.stream().map(s -> scopeProviderX.getScopeName(s)).collect(Collectors.toCollection(java.util.LinkedHashSet::new)); + for (final String typeScopeName : typeScopeNames) { + builder.append(" case \"").append(typeScopeName).append("\":\n"); + for (final ScopeDefinition scope : typeScopes.stream().filter(s -> scopeProviderX.getScopeName(s).equals(typeScopeName)).toList()) { + builder.append(" if (type == ").append(genModelUtil.literalIdentifier(scope.getTargetType())).append(") return ").append(scopeProviderX.scopeMethodName(scope)).append("(context, type, originalResource);\n"); + } + builder.append(" break;\n"); + } + builder.append(" default: break;\n"); + builder.append(" }\n"); + } + builder.append(" return null;\n"); + builder.append(" }\n"); + builder.append('\n'); + + // doGlobalCache with EReference + builder.append(" @Override\n"); + builder.append(" protected boolean doGlobalCache(final EObject context, final EReference reference, final String scopeName, final Resource originalResource) {\n"); + final List refGlobalScopes = refScopes.stream().filter(s -> scopeProviderX.allScopeRules(s).stream().anyMatch(r -> r.getContext().isGlobal())).toList(); + if (!refGlobalScopes.isEmpty()) { + builder.append(" if (scopeName != null && context.eContainer() == null) {\n"); + builder.append(" switch (scopeName) {\n"); + final Set refGlobalNames = refGlobalScopes.stream().map(s -> scopeProviderX.getScopeName(s)).collect(Collectors.toCollection(java.util.LinkedHashSet::new)); + for (final String refGlobalName : refGlobalNames) { + builder.append(" case \"").append(refGlobalName).append("\":\n"); + for (final ScopeDefinition scope : refScopes.stream().filter(s -> scopeProviderX.getScopeName(s).equals(refGlobalName)).toList()) { + final List globalRules = scopeProviderX.allScopeRules(scope).stream().filter(r -> r.getContext().isGlobal()).toList(); + if (!globalRules.isEmpty()) { + builder.append(" if (reference == ").append(genModelUtil.literalIdentifier(scope.getReference())).append(") return true;\n"); + } + } + builder.append(" break;\n"); + } + builder.append(" default: break;\n"); + builder.append(" }\n"); + builder.append(" }\n"); + } + builder.append(" return false;\n"); + builder.append(" }\n"); + builder.append('\n'); + + // doGlobalCache with EClass + builder.append(" @Override\n"); + builder.append(" protected boolean doGlobalCache(final EObject context, final EClass type, final String scopeName, final Resource originalResource) {\n"); + final List typeGlobalScopes = typeScopes.stream().filter(s -> scopeProviderX.allScopeRules(s).stream().anyMatch(r -> r.getContext().isGlobal())).toList(); + if (!typeGlobalScopes.isEmpty()) { + builder.append(" if (context.eContainer() == null) {\n"); + builder.append(" switch (scopeName) {\n"); + final Set typeGlobalNames = typeGlobalScopes.stream().map(s -> scopeProviderX.getScopeName(s)).collect(Collectors.toCollection(java.util.LinkedHashSet::new)); + for (final String typeGlobalName : typeGlobalNames) { + builder.append(" case \"").append(typeGlobalName).append("\":\n"); + for (final ScopeDefinition scope : typeScopes.stream().filter(s -> scopeProviderX.getScopeName(s).equals(typeGlobalName)).toList()) { + final List globalRules = scopeProviderX.allScopeRules(scope).stream().filter(r -> r.getContext().isGlobal()).toList(); + if (!globalRules.isEmpty()) { + builder.append(" if (type == ").append(genModelUtil.literalIdentifier(scope.getTargetType())).append(") return true;\n"); + } + } + builder.append(" break;\n"); + } + builder.append(" default: break;\n"); + builder.append(" }\n"); + builder.append(" }\n"); + } + builder.append(" return false;\n"); + builder.append(" }\n"); + builder.append('\n'); + + // Per-scope methods + for (final ScopeDefinition scope : scopeProviderX.allScopes(it)) { + builder.append(" protected IScope ").append(scopeProviderX.scopeMethodName(scope)).append("(final EObject context, final "); + if (scope.getReference() != null) { + builder.append("EReference ref"); + } else { + builder.append("EClass type"); + } + builder.append(", final Resource originalResource) {\n"); + final List localRules = scopeProviderX.allScopeRules(scope).stream().filter(r -> !r.getContext().isGlobal()).toList(); + final List globalRules = scopeProviderX.allScopeRules(scope).stream().filter(r -> r.getContext().isGlobal()).toList(); + if (globalRules.size() > 1) { + throw new IllegalStateException("only one global rule allowed"); + } + for (final ScopeRule r : scopeProviderX.sortedRules(scopeProviderX.filterUniqueRules(new ArrayList<>(localRules)))) { + builder.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(r))).append('\n'); + if (EClassComparator.isEObjectType(r.getContext().getContextType())) { + builder.append(" if (true) {\n"); + } else { + builder.append(" if (context instanceof ").append(genModelUtil.instanceClassName(r.getContext().getContextType())).append(") {\n"); + } + builder.append(" final ").append(genModelUtil.instanceClassName(r.getContext().getContextType())).append(" ctx = (").append(genModelUtil.instanceClassName(r.getContext().getContextType())).append(") context;\n"); + final List rulesForTypeAndContext = localRules.stream().filter(r2 -> scopeProviderX.hasSameContext(r2, r)).toList(); + builder.append(scopeRuleBlock(rulesForTypeAndContext, it, scopeProviderX.contextRef(r) != null ? "ref" : "type", r.getContext().getContextType(), r.getContext().isGlobal())); + builder.append(" }\n"); + } + if (!localRules.isEmpty() || !globalRules.isEmpty()) { + builder.append('\n'); + builder.append(" final EObject eContainer = context.eContainer();\n"); + builder.append(" if (eContainer != null) {\n"); + builder.append(" return internalGetScope("); + if (!localRules.isEmpty()) { + builder.append("eContainer"); + } else { + builder.append("getRootObject(eContainer)"); + } + builder.append(", "); + if (scope.getReference() != null) { + builder.append("ref"); + } else { + builder.append("type"); + } + builder.append(", \"").append(scopeProviderX.getScopeName(scope)).append("\", originalResource);\n"); + builder.append(" }\n"); + builder.append('\n'); + } + if (!globalRules.isEmpty()) { + final ScopeRule r = globalRules.get(0); + final List rulesForTypeAndContext = List.of(r); + builder.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(r))).append('\n'); + builder.append(" if (context.eResource() != null) {\n"); + builder.append(" final Resource ctx = context.eResource();\n"); + builder.append(scopeRuleBlock(rulesForTypeAndContext, it, scopeProviderX.contextRef(r) != null ? "ref" : "type", r.getContext().getContextType(), r.getContext().isGlobal())); + builder.append(" }\n"); + builder.append('\n'); + } + builder.append(" return null;\n"); + builder.append(" }\n"); + builder.append('\n'); + } + + return builder; + } + + /** + * Generates a scope rule block for the given rules. + * + * @param it + * the scope rules + * @param model + * the scope model + * @param typeOrRef + * type or reference identifier + * @param contextType + * the context EClass type + * @param isGlobal + * whether the scope is global + * @return the generated scope rule block + */ + public CharSequence scopeRuleBlock(final List it, final ScopeModel model, final String typeOrRef, final EClass contextType, final Boolean isGlobal) { + final StringBuilder builder = new StringBuilder(512); + builder.append(" IScope scope = IScope.NULLSCOPE;\n"); + builder.append(" try {\n"); + if (it.stream().anyMatch(r -> r.getContext().getGuard() != null)) { + boolean first = true; + final List sorted = it.stream() + .sorted(Comparator.comparingInt(r -> r.getContext().getGuard() == null ? it.size() : it.indexOf(r))) + .toList(); + for (final ScopeRule r : sorted) { + if (!first) { + builder.append(" else "); + } else { + builder.append(" "); + first = false; + } + if (r.getContext().getGuard() != null) { + builder.append("if (").append(codeGenerationX.javaExpression(r.getContext().getGuard(), compilationContext.clone("ctx", scopeProviderX.scopeType(r)))).append(") "); + } + builder.append("{\n"); + if (it.size() > 1) { + builder.append(" ").append(generatorUtilX.javaContributorComment(generatorUtilX.location(r))).append('\n'); + } + final List reversed = Lists.newArrayList(r.getExprs()); + Collections.reverse(reversed); + for (final ScopeExpression e : reversed) { + builder.append(scopeExpression(e, model, typeOrRef, scopeProviderX.getScope(r), isGlobal)); + } + builder.append(" }"); + } + if (it.stream().noneMatch(r -> r.getContext().getGuard() == null)) { + builder.append(" else {\n"); + builder.append(" throw new UnsupportedOperationException(); // continue matching other definitions\n"); + builder.append(" }"); + } + builder.append('\n'); + } else if (it.size() == 1) { + final List reversed = Lists.newArrayList(it.get(0).getExprs()); + Collections.reverse(reversed); + for (final ScopeExpression e : reversed) { + builder.append(scopeExpression(e, model, typeOrRef, scopeProviderX.getScope(it.get(0)), isGlobal)); + } + } else { + final List locations = new ArrayList<>(); + for (final ScopeRule r : it) { + locations.add(generatorUtilX.location(r)); + } + error("scope context not unique for definitions: " + String.join(", ", locations)); + } + builder.append(" } catch (Exception e) {\n"); + builder.append(" LOGGER.error(\"Error calculating scope for "); + if (isGlobal) { + builder.append("Resource. Context:"); + } else { + builder.append(contextType.getName()); + } + builder.append(" \" + EObjectUtil.getLocationString(context) + \" (").append(scopeProviderX.locatorString(it.get(0))).append(")\", e);\n"); + builder.append(" }\n"); + builder.append(" return scope;\n"); + return builder; + } + + // dispatch scopeExpression + protected CharSequence _scopeExpression(final ScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope, final Boolean isGlobal) { + return error("Xtend called the wrong definition." + it.toString() + generatorUtilX.javaContributorComment(generatorUtilX.location(it))); + } + + protected CharSequence _scopeExpression(final FactoryExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope, final Boolean isGlobal) { + final StringBuilder b = new StringBuilder(512); + final CompilationContext ctx = compilationContext.clone("ctx", scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType()); + b.append("scope = ").append(javaCall(it.getExpr(), ctx)).append("(scope, ctx, ").append(typeOrRef).append(", originalResource"); + if (it.getExpr() instanceof OperationCall operationCall) { + for (final Expression param : operationCall.getParams()) { + b.append(", ").append(codeGenerationX.javaExpression(param, ctx)); + } + } + b.append(");\n"); + return b; + } + + // dispatch javaCall + protected String _javaCall(final Expression it, final CompilationContext ctx) { + return error("cannot handle scope factory " + it.toString()); + } + + protected String _javaCall(final OperationCall it, final CompilationContext ctx) { + if (codeGenerationX.isJavaExtensionCall(it, ctx)) { + return codeGenerationX.calledJavaMethod(it, ctx); + } else { + return "/* Error: cannot handle scope factory " + it.toString() + " */"; + } + } + + /** + * Dispatches to the appropriate java call generator. + * + * @param it + * the expression + * @param ctx + * the compilation context + * @return the generated java call + * @throws IllegalArgumentException + * if the parameter types are not handled + */ + public String javaCall(final Expression it, final CompilationContext ctx) { + if (it instanceof OperationCall operationCall) { + return _javaCall(operationCall, ctx); + } else if (it != null) { + return _javaCall(it, ctx); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + it); + } + } + + protected CharSequence _scopeExpression(final ScopeDelegation it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope, final Boolean isGlobal) { + final StringBuilder builder = new StringBuilder(512); + if (it.getDelegate() != null) { + final String delegateString = expressionExtensionsX.serialize(it.getDelegate()); + if ("this.eContainer()".equals(delegateString) || "this.eContainer".equals(delegateString) || "eContainer()".equals(delegateString) || "eContainer".equals(delegateString)) { + builder.append(" scope = newSameScope(\"").append(scopeProviderX.locatorString(it)).append("\", scope, ctx.eContainer()"); + } else if ("this".equals(delegateString)) { + builder.append(" scope = newSameScope(\"").append(scopeProviderX.locatorString(it)).append("\", scope, ctx"); + } else { + builder.append(" scope = newDelegateScope(\"").append(scopeProviderX.locatorString(it)).append("\", scope, "); + if (!isGlobal) { + builder.append("() -> IContextSupplier.makeIterable(").append(scopedElements(it.getDelegate(), model, scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType(), "ctx")).append(')'); + } else { + builder.append(scopedElements(it.getDelegate(), model, scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType(), "ctx")); + } + } + } else { + builder.append(" scope = newExternalDelegateScope(\"").append(scopeProviderX.locatorString(it)).append("\", scope, "); + builder.append(query(it.getExternal(), model, typeOrRef, scope)).append(".execute(originalResource)"); + } + builder.append(", "); + if (it.getScope() != null && scopeProviderX.typeOrRef(it.getScope()) != scopeProviderX.typeOrRef(scopeProviderX.getScope(it))) { + builder.append(genModelUtil.literalIdentifier(scopeProviderX.typeOrRef(it.getScope()))); + } else { + builder.append(typeOrRef); + } + builder.append(", \""); + if (it.getScope() != null && it.getScope().getName() != null) { + builder.append(it.getScope().getName()); + } else { + builder.append("scope"); + } + builder.append("\", originalResource);\n"); + return builder; + } + + protected CharSequence _scopeExpression(final NamedScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope, final Boolean isGlobal) { + final StringBuilder builder = new StringBuilder(512); + builder.append(" scope = ").append(scopeExpressionPart(it, model, typeOrRef, scope)); + builder.append(scopeExpressionNaming(it, model, typeOrRef, scope)); + builder.append(scopeExpressionCasing(it, model, typeOrRef, scope)).append(");\n"); + return builder; + } + + protected CharSequence _scopeExpression(final SimpleScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope, final Boolean isGlobal) { + final StringBuilder builder = new StringBuilder(512); + if (expressionExtensionsX.isEmptyList(it.getExpr())) { + builder.append(" // Empty scope from ").append(generatorUtilX.location(it)).append('\n'); + } else { + builder.append(" scope = ").append(scopeExpressionPart(it, model, typeOrRef, scope)); + builder.append(scopeExpressionNaming(it, model, typeOrRef, scope)); + builder.append(scopeExpressionCasing(it, model, typeOrRef, scope)).append(");\n"); + } + return builder; + } + + /** + * Dispatches to the appropriate scope expression generator. + * + * @param it + * the scope expression + * @param model + * the scope model + * @param typeOrRef + * type or reference identifier + * @param scope + * the scope definition + * @param isGlobal + * whether the scope is global + * @return the generated scope expression + * @throws IllegalArgumentException + * if the parameter types are not handled + */ + public CharSequence scopeExpression(final ScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope, final Boolean isGlobal) { + if (it instanceof FactoryExpression factoryExpression) { + return _scopeExpression(factoryExpression, model, typeOrRef, scope, isGlobal); + } else if (it instanceof ScopeDelegation scopeDelegation) { + return _scopeExpression(scopeDelegation, model, typeOrRef, scope, isGlobal); + } else if (it instanceof SimpleScopeExpression simpleScopeExpression) { + return _scopeExpression(simpleScopeExpression, model, typeOrRef, scope, isGlobal); + } else if (it instanceof GlobalScopeExpression) { + // GlobalScopeExpression extends NamedScopeExpression, must be checked first + return _scopeExpression((NamedScopeExpression) it, model, typeOrRef, scope, isGlobal); + } else if (it instanceof NamedScopeExpression namedScopeExpression) { + return _scopeExpression(namedScopeExpression, model, typeOrRef, scope, isGlobal); + } else if (it != null) { + return _scopeExpression(it, model, typeOrRef, scope, isGlobal); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + it); + } + } + + // dispatch scopeExpressionPart + protected String _scopeExpressionPart(final NamedScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + return error("Xtend called the wrong definition for scopeExpressionPart with this=" + it.toString() + generatorUtilX.javaContributorComment(generatorUtilX.location(it))); + } + + protected String _scopeExpressionPart(final SimpleScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + return "newSimpleScope(\"" + scopeProviderX.locatorString(it) + "\", scope, " + scopedElements(it.getExpr(), model, scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType(), "ctx") + ", "; + } + + protected CharSequence _scopeExpressionPart(final GlobalScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + final StringBuilder builder = new StringBuilder(512); + final List matchData = new ArrayList<>(); + for (final Object d : it.getData()) { + if (d instanceof LambdaDataExpression lambdaDataExpression) { + matchData.add(lambdaDataExpression); + } + } + builder.append('\n'); + if (matchData.isEmpty() && it.getPrefix() == null) { + builder.append("newContainerScope("); + } else if (matchData.isEmpty() && it.getPrefix() != null) { + builder.append("newPrefixedContainerScope("); + } else { + builder.append("newDataMatchScope("); + } + builder.append('"').append(scopeProviderX.locatorString(it)).append("\", scope, ctx, "); + builder.append(query(it, model, typeOrRef, scope)).append(", originalResource"); + if (!matchData.isEmpty()) { + builder.append(", //\n"); + builder.append(" Arrays.asList(\n"); + boolean firstData = true; + for (final LambdaDataExpression d : matchData) { + if (!firstData) { + builder.append(",\n"); + } + firstData = false; + final CompilationContext cc = compilationContext.cloneWithVariable("ctx", scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType(), d.getDesc(), "org::eclipse::xtext::resource::IEObjectDescription"); + if (codeGenerationX.isCompilable(d.getValue(), cc.clone("ctx"))) { + builder.append(" ").append(d.getDesc()).append(" -> ").append(codeGenerationX.javaExpression(d.getValue(), cc.clone("ctx"))); + } else { + builder.append(" ").append(d.getDesc()).append(" -> EXPRESSION_NOT_SUPPORTED(\"").append(expressionExtensionsX.serialize(it)).append("\")"); + } + } + builder.append(" )"); + } else if (it.getPrefix() != null) { + builder.append(", ").append(doExpression(it.getPrefix(), model, "ctx", scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType())); + builder.append(", ").append(it.isRecursivePrefix()); + } + return builder; + } + + /** + * Dispatches to the appropriate scope expression part generator. + * + * @param it + * the named scope expression + * @param model + * the scope model + * @param typeOrRef + * type or reference identifier + * @param scope + * the scope definition + * @return the generated scope expression part + * @throws IllegalArgumentException + * if the parameter types are not handled + */ + public CharSequence scopeExpressionPart(final NamedScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + if (it instanceof GlobalScopeExpression globalScopeExpression) { + return _scopeExpressionPart(globalScopeExpression, model, typeOrRef, scope); + } else if (it instanceof SimpleScopeExpression simpleScopeExpression) { + return _scopeExpressionPart(simpleScopeExpression, model, typeOrRef, scope); + } else if (it != null) { + return _scopeExpressionPart(it, model, typeOrRef, scope); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + it); + } + } + + /** + * Generates a query for a global scope expression. + * + * @param it + * the global scope expression + * @param model + * the scope model + * @param typeOrRef + * type or reference identifier + * @param scope + * the scope definition + * @return the generated query + */ + public CharSequence query(final GlobalScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + final StringBuilder builder = new StringBuilder(512); + builder.append("newQuery(").append(genModelUtil.literalIdentifier(it.getType())).append(')'); + final List matchData = new ArrayList<>(); + for (final Object d : it.getData()) { + if (d instanceof MatchDataExpression matchDataExpression) { + matchData.add(matchDataExpression); + } + } + if (it.getName() != null) { + builder.append(".name(").append(doExpression(it.getName(), model, "ctx", scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType())).append(')'); + } + if (!matchData.isEmpty()) { + for (final MatchDataExpression d : matchData) { + builder.append(".data(\"").append(codeGenerationX.javaEncode(d.getKey())).append("\", ").append(doExpression(d.getValue(), model, "ctx", scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType())).append(')'); + } + } + if (!it.getDomains().isEmpty() && !"*".equals(it.getDomains().get(0))) { + builder.append(".domains("); + boolean firstDomain = true; + for (final String d : it.getDomains()) { + if (!firstDomain) { + builder.append(", "); + } + firstDomain = false; + builder.append('"').append(codeGenerationX.javaEncode(d)).append('"'); + } + builder.append(')'); + } + return builder; + } + // CHECKSTYLE:CONSTANTS-ON + + // dispatch scopeExpressionNaming + protected String _scopeExpressionNaming(final NamedScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + return error("Xtend called the wrong definition for scopeExpressionNaming with this=" + it.toString() + generatorUtilX.javaContributorComment(generatorUtilX.location(it))); + } + + protected String _scopeExpressionNaming(final SimpleScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + return name(it, model, typeOrRef, "ctx", scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType()); + } + + protected String _scopeExpressionNaming(final GlobalScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + return ", " + name(it, model, typeOrRef, "ctx", scopeProviderX.eContainer(it, ScopeRule.class).getContext().getContextType()); + } + + /** + * Dispatches to the appropriate scope expression naming generator. + * + * @param it + * the named scope expression + * @param model + * the scope model + * @param typeOrRef + * type or reference identifier + * @param scope + * the scope definition + * @return the generated naming expression + * @throws IllegalArgumentException + * if the parameter types are not handled + */ + public String scopeExpressionNaming(final NamedScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + if (it instanceof GlobalScopeExpression globalScopeExpression) { + return _scopeExpressionNaming(globalScopeExpression, model, typeOrRef, scope); + } else if (it instanceof SimpleScopeExpression simpleScopeExpression) { + return _scopeExpressionNaming(simpleScopeExpression, model, typeOrRef, scope); + } else if (it != null) { + return _scopeExpressionNaming(it, model, typeOrRef, scope); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + it); + } + } + + /** + * Returns the case sensitivity setting for the scope expression. + * + * @param it + * the named scope expression + * @param model + * the scope model + * @param typeOrRef + * type or reference identifier + * @param scope + * the scope definition + * @return the case sensitivity expression + */ + public String scopeExpressionCasing(final NamedScopeExpression it, final ScopeModel model, final String typeOrRef, final ScopeDefinition scope) { + return ", " + Boolean.toString(scopeProviderX.isCaseInsensitive(it)); + } + + /** + * Returns the scoped elements expression. + * + * @param it + * the expression + * @param model + * the scope model + * @param type + * the EClass type + * @param object + * the object name + * @return the scoped elements expression + */ + public String scopedElements(final Expression it, final ScopeModel model, final EClass type, final String object) { + return doExpression(it, model, object, type); + } + + /** + * Compiles an expression to Java. + * + * @param it + * the expression + * @param model + * the scope model + * @param object + * the object name + * @param type + * the EClass type + * @return the compiled Java expression + */ + public String doExpression(final Expression it, final ScopeModel model, final String object, final EClass type) { + return codeGenerationX.javaExpression(it, compilationContext.clone(object, type)); + } + + /** + * Returns name functions for the given expression. + * + * @param it + * the named scope expression + * @param model + * the scope model + * @param typeOrRef + * type or reference identifier + * @param contextName + * the context name + * @param contextType + * the context type + * @return the name functions expression + */ + public String name(final NamedScopeExpression it, final ScopeModel model, final String typeOrRef, final String contextName, final EClass contextType) { + if (it.getNaming() != null) { + return nameProviderGenerator.nameFunctions(it.getNaming(), model, contextName, contextType).toString(); + } else { + return "getNameFunctions(" + typeOrRef + ")"; + } + } + + /** + * Throws a runtime exception with the given message. + * + * @param message + * the error message + * @return never returns + * @throws IllegalStateException + * always thrown with the given message + */ + public String error(final String message) { + throw new IllegalStateException(message); + } +} diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.xtend b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.xtend deleted file mode 100644 index cf57184807..0000000000 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderGenerator.xtend +++ /dev/null @@ -1,386 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. it program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies it distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.scope.generator - -import com.avaloq.tools.ddk.xtext.expression.expression.Expression -import com.avaloq.tools.ddk.xtext.expression.expression.OperationCall -import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX -import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext -import com.avaloq.tools.ddk.xtext.expression.generator.EClassComparator -import com.avaloq.tools.ddk.xtext.expression.generator.ExpressionExtensionsX -import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.avaloq.tools.ddk.xtext.scope.scope.FactoryExpression -import com.avaloq.tools.ddk.xtext.scope.scope.GlobalScopeExpression -import com.avaloq.tools.ddk.xtext.scope.scope.LambdaDataExpression -import com.avaloq.tools.ddk.xtext.scope.scope.MatchDataExpression -import com.avaloq.tools.ddk.xtext.scope.scope.NamedScopeExpression -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeDefinition -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeDelegation -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeExpression -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeRule -import com.avaloq.tools.ddk.xtext.scope.scope.SimpleScopeExpression -import com.google.common.collect.Lists -import com.google.inject.Inject -import java.util.List -import org.eclipse.emf.ecore.EClass - -class ScopeProviderGenerator { - - @Inject extension CodeGenerationX - @Inject extension ExpressionExtensionsX - @Inject extension GeneratorUtilX - @Inject extension Naming - @Inject extension ScopeProviderX - - ScopeNameProviderGenerator nameProviderGenerator - CompilationContext compilationContext - extension GenModelUtilX genModelUtil - - def generate(ScopeModel it, ScopeNameProviderGenerator nameProviderGenerator, CompilationContext compilationContext, GenModelUtilX genModelUtil) { - this.nameProviderGenerator = nameProviderGenerator - this.compilationContext = compilationContext - this.genModelUtil = genModelUtil - ''' - package «getScopeProvider().toJavaPackage()»; - - import java.util.Arrays; - - import org.apache.logging.log4j.Logger; - import org.apache.logging.log4j.LogManager; - import org.eclipse.emf.ecore.EClass; - import org.eclipse.emf.ecore.EObject; - import org.eclipse.emf.ecore.EPackage; - import org.eclipse.emf.ecore.EReference; - import org.eclipse.emf.ecore.resource.Resource; - - import org.eclipse.xtext.naming.QualifiedName; - import org.eclipse.xtext.resource.IEObjectDescription; - import org.eclipse.xtext.scoping.IScope; - - import com.avaloq.tools.ddk.xtext.scoping.AbstractNameFunction; - import com.avaloq.tools.ddk.xtext.scoping.AbstractPolymorphicScopeProvider; - import com.avaloq.tools.ddk.xtext.scoping.IContextSupplier; - import com.avaloq.tools.ddk.xtext.scoping.INameFunction; - import com.avaloq.tools.ddk.xtext.scoping.NameFunctions; - import com.avaloq.tools.ddk.xtext.util.EObjectUtil; - - import com.google.common.base.Predicate; - «IF !allInjections().isEmpty» - import com.google.inject.Inject; - «ENDIF» - - @SuppressWarnings("all") - public class «getScopeProvider().toSimpleName()» extends AbstractPolymorphicScopeProvider { - - /** Class-wide logger. */ - private static final Logger LOGGER = LogManager.getLogger(«getScopeProvider().toSimpleName()».class); - «IF !allInjections().isEmpty» - «FOR i : allInjections()» - @Inject - private «i.type» «i.name»; - «ENDFOR» - «ENDIF» - - «scopeMethods(it, name.toSimpleName())» - - } - ''' - } - - def scopeMethods(ScopeModel it, String baseName) ''' - @Override - protected IScope doGetScope(final EObject context, final EReference reference, final String scopeName, final Resource originalResource) { - «IF !allScopes().filter(s|s.reference !== null).empty» - if (scopeName == null) { - return null; - } - - switch (scopeName) { - «FOR name : allScopes().filter(s|s.reference !== null).map(s|s.getScopeName()).toSet() - »case "«name»": - «FOR scope : allScopes().filter(s|s.reference !== null).filter(s|s.getScopeName()==name)» - if (reference == «scope.reference.literalIdentifier()») return «scope.scopeMethodName()»(context, reference, originalResource); - «ENDFOR» - break; - « - ENDFOR» - default: break; - } - «ENDIF» - return null; - } - - @Override - protected IScope doGetScope(final EObject context, final EClass type, final String scopeName, final Resource originalResource) { - «IF !allScopes().filter(s|s.reference === null).empty» - if (scopeName == null) { - return null; - } - - switch (scopeName) { - «FOR name : allScopes().filter(s|s.reference === null).map(s|s.getScopeName()).toSet() - »case "«name»": - «FOR scope : allScopes().filter(s|s.reference === null).filter(s|s.getScopeName()==name)» - if (type == «scope.targetType.literalIdentifier()») return «scope.scopeMethodName()»(context, type, originalResource); - «ENDFOR» - break; - « - ENDFOR» - default: break; - } - «ENDIF» - return null; - } - - @Override - protected boolean doGlobalCache(final EObject context, final EReference reference, final String scopeName, final Resource originalResource) { - «IF !allScopes().filter(s|s.reference !== null).filter(s|s.allScopeRules().filter(r|r.context.global).size > 0).empty» - if (scopeName != null && context.eContainer() == null) { - switch (scopeName) { - «FOR name : allScopes().filter(s|s.reference !== null).filter(s|s.allScopeRules().filter(r|r.context.global).size > 0).map(s|s.getScopeName()).toSet() - »case "«name»": - «FOR scope : allScopes().filter(s|s.reference !== null).filter(s|s.getScopeName()==name)» - «val globalRules = scope.allScopeRules().filter(r|r.context.global)» - «IF globalRules.size > 0» - if (reference == «scope.reference.literalIdentifier()») return true; - «ENDIF» - «ENDFOR» - break; - « - ENDFOR» - default: break; - } - } - «ENDIF» - return false; - } - - @Override - protected boolean doGlobalCache(final EObject context, final EClass type, final String scopeName, final Resource originalResource) { - «IF !allScopes().filter(s|s.reference === null).filter(s|s.allScopeRules().filter(r|r.context.global).size > 0).empty» - if (context.eContainer() == null) { - switch (scopeName) { - «FOR name : allScopes().filter(s|s.reference === null).filter(s|s.allScopeRules().filter(r|r.context.global).size > 0).map(s|s.getScopeName()).toSet() - »case "«name»": - «FOR scope : allScopes().filter(s|s.reference === null).filter(s|s.getScopeName()==name)» - «val globalRules = scope.allScopeRules().filter(r|r.context.global)» - «IF globalRules.size > 0» - if (type == «scope.targetType.literalIdentifier()») return true; - «ENDIF» - «ENDFOR» - break; - « - ENDFOR» - default: break; - } - } - «ENDIF» - return false; - } - - «FOR scope : allScopes()» - protected IScope «scope.scopeMethodName()»(final EObject context, final «IF scope.reference !== null»EReference ref«ELSE»EClass type«ENDIF», final Resource originalResource) { - «val localRules = scope.allScopeRules().filter(r|!r.context.global).toList» - «val globalRules = scope.allScopeRules().filter(r|r.context.global).toList» - «if (globalRules.size > 1) throw new RuntimeException("only one global rule allowed")» - «FOR r : localRules.filterUniqueRules().sortedRules()» - «javaContributorComment(r.location())» - if («IF EClassComparator.isEObjectType(r.context.contextType)»true«ELSE»context instanceof «r.context.contextType.instanceClassName()»«ENDIF») { - final «r.context.contextType.instanceClassName()» ctx = («r.context.contextType.instanceClassName()») context; - «val rulesForTypeAndContext = localRules.filter(r2|r2.hasSameContext(r)).toList» - «scopeRuleBlock(rulesForTypeAndContext, it, if (r.contextRef() !== null) 'ref' else 'type', r.context.contextType, r.context.global)» - } - «ENDFOR» - «IF !localRules.isEmpty || !globalRules.isEmpty» - - final EObject eContainer = context.eContainer(); - if (eContainer != null) { - return internalGetScope(«IF !localRules.isEmpty»eContainer«ELSE»getRootObject(eContainer)«ENDIF», «IF scope.reference !== null»ref«ELSE»type«ENDIF», "«scope.getScopeName()»", originalResource); - } - - «ENDIF» - «IF !globalRules.isEmpty» - «val r = globalRules.head» - «val rulesForTypeAndContext = #[r]» - «javaContributorComment(r.location())» - if (context.eResource() != null) { - final Resource ctx = context.eResource(); - «scopeRuleBlock(rulesForTypeAndContext, it, if (r.contextRef() !== null) 'ref' else 'type', r.context.contextType, r.context.global)» - } - - «ENDIF» - return null; - } - - «ENDFOR» - ''' - - def scopeRuleBlock(List it, ScopeModel model, String typeOrRef, EClass contextType, Boolean isGlobal) ''' - IScope scope = IScope.NULLSCOPE; - try { - «IF it.exists(r|r.context.guard !== null)» - «FOR r : it.sortBy(r|if (r.context.guard === null) it.size else it.indexOf(r)) SEPARATOR ' else ' - »«IF r.context.guard !== null»if («r.context.guard.javaExpression(compilationContext.clone('ctx', r.scopeType()))») «ENDIF»{ - «IF it.size > 1»«javaContributorComment(r.location())» - «ENDIF - »«FOR e : Lists.newArrayList(r.exprs).reverse()»«scopeExpression(e, model, typeOrRef, r.getScope(), isGlobal)»«ENDFOR» - }«ENDFOR»« - IF !it.exists(r|r.context.guard === null)» else { - throw new UnsupportedOperationException(); // continue matching other definitions - }«ENDIF» - «ELSEIF it.size == 1» - «FOR e : Lists.newArrayList(it.head.exprs).reverse()»«scopeExpression(e, model, typeOrRef, it.head.getScope(), isGlobal)»«ENDFOR» - «ELSE» - «error('scope context not unique for definitions: ' + ', '.join(it.map(r|r.location())))» - «ENDIF» - } catch (Exception e) { - LOGGER.error("Error calculating scope for «if (isGlobal) "Resource. Context:" else contextType.name» " + EObjectUtil.getLocationString(context) + " («it.get(0).locatorString()»)", e); - } - return scope; - ''' - - def dispatch scopeExpression(ScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope, Boolean isGlobal) { - error("Xtend called the wrong definition." + it.toString() + javaContributorComment(it.location())) - } - - def dispatch scopeExpression(FactoryExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope, Boolean isGlobal) { - val b = new StringBuilder - val ctx = compilationContext.clone('ctx', eContainer(ScopeRule).context.contextType) - b.append('scope = ').append(javaCall(it.expr, ctx)).append('(scope, ctx, ').append(typeOrRef).append(', originalResource'); - if (expr instanceof OperationCall) { - for (param : (expr as OperationCall).params) { - b.append(', ').append(javaExpression(param, ctx)) - } - } - b.append(');\n') - return b - } - - def dispatch javaCall(Expression it, CompilationContext ctx) { - error('cannot handle scope factory ' + it.toString()) - } - - def dispatch javaCall(OperationCall it, CompilationContext ctx) { - if (isJavaExtensionCall(ctx)) - calledJavaMethod(ctx) - else - '/* Error: cannot handle scope factory ' + it.toString() + ' */' - } - - def dispatch scopeExpression(ScopeDelegation it, ScopeModel model, String typeOrRef, ScopeDefinition scope, Boolean isGlobal) ''' - «IF delegate !== null » - «val delegateString = delegate.serialize()» - «IF delegateString == "this.eContainer()" || delegateString == "this.eContainer" || delegateString == "eContainer()" || delegateString == "eContainer"» - scope = newSameScope("«it.locatorString()»", scope, ctx.eContainer()« - ELSEIF delegateString == "this"» - scope = newSameScope("«it.locatorString()»", scope, ctx« - ELSE» - scope = newDelegateScope("«it.locatorString()»", scope, « - IF !isGlobal »() -> IContextSupplier.makeIterable(«scopedElements(delegate, model, eContainer(ScopeRule).context.contextType, 'ctx')»)« - ELSE»«scopedElements(delegate, model, eContainer(ScopeRule).context.contextType, 'ctx')»« - ENDIF»« - ENDIF»« - ELSE» - scope = newExternalDelegateScope("«it.locatorString()»", scope, « - query(external, model, typeOrRef, scope)».execute(originalResource)« - ENDIF», « - IF it.scope !== null && it.scope.typeOrRef() != getScope(it).typeOrRef()»«it.scope.typeOrRef().literalIdentifier()»«ELSE»«typeOrRef»«ENDIF», "«if (it.scope !== null && it.scope.name !== null) it.scope.name else "scope"»", originalResource); - ''' - - def dispatch scopeExpression(NamedScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope, Boolean isGlobal) ''' - scope = «scopeExpressionPart (it, model, typeOrRef, scope)»« - scopeExpressionNaming (it, model, typeOrRef, scope)»« - scopeExpressionCasing (it, model, typeOrRef, scope)»); - ''' - - def dispatch scopeExpression(SimpleScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope, Boolean isGlobal) ''' - «IF expr.isEmptyList() » - // Empty scope from «it.location()» - «ELSE» - scope = «scopeExpressionPart (it, model, typeOrRef, scope)»« - scopeExpressionNaming (it, model, typeOrRef, scope)»« - scopeExpressionCasing (it, model, typeOrRef, scope)»); - «ENDIF» - ''' - - def dispatch scopeExpressionPart (NamedScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) { - error("Xtend called the wrong definition for scopeExpressionPart with this=" + it.toString() + javaContributorComment(it.location())) - } - - def dispatch scopeExpressionPart (SimpleScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) { - 'newSimpleScope("' + locatorString() + '", scope, ' + scopedElements(expr, model, eContainer(ScopeRule).context.contextType, "ctx") + ', ' - } - - def query (GlobalScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) ''' - newQuery(«type.literalIdentifier()»)« - val matchData = data.filter(MatchDataExpression)»« - IF name !== null».name(«doExpression (name, model, 'ctx', eContainer(ScopeRule).context.contextType)»)«ENDIF»« - IF !matchData.isEmpty»«FOR d : matchData».data("«javaEncode(d.key)»", «doExpression (d.value, model, 'ctx', eContainer(ScopeRule).context.contextType)»)«ENDFOR»«ENDIF»« - IF !domains.isEmpty && domains.get(0) != "*"».domains(«FOR d : domains SEPARATOR ", "»"«javaEncode(d)»"«ENDFOR»)«ENDIF - »''' - - def dispatch scopeExpressionPart (GlobalScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) ''' - «val matchData = data.filter(LambdaDataExpression)» - «IF matchData.isEmpty && prefix === null»newContainerScope(«ELSEIF matchData.isEmpty && prefix !== null»newPrefixedContainerScope(«ELSE»newDataMatchScope(«ENDIF»"«it.locatorString()»", scope, ctx, «query (it, model, typeOrRef, scope)», originalResource« - IF !matchData.isEmpty», // - Arrays.asList( - «FOR d : matchData SEPARATOR ","» - «val CompilationContext cc = compilationContext.cloneWithVariable('ctx', eContainer(ScopeRule).context.contextType, d.desc, 'org::eclipse::xtext::resource::IEObjectDescription')» - «IF d.value.isCompilable(cc.clone('ctx'))» - «d.desc» -> «d.value.javaExpression(cc.clone('ctx'))» - «ELSE» - «d.desc» -> EXPRESSION_NOT_SUPPORTED("«serialize()»") - «ENDIF»« - ENDFOR» )« - ELSEIF prefix !== null», «doExpression (prefix, model, 'ctx', eContainer(ScopeRule).context.contextType)», «recursivePrefix»« - ENDIF - »''' - - def dispatch scopeExpressionNaming (NamedScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) { - error("Xtend called the wrong definition for scopeExpressionNaming with this=" + it.toString() + javaContributorComment(it.location())) - } - - def dispatch scopeExpressionNaming (SimpleScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) { - name(it, model, typeOrRef, 'ctx', eContainer(ScopeRule).context.contextType) - } - - def dispatch scopeExpressionNaming (GlobalScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) { - ', ' + name(it, model, typeOrRef, 'ctx', eContainer(ScopeRule).context.contextType) - } - - def scopeExpressionCasing (NamedScopeExpression it, ScopeModel model, String typeOrRef, ScopeDefinition scope) { - ', ' + isCaseInsensitive().toString - } - - def scopedElements(Expression it, ScopeModel model, EClass type, String object) { - doExpression(it, model, object, type) - } - - def doExpression(Expression it, ScopeModel model, String object, EClass type) { - javaExpression (compilationContext.clone(object, type)) - } - - def name(NamedScopeExpression it, ScopeModel model, String typeOrRef, String contextName, EClass contextType) { - if (it.naming !== null) - nameProviderGenerator.nameFunctions(it.naming, model, contextName, contextType) - else - 'getNameFunctions(' + typeOrRef + ')' - } - - def error(String message) { - throw new RuntimeException(message) - } - -} diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderX.java b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderX.java new file mode 100644 index 0000000000..fd8e0ec884 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderX.java @@ -0,0 +1,561 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.scope.generator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import com.avaloq.tools.ddk.xtext.expression.expression.Expression; +import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall; +import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX; +import com.avaloq.tools.ddk.xtext.expression.generator.ExpressionExtensionsX; +import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX; +import com.avaloq.tools.ddk.xtext.expression.generator.Naming; +import com.avaloq.tools.ddk.xtext.scope.ScopeUtil; +import com.avaloq.tools.ddk.xtext.scope.scope.Injection; +import com.avaloq.tools.ddk.xtext.scope.scope.NamedScopeExpression; +import com.avaloq.tools.ddk.xtext.scope.scope.NamingDefinition; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeDefinition; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel; +import com.avaloq.tools.ddk.xtext.scope.scope.ScopeRule; +import com.google.inject.Inject; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.ENamedElement; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; + +@SuppressWarnings({"checkstyle:MethodName", "PMD.UnusedFormalParameter", "nls"}) +public class ScopeProviderX { + + @Inject + private Naming naming; + @Inject + private GeneratorUtilX generatorUtilX; + @Inject + private CodeGenerationX codeGenerationX; + @Inject + private ExpressionExtensionsX expressionExtensionsX; + + /* + * CODE GENERATION + */ + + /** + * Returns the fully qualified name of the scope provider for the given model. + * + * @param model + * the scope model + * @return the scope provider name + */ + public String getScopeProvider(final ScopeModel model) { + return naming.toJavaPackage(model.getName()) + ".scoping." + naming.toSimpleName(model.getName()) + "ScopeProvider"; + } + + /** + * Returns the fully qualified name of the scope name provider for the given model. + * + * @param model + * the scope model + * @return the scope name provider name + */ + public String getScopeNameProvider(final ScopeModel model) { + return naming.toJavaPackage(model.getName()) + ".scoping." + naming.toSimpleName(model.getName()) + "ScopeNameProvider"; + } + + /** + * Returns the name of the scope method generated for the given scope definition. + * + * @param it + * the scope definition + * @return the scope method name + */ + public String scopeMethodName(final ScopeDefinition it) { + return getScopeName(it) + "_" + (it.getTargetType() != null ? it.getTargetType().getEPackage().getName() + "_" + it.getTargetType().getName() : it.getContextType().getEPackage().getName() + "_" + it.getContextType().getName() + "_" + it.getReference().getName()); + } + + /** + * Returns the locator string for the given object. + * + * @param it + * the EObject + * @return the encoded locator string + */ + public String locatorString(final EObject it) { + final String location = generatorUtilX.location(it); + final String[] parts = location.split("/"); + final String last = parts.length > 0 ? parts[parts.length - 1] : null; + return codeGenerationX.javaEncode(last); + } + + /** + * Returns the called feature name from a feature call. + * + * @param it + * the feature call + * @return the called feature name + */ + public String calledFeature(final FeatureCall it) { + return it.getType().getId().get(0); + } + + /** + * Returns the structural feature for the given feature call. + * + * @param it + * the feature call + * @return the structural feature + */ + public EStructuralFeature feature(final FeatureCall it) { + return scopeType(it).getEStructuralFeature(calledFeature(it)); + } + + /* + * SCOPE RULES + */ + // dispatch allScopeRules + protected List _allScopeRules(final Void it) { + return new ArrayList<>(); + } + + protected List _allScopeRules(final ScopeDefinition it) { + return collectAllScopeRules(getModel(it), it); + } + + /** + * Returns all scope rules for the given scope definition. + * + * @param it + * the scope definition + * @return the list of all scope rules + */ + public List allScopeRules(final ScopeDefinition it) { + if (it != null) { + return _allScopeRules(it); + } else { + return _allScopeRules((Void) null); + } + } + + /** + * Collects all scope rules from the model and included scopes. + * + * @param it + * the scope model + * @param def + * the scope definition to match + * @return the collected scope rules + */ + public List collectAllScopeRules(final ScopeModel it, final ScopeDefinition def) { + final List myScopeRules = new ArrayList<>(); + for (final ScopeDefinition d : it.getScopes()) { + if (isEqual(d, def)) { + myScopeRules.addAll(d.getRules()); + } + } + final List result = new ArrayList<>(); + if (!it.getIncludedScopes().isEmpty()) { + for (final ScopeModel included : it.getIncludedScopes()) { + result.addAll(collectAllScopeRules(included, def)); + } + } + result.addAll(myScopeRules); + return result; + } + + /** + * Returns the rules sorted. + * + * @param it + * the collection of scope rules + * @return the sorted list of rules + */ + public List sortedRules(final Collection it) { + return ScopingGeneratorUtil.sortedRules(it); + } + + /** + * Filters the unique rules from the list. + * + * @param it + * the list of scope rules + * @return the set of unique rules + */ + public Set filterUniqueRules(final List it) { + return it.stream() + .map(r -> it.stream().filter(r2 -> hasSameContext(r2, r)).findFirst().orElse(null)) + .collect(Collectors.toSet()); + } + + // dispatch isEqual(ScopeRule, ScopeRule) + protected boolean _isEqual(final ScopeRule a, final ScopeRule b) { + return hasSameContext(a, b) + // && ((a.name === null) == (b.name === null)) && (a.name === null || a.name.matches (b.name)) + && Objects.equals(expressionExtensionsX.serialize(a.getContext().getGuard()), expressionExtensionsX.serialize(b.getContext().getGuard())); + } + + /** + * Returns whether two scope rules have the same context. + * + * @param a + * the first scope rule + * @param b + * the second scope rule + * @return true if the rules have the same context + */ + public boolean hasSameContext(final ScopeRule a, final ScopeRule b) { + return ruleSignature(a).equals(ruleSignature(b)); + } + + // Hrmph. Use naming here, otherwise we'll get strange (and wrong) results in the GenerateAllAPSLs workflow for netwStruct?! + private /*cached*/ String ruleSignature(final ScopeRule s) { + return ScopeUtil.getSignature(s); + } + + /* + * SCOPE DEFINITIONS + */ + // returns the list of all local and inherited scope definition (skipping any shadowed or extended scope definitions) + // dispatch allScopes + protected List _allScopes(final ScopeModel it) { + final List myScopes = it.getScopes(); + final List result = new ArrayList<>(); + if (!it.getIncludedScopes().isEmpty()) { + for (final ScopeModel included : it.getIncludedScopes()) { + result.addAll(allScopes(included)); + } + } + result.removeIf(s -> hasScope(myScopes, s)); + result.addAll(myScopes); + return result; + } + + protected List _allScopes(final Void it) { + return new ArrayList<>(); + } + + /** + * Returns the list of all local and inherited scope definitions. + * + * @param it + * the scope model + * @return the list of all scope definitions + */ + public List allScopes(final ScopeModel it) { + if (it != null) { + return _allScopes(it); + } else { + return _allScopes((Void) null); + } + } + + /** + * Returns the scope name for the given definition. + * + * @param it + * the scope definition + * @return the scope name, or "scope" if not set + */ + public String getScopeName(final ScopeDefinition it) { + if (it.getName() == null) { + return "scope"; + } else { + return it.getName(); + } + } + + /** + * Returns whether the list contains a scope equal to the given scope. + * + * @param list + * the list of scope definitions + * @param scope + * the scope definition to check + * @return true if the list contains an equal scope + */ + public boolean hasScope(final List list, final ScopeDefinition scope) { + return !list.isEmpty() && list.stream().anyMatch(s -> isEqual(s, scope)); + } + + // dispatch isEqual(ScopeDefinition, ScopeDefinition) + protected boolean _isEqual(final ScopeDefinition a, final ScopeDefinition b) { + return getScopeName(a).equals(getScopeName(b)) && isEqual(a.getTargetType(), b.getTargetType()) && isEqual(a.getReference(), b.getReference()); + } + + /* + * SCOPE TYPE + */ + // dispatch scopeType + protected EClass _scopeType(final ScopeDefinition it) { + if (it.getReference() != null) { + return it.getReference().getEReferenceType(); + } else { + return it.getTargetType(); + } + } + + protected EClass _scopeType(final ScopeRule it) { + return scopeType(getScope(it)); + } + + protected EClass _scopeType(final Expression it) { + if (getScope(it) != null) { + return scopeType(getScope(it)); + } else { + return getNamingDef(it).getType(); + } + } + + /** + * Returns the scope type for the given object. + * + * @param it + * the EObject (ScopeDefinition, ScopeRule, or Expression) + * @return the scope EClass type + * @throws IllegalArgumentException + * if the parameter type is not handled + */ + public EClass scopeType(final EObject it) { + if (it instanceof ScopeDefinition scopeDefinition) { + return _scopeType(scopeDefinition); + } else if (it instanceof ScopeRule scopeRule) { + return _scopeType(scopeRule); + } else if (it instanceof Expression expression) { + return _scopeType(expression); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + it); + } + } + + /** + * Returns the type or reference of the scope definition. + * + * @param it + * the scope definition + * @return the type or reference + */ + public ENamedElement typeOrRef(final ScopeDefinition it) { + if (it.getReference() != null) { + return it.getReference(); + } else { + return it.getTargetType(); + } + } + + /** + * Returns the context reference of the scope rule. + * + * @param it + * the scope rule + * @return the context reference + */ + public EReference contextRef(final ScopeRule it) { + return getScope(it).getReference(); + } + + /* + * Injections + */ + // returns the list of all local and inherited injections (skipping any shadowed injections) + // dispatch allInjections + protected List _allInjections(final ScopeModel it) { + final List myInjections = it.getInjections(); + final List result = new ArrayList<>(); + if (!it.getIncludedScopes().isEmpty()) { + for (final ScopeModel included : it.getIncludedScopes()) { + result.addAll(allInjections(included)); + } + } + result.removeIf(i -> hasInjection(myInjections, i)); + result.addAll(myInjections); + return result; + } + + protected List _allInjections(final Void it) { + return new ArrayList<>(); + } + + /** + * Returns the list of all local and inherited injections. + * + * @param it + * the scope model + * @return the list of all injections + */ + public List allInjections(final ScopeModel it) { + if (it != null) { + return _allInjections(it); + } else { + return _allInjections((Void) null); + } + } + + /** + * Returns whether the list contains an injection equal to the given injection. + * + * @param list + * the list of injections + * @param injection + * the injection to check + * @return true if the list contains an equal injection + */ + public boolean hasInjection(final List list, final Injection injection) { + return !list.isEmpty() && list.stream().anyMatch(i -> isEqual(i, injection)); + } + + // dispatch isEqual(Injection, Injection) + protected boolean _isEqual(final Injection a, final Injection b) { + return a.getType().equals(b.getType()) && a.getName().equals(b.getName()); + } + + /* + * SCOPE EXPRESSIONS + */ + + /** + * Returns whether the named scope expression is case insensitive. + * + * @param it + * the named scope expression + * @return true if case insensitive + */ + public boolean isCaseInsensitive(final NamedScopeExpression it) { + return ScopingGeneratorUtil.isCaseInsensitive(it); + } + + /* + * ECONTAINER + */ + + /** + * Returns the scope model from the given object's resource. + * + * @param it + * the EObject + * @return the scope model + */ + public ScopeModel getModel(final EObject it) { + return (ScopeModel) it.eResource().getContents().get(0); + } + + /** + * Returns the enclosing scope definition for the given object. + * + * @param it + * the EObject + * @return the enclosing scope definition + */ + public /*cached*/ ScopeDefinition getScope(final EObject it) { + return eContainer(it, ScopeDefinition.class); + } + + /** + * Returns the enclosing naming definition for the given object. + * + * @param it + * the EObject + * @return the enclosing naming definition + */ + public /*cached*/ NamingDefinition getNamingDef(final EObject it) { + return eContainer(it, NamingDefinition.class); + } + + /** + * Finds the nearest ancestor of the given type. + * + * @param + * the ancestor type + * @param it + * the EObject + * @param type + * the class of the ancestor type + * @return the nearest ancestor of the given type, or null + */ + public T eContainer(final EObject it, final Class type) { + if (it == null) { + return null; + } else if (type.isInstance(it)) { + @SuppressWarnings("unchecked") + final T result = (T) it; + return result; + } else { + return eContainer(it.eContainer(), type); + } + } + + /* + * ECORE + */ + // dispatch isEqual(EClass, EClass) + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _isEqual(final EClass a, final EClass b) { + return a == b || (a != null && b != null && a.getName().equals(b.getName()) && a.getEPackage().getNsURI().equals(b.getEPackage().getNsURI())); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + protected boolean _isEqualVoidVoid(final Void a, final Void b) { + return true; + } + + protected boolean _isEqualObjectVoid(final EObject a, final Void b) { + return false; + } + + protected boolean _isEqualVoidObject(final Void a, final EObject b) { + return false; + } + + // dispatch isEqual(EReference, EReference) + // CHECKSTYLE:CHECK-OFF BooleanExpressionComplexity + protected boolean _isEqual(final EReference a, final EReference b) { + return a == b || (a != null && b != null && a.getName().equals(b.getName()) && isEqual(a.getEContainingClass(), b.getEContainingClass())); + } + // CHECKSTYLE:CHECK-ON BooleanExpressionComplexity + + /** + * Public dispatcher for isEqual - handles all type combinations. + * + * @param a + * the first object + * @param b + * the second object + * @return true if the two objects are considered equal + * @throws IllegalArgumentException + * if the parameter types are not handled + */ + public boolean isEqual(final Object a, final Object b) { + if (a instanceof ScopeRule ruleA && b instanceof ScopeRule ruleB) { + return _isEqual(ruleA, ruleB); + } else if (a instanceof ScopeDefinition defA && b instanceof ScopeDefinition defB) { + return _isEqual(defA, defB); + } else if (a instanceof Injection injA && b instanceof Injection injB) { + return _isEqual(injA, injB); + } else if (a instanceof EReference refA && b instanceof EReference refB) { + return _isEqual(refA, refB); + } else if (a instanceof EClass classA && b instanceof EClass classB) { + return _isEqual(classA, classB); + } else if (a == null && b == null) { + return _isEqualVoidVoid(null, null); + } else if (a instanceof EObject && b == null) { + return _isEqualObjectVoid((EObject) a, null); + } else if (a == null && b instanceof EObject) { + return _isEqualVoidObject(null, (EObject) b); + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + a + ", " + b); + } + } +} diff --git a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderX.xtend b/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderX.xtend deleted file mode 100644 index 6ad50bec0e..0000000000 --- a/com.avaloq.tools.ddk.xtext.scope/src/com/avaloq/tools/ddk/xtext/scope/generator/ScopeProviderX.xtend +++ /dev/null @@ -1,248 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. it program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies it distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.scope.generator - -import com.avaloq.tools.ddk.xtext.expression.expression.Expression -import com.avaloq.tools.ddk.xtext.expression.expression.FeatureCall -import com.avaloq.tools.ddk.xtext.expression.generator.CodeGenerationX -import com.avaloq.tools.ddk.xtext.expression.generator.ExpressionExtensionsX -import com.avaloq.tools.ddk.xtext.expression.generator.GeneratorUtilX -import com.avaloq.tools.ddk.xtext.expression.generator.Naming -import com.avaloq.tools.ddk.xtext.scope.ScopeUtil -import com.avaloq.tools.ddk.xtext.scope.scope.Injection -import com.avaloq.tools.ddk.xtext.scope.scope.NamedScopeExpression -import com.avaloq.tools.ddk.xtext.scope.scope.NamingDefinition -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeDefinition -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeModel -import com.avaloq.tools.ddk.xtext.scope.scope.ScopeRule -import com.google.inject.Inject -import java.util.Collection -import java.util.List -import java.util.Set -import org.eclipse.emf.ecore.EClass -import org.eclipse.emf.ecore.ENamedElement -import org.eclipse.emf.ecore.EObject -import org.eclipse.emf.ecore.EReference -import org.eclipse.emf.ecore.EStructuralFeature - -class ScopeProviderX { - - @Inject - extension Naming - @Inject - extension GeneratorUtilX - @Inject - extension CodeGenerationX - @Inject - extension ExpressionExtensionsX - - /* - * CODE GENERATION - */ - def getScopeProvider(ScopeModel model) { - model.name.toJavaPackage() + ".scoping." + model.name.toSimpleName() + "ScopeProvider" - } - - def getScopeNameProvider(ScopeModel model) { - model.name.toJavaPackage() + ".scoping." + model.name.toSimpleName() + "ScopeNameProvider" - } - - // returns the name of the scope method generated for the given scope definition - def String scopeMethodName(ScopeDefinition it) { - getScopeName() + '_' + (if (targetType !== null) targetType.EPackage.name + '_' + targetType.name else contextType.EPackage.name + '_' + contextType.name + '_' + reference.name) - } - - def String locatorString(EObject it) { - location().split('/').lastOrNull().javaEncode() - } - - def String calledFeature(FeatureCall it) { - type.id.head - } - - def EStructuralFeature feature(FeatureCall it) { - scopeType().getEStructuralFeature(calledFeature()) - } - - /* - * SCOPE RULES - */ - def dispatch List allScopeRules(Void it) { - newArrayList() - } - - def dispatch List allScopeRules(ScopeDefinition it) { - getModel().collectAllScopeRules(it) - } - - def List collectAllScopeRules(ScopeModel it, ScopeDefinition ^def) { - val d = scopes.filter(d|d.isEqual(^def)) - val myScopeRules = if (d === null) newArrayList else d.map[rules].flatten - val result = - if (includedScopes.isEmpty) - newArrayList - else - includedScopes.map[collectAllScopeRules(def)].flatten().toList - result.addAll(myScopeRules) - result - } - - def List sortedRules(Collection it) { - ScopingGeneratorUtil.sortedRules(it) - } - - def Set filterUniqueRules(List it) { - map(r|findFirst(r2|r2.hasSameContext(r))).toSet() - } - - def dispatch boolean isEqual(ScopeRule a, ScopeRule b) { - a.hasSameContext(b) - // && ((a.name === null) == (b.name === null)) && (a.name === null || a.name.matches (b.name)) - && a.context.guard.serialize() == b.context.guard.serialize() - } - - def boolean hasSameContext(ScopeRule a, ScopeRule b) { - a.ruleSignature() == b.ruleSignature() - } - - // Hrmph. Use naming here, otherwise we'll get strange (and wrong) results in the GenerateAllAPSLs workflow for netwStruct?! - def private /*cached*/ String ruleSignature(ScopeRule s) { - ScopeUtil.getSignature(s) - } - - /* - * SCOPE DEFINITIONS - */ - // returns the list of all local and inherited scope definition (skipping any shadowed or extended scope definitions) - def dispatch List allScopes(ScopeModel it) { - val myScopes = it.scopes - val result = if (it.includedScopes.isEmpty) newArrayList else it.includedScopes.map[allScopes()].flatten.toList - result.removeIf(s|myScopes.hasScope(s)) - result.addAll(myScopes) - result - } - - def dispatch List allScopes(Void it) { - newArrayList - } - - def String getScopeName(ScopeDefinition it) { - if (it.name === null) 'scope' else it.name - } - - def boolean hasScope(List list, ScopeDefinition scope) { - if (list.isEmpty) false else !(list.filter(s|s.isEqual(scope)).isEmpty) - } - - def dispatch boolean isEqual(ScopeDefinition a, ScopeDefinition b) { - a.getScopeName() == b.getScopeName() && a.targetType.isEqual(b.targetType) && a.reference.isEqual(b.reference) - } - - /* - * SCOPE TYPE - */ - def dispatch EClass scopeType(ScopeDefinition it) { - if (reference !== null) reference.EReferenceType else targetType - } - - def dispatch EClass scopeType(ScopeRule it) { - getScope().scopeType() - } - - def dispatch EClass scopeType(Expression it) { - if (getScope() !== null) getScope().scopeType() else getNamingDef().type - } - - def ENamedElement typeOrRef(ScopeDefinition it) { - if (reference !== null) reference else targetType - } - - def EReference contextRef(ScopeRule it) { - getScope().reference - } - - /* - * Injections - */ - // returns the list of all local and inherited injections (skipping any shadowed injections) - def dispatch List allInjections(ScopeModel it) { - val myInjections = it.injections - val result = if (it.includedScopes.isEmpty) newArrayList else it.includedScopes.map[allInjections()].flatten.toList - result.removeIf(i|myInjections.hasInjection(i)) - result.addAll(myInjections) - result - } - - def dispatch List allInjections(Void it) { - newArrayList - } - - def boolean hasInjection(List list, Injection injection) { - if (list.isEmpty) false else !(list.filter(i|i.isEqual(injection)).isEmpty) - } - - def dispatch boolean isEqual(Injection a, Injection b) { - a.type == b.type && a.name == b.name - } - - /* - * SCOPE EXPRESSIONS - */ - def boolean isCaseInsensitive(NamedScopeExpression it) { - ScopingGeneratorUtil.isCaseInsensitive(it) - } - - /* - * ECONTAINER - */ - def ScopeModel getModel(EObject it) { - it.eResource().contents.head as ScopeModel - } - - def /*cached*/ ScopeDefinition getScope(EObject it) { - eContainer(ScopeDefinition) - } - - def /*cached*/ NamingDefinition getNamingDef(EObject it) { - eContainer(NamingDefinition) - } - - def T eContainer(EObject it, Class type) { - if (it === null) return null - else if (type.isInstance(it)) it as T - else eContainer().eContainer(type) - } - - /* - * ECORE - */ - def dispatch boolean isEqual(EClass a, EClass b) { - a == b || (a !== null && b !== null && a.name == b.name && a.EPackage.nsURI == b.EPackage.nsURI) - } - - def dispatch boolean isEqual(Void a, Void b) { - true - } - - def dispatch boolean isEqual(EObject a, Void b) { - false - } - - def dispatch boolean isEqual(Void a, EObject b) { - false - } - - def dispatch boolean isEqual(EReference a, EReference b) { - a == b || (a !== null && b !== null && a.name == b.name && a.EContainingClass.isEqual(b.EContainingClass)) - } - -} \ No newline at end of file diff --git a/com.avaloq.tools.ddk.xtext.scope/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.scope/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.test.core/.classpath b/com.avaloq.tools.ddk.xtext.test.core/.classpath index d651646e41..4e3ebccc78 100644 --- a/com.avaloq.tools.ddk.xtext.test.core/.classpath +++ b/com.avaloq.tools.ddk.xtext.test.core/.classpath @@ -1,11 +1,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF index 47dd4106a2..4ce941fbe6 100644 --- a/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF +++ b/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF @@ -13,7 +13,6 @@ Require-Bundle: com.avaloq.tools.ddk.xtext, org.eclipse.jdt.core, org.eclipse.pde.core, org.eclipse.ui.ide, - org.eclipse.xtend.lib, org.eclipse.xtext.testing;visibility:=reexport, org.eclipse.xtext.ui.testing;visibility:=reexport, org.eclipse.xtext.ui, diff --git a/com.avaloq.tools.ddk.xtext.test.core/build.properties b/com.avaloq.tools.ddk.xtext.test.core/build.properties index eeeed051e3..a12d47e566 100644 --- a/com.avaloq.tools.ddk.xtext.test.core/build.properties +++ b/com.avaloq.tools.ddk.xtext.test.core/build.properties @@ -1,4 +1,3 @@ -source.. = src/,\ - xtend-gen/ +source.. = src/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/Tag.xtend b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/Tag.java similarity index 79% rename from com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/Tag.xtend rename to com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/Tag.java index c3d18b00c8..dcaf3c9c81 100644 --- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/Tag.xtend +++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/Tag.java @@ -8,16 +8,16 @@ * Contributors: * Avaloq Group AG - initial API and implementation *******************************************************************************/ -package com.avaloq.tools.ddk.xtext.test +package com.avaloq.tools.ddk.xtext.test; -import org.eclipse.xtend.lib.macro.Active +import org.eclipse.xtend.lib.macro.Active; /** * Initializes global tags in linking tests. * The annotated field must be of integer type. * Usage example: @Tag int MEM_DOC */ -@Active(typeof(TagCompilationParticipant)) -annotation Tag { +@Active(TagCompilationParticipant.class) +public @interface Tag { } diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/resource/AbstractResourceDescriptionManagerTest.xtend b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/resource/AbstractResourceDescriptionManagerTest.java similarity index 58% rename from com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/resource/AbstractResourceDescriptionManagerTest.xtend rename to com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/resource/AbstractResourceDescriptionManagerTest.java index f8b03f2670..440b189171 100644 --- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/resource/AbstractResourceDescriptionManagerTest.xtend +++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/resource/AbstractResourceDescriptionManagerTest.java @@ -10,60 +10,70 @@ *******************************************************************************/ package com.avaloq.tools.ddk.xtext.test.resource; +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.xtext.resource.IResourceDescription; +import org.eclipse.xtext.resource.IResourceDescription.Delta; +import org.eclipse.xtext.resource.IResourceDescriptions; +import org.junit.Assert; + +import com.avaloq.tools.ddk.xtext.resource.AbstractCachingResourceDescriptionManager; import com.avaloq.tools.ddk.xtext.test.AbstractXtextTest; -import org.eclipse.xtext.resource.IResourceDescription -import com.avaloq.tools.ddk.xtext.resource.AbstractCachingResourceDescriptionManager -import org.eclipse.xtext.resource.IResourceDescriptions -import org.eclipse.emf.common.util.URI -import java.util.Collection -import org.eclipse.xtext.resource.IResourceDescription.Delta -import com.google.common.collect.HashMultiset -import org.junit.Assert -import com.google.common.collect.Sets -import com.avaloq.tools.ddk.xtext.test.TestSource +import com.avaloq.tools.ddk.xtext.test.TestSource; +import com.avaloq.tools.ddk.xtext.test.XtextTestSource; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Sets; /** * Abstract base class for {@link AbstractCachingResourceDescriptionManager} tests. */ -abstract class AbstractResourceDescriptionManagerTest extends AbstractXtextTest { +@SuppressWarnings("nls") +public abstract class AbstractResourceDescriptionManagerTest extends AbstractXtextTest { /** * Simple unchanged {@link Delta} implementation with {@link URI}. */ static class UnchangedDelta implements Delta { - val URI uri; + private final URI uri; - new(URI uri) { + UnchangedDelta(final URI uri) { this.uri = uri; } - override getNew() { - null + @Override + public IResourceDescription getNew() { + return null; } - override getOld() { - null + @Override + public IResourceDescription getOld() { + return null; } - override getUri() { - uri + @Override + public URI getUri() { + return uri; } - override haveEObjectDescriptionsChanged() { - false + @Override + public boolean haveEObjectDescriptionsChanged() { + return false; } } - val AbstractCachingResourceDescriptionManager resourceDescriptionManager = testUtil.get(IResourceDescription.Manager) as AbstractCachingResourceDescriptionManager; - val IResourceDescriptions index = testUtil.get(IResourceDescriptions); + private final AbstractCachingResourceDescriptionManager resourceDescriptionManager = (AbstractCachingResourceDescriptionManager) getTestUtil().get(IResourceDescription.Manager.class); + private final IResourceDescriptions index = getTestUtil().get(IResourceDescriptions.class); /** * Returns the {@link AbstractCachingResourceDescriptionManager) to use in the test. * * @return the {@link AbstractCachingResourceDescriptionManager) to use in the test, never {@code null} */ - def AbstractCachingResourceDescriptionManager getResourceDescriptionManager() { + public AbstractCachingResourceDescriptionManager getResourceDescriptionManager() { return resourceDescriptionManager; } @@ -72,7 +82,7 @@ def AbstractCachingResourceDescriptionManager getResourceDescriptionManager() { * * @return the {@link IResourceDescriptions) to use in the test, never {@code null} */ - def IResourceDescriptions getResourceDescriptions() { + public IResourceDescriptions getResourceDescriptions() { return index; } @@ -84,11 +94,12 @@ def IResourceDescriptions getResourceDescriptions() { * * @return the candidates for the affected resource computation, never {@code null} */ - def Collection getCandidates() { - var Collection candidates = testInformation.getTestObject(URI) as Collection; - if (candidates === null) { - candidates = Sets.newHashSet; - testInformation.putTestObject(URI, candidates); + @SuppressWarnings("unchecked") + public Collection getCandidates() { + Collection candidates = (Collection) getTestInformation().getTestObject(URI.class); + if (candidates == null) { + candidates = Sets.newHashSet(); + getTestInformation().putTestObject(URI.class, candidates); } return candidates; } @@ -102,9 +113,10 @@ def Collection getCandidates() { * content of source, must not be {@code null} * @return a new {@link TestSource} with the given parameters, never {@code null} */ - override protected createTestSource(String sourceFileName, String content) { - val testSource = super.createTestSource(sourceFileName, content); - getCandidates().add(testSource.uri); + @Override + protected XtextTestSource createTestSource(final String sourceFileName, final String content) { + final XtextTestSource testSource = super.createTestSource(sourceFileName, content); + getCandidates().add(testSource.getUri()); return testSource; } @@ -118,7 +130,7 @@ override protected createTestSource(String sourceFileName, String content) { * the delta {@link URI}, must not be {@code null} * @return a new {@link Delta}, never {@code null} */ - def Delta createDelta(URI uri) { + public Delta createDelta(final URI uri) { return new UnchangedDelta(uri); } @@ -129,8 +141,8 @@ def Delta createDelta(URI uri) { * file name for the test source, must not be {@code null} * @return the {@link URI} of the {@link TestSource} with the given file name, never {@code null} */ - def URI getUri(String sourceFileName) { - return getTestSource(sourceFileName).uri; + public URI getUri(final String sourceFileName) { + return getTestSource(sourceFileName).getUri(); } /** @@ -144,9 +156,9 @@ def URI getUri(String sourceFileName) { * @param expectedSourceNames * the expected affected source names, must not be {@code null} */ - def assertAffectedResources(String deltaSourceName, String... expectedSourceNames) { - val Collection expectedUris = Sets.newHashSet; - for (sourceName : expectedSourceNames) { + public void assertAffectedResources(final String deltaSourceName, final String... expectedSourceNames) { + final Collection expectedUris = Sets.newHashSet(); + for (final String sourceName : expectedSourceNames) { expectedUris.add(getUri(sourceName)); } assertAffectedResources(Sets.newHashSet(getUri(deltaSourceName)), getCandidates(), expectedUris); @@ -163,7 +175,7 @@ def assertAffectedResources(String deltaSourceName, String... expectedSourceName * @param expectedUris * the expected affected {@link URI}s, must not be {@code null} */ - def assertAffectedResources(Collection deltaUris, Collection expectedUris) { + public void assertAffectedResources(final Collection deltaUris, final Collection expectedUris) { assertAffectedResources(deltaUris, getCandidates(), expectedUris); } @@ -177,8 +189,9 @@ def assertAffectedResources(Collection deltaUris, Collection expectedU * @param expectedUris * the expected affected {@link URI}s, must not be {@code null} */ - def assertAffectedResources(Collection deltaUris, Collection candidates, Collection expectedUris) { - assertDeltaAffectedResources(Sets.newHashSet(deltaUris.map[createDelta(it)]), candidates, expectedUris); + public void assertAffectedResources(final Collection deltaUris, final Collection candidates, final Collection expectedUris) { + final Set deltas = deltaUris.stream().map(uri -> createDelta(uri)).collect(Collectors.toSet()); + assertDeltaAffectedResources(deltas, candidates, expectedUris); } /** @@ -191,8 +204,8 @@ def assertAffectedResources(Collection deltaUris, Collection candidate * @param expectedUris * the expected affected {@link URI}s, must not be {@code null} */ - def assertDeltaAffectedResources(Collection deltas, Collection candidates, Collection expectedUris) { - val result = getResourceDescriptionManager().getAffectedResources(deltas, candidates, getResourceDescriptions()); + public void assertDeltaAffectedResources(final Collection deltas, final Collection candidates, final Collection expectedUris) { + final Collection result = getResourceDescriptionManager().getAffectedResources(deltas, candidates, getResourceDescriptions()); Assert.assertEquals("Affected URIs must be correct.", HashMultiset.create(expectedUris), HashMultiset.create(result)); } } diff --git a/com.avaloq.tools.ddk.xtext.test.core/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.test.core/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.ui.test/.classpath b/com.avaloq.tools.ddk.xtext.ui.test/.classpath index 338476cab8..5540ec29d4 100644 --- a/com.avaloq.tools.ddk.xtext.ui.test/.classpath +++ b/com.avaloq.tools.ddk.xtext.ui.test/.classpath @@ -1,7 +1,6 @@ - diff --git a/com.avaloq.tools.ddk.xtext.ui.test/build.properties b/com.avaloq.tools.ddk.xtext.ui.test/build.properties index d6c49e5cf1..6b7927735f 100644 --- a/com.avaloq.tools.ddk.xtext.ui.test/build.properties +++ b/com.avaloq.tools.ddk.xtext.ui.test/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - xtend-gen/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.ui.test/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelperTest.java b/com.avaloq.tools.ddk.xtext.ui.test/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelperTest.java new file mode 100644 index 0000000000..560faa4da9 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.ui.test/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelperTest.java @@ -0,0 +1,267 @@ +/******************************************************************************* + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Avaloq Group AG - initial API and implementation + *******************************************************************************/ + +package com.avaloq.tools.ddk.xtext.ui.templates; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.inject.Guice; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.templates.Template; +import org.eclipse.jface.text.templates.TemplateBuffer; +import org.eclipse.xtext.XtextRuntimeModule; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.ui.editor.templates.XtextTemplateContext; +import org.eclipse.xtext.ui.editor.templates.XtextTemplateContextType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + + +@ExtendWith(InjectionExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@SuppressWarnings("nls") +public class TemplateProposalProviderHelperTest { + + private static final String SIMPLE_ENUM_VARIABLE_TYPE = new SimpleEnumTemplateVariableResolver().getType(); + private static final String VARIABLE_NAME = "variableName"; + private static final String RETURNED_PATTERN = "Pattern returned from createSimpleEnumPattern()"; + + private static final String TEMPLATE_NAME = "TemplateName"; + private static final String TEMPLATE_DESCRIPTION = "Template description"; + private static final String CONTEXT_TYPE_ID = "context.type.ID"; + private static final boolean IS_AUTO_INSERTABLE = true; + + private static XtextTemplateContext templateContext; + + private static TemplateProposalProviderHelper helper; + + @BeforeAll + @SuppressWarnings("PMD.SignatureDeclareThrowsException") + public void beforeAll() throws Exception { + final IDocument mockDocument = mock(IDocument.class); + final Position mockPosition = mock(Position.class); + final IRegion mockRegion = mock(IRegion.class); + + final XtextTemplateContextType templateContextType = new XtextTemplateContextType(); + templateContextType.addResolver(new SimpleEnumTemplateVariableResolver()); + templateContext = new XtextTemplateContext(templateContextType, mockDocument, mockPosition, null, null); + + helper = Guice.createInjector(new XtextRuntimeModule()).getInstance(TemplateProposalProviderHelper.class); + + when(mockDocument.getLineInformationOfOffset(anyInt())).thenReturn(mockRegion); + when(mockDocument.get(anyInt(), anyInt())).thenReturn(""); + } + + @AfterAll + public void afterAll() { + templateContext = null; + + helper = null; + } + + // CHECKSTYLE:CONSTANTS-OFF + @Test + public void testCreateLiteralValuePatternWithNullName() { + assertThrows(NullPointerException.class, () -> helper.createLiteralValuePattern(null, 42)); + } + + @Test + public void testCreateLiteralValuePatternWithNameContainingWhitespace() { + assertThrows(IllegalArgumentException.class, () -> helper.createLiteralValuePattern("Contains whitespace", 42)); + } + + @Test + public void testCreateLiteralValuePatternWithNullDefaultValue() { + testCreateLiteralValuePattern(null, RETURNED_PATTERN, RETURNED_PATTERN); + } + + @Test + public void testCreateLiteralValuePatternWithFalse() { + testCreateLiteralValuePattern(false, RETURNED_PATTERN, RETURNED_PATTERN); + } + + @Test + public void testCreateLiteralValuePatternWithTrue() { + testCreateLiteralValuePattern(true, RETURNED_PATTERN, RETURNED_PATTERN); + } + + @Test + public void testCreateLiteralValuePatternWithNumber() { + testCreateLiteralValuePattern(42, RETURNED_PATTERN, RETURNED_PATTERN); + } + + @Test + public void testCreateLiteralValuePatternWithString() { + testCreateLiteralValuePattern("Supercalifragilisticexpialidocious", RETURNED_PATTERN, "\"" + RETURNED_PATTERN + "\""); + } + + /** + * Test createLiteralValuePattern() using a mock createSimpleEnumPattern(). + * + * @param defaultValue default value to supply to createLiteralValuePattern(), may be {@code null} + * @param pattern pattern to return from createSimpleEnumPattern(), may be {@code null} + * @param expectedResult expected return value from createLiteralValuePattern(), may be {@code null} + */ + public void testCreateLiteralValuePattern(final Object defaultValue, final String pattern, final String expectedResult) { + + // ARRANGE + final TemplateProposalProviderHelper helperSpy = spy(helper); + when(helperSpy.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, defaultValue)). + thenReturn(pattern); + + // ACT + final String actualResult = helperSpy.createLiteralValuePattern(VARIABLE_NAME, defaultValue); + + // ASSERT + verify(helperSpy).createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, defaultValue); + assertEquals(expectedResult, actualResult, "Expected result"); + } + + @Test + public void testCreateTemplateVariablePatternWithNullType() { + assertThrows(NullPointerException.class, () -> helper.createTemplateVariablePattern(null, VARIABLE_NAME)); + } + + @Test + public void testCreateTemplateVariablePatternWithTypeContainingWhitespace() { + assertThrows(IllegalArgumentException.class, () -> helper.createTemplateVariablePattern("Contains whitespace", VARIABLE_NAME)); + } + + @Test + public void testCreateTemplateVariablePatternWithNullName() { + assertThrows(NullPointerException.class, () -> helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, null)); + } + + public void testCreateTemplateVariablePatternWithNameContainingWhitespace() { + assertThrows(IllegalArgumentException.class, () -> helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, "Contains whitespace")); + } + + @Test + public void testCreateTemplateVariablePatternWithNull() { + assertThrows(NullPointerException.class, () -> helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, (Object[]) null)); + } + + @Test + public void testCreateTemplateVariablePatternWithNoValues() { + assertThrows(IllegalArgumentException.class, () -> helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME)); + } + + @Test + public void testCreateTemplateVariablePatternWithFalse() { + testCreateTemplateVariablePattern(new Object[]{false}, "false", "false", "true"); + } + + @Test + public void testCreateTemplateVariablePatternWithTrue() { + testCreateTemplateVariablePattern(new Object[]{true}, "true", "true", "false"); + } + + @Test + public void testCreateTemplateVariablePatternWithMultipleBooleans() { + testCreateTemplateVariablePattern(new Object[]{false, false, true}, "false", "false", "false", "true"); + } + + @Test + public void testCreateTemplateVariablePatternWithNumber() { + testCreateTemplateVariablePattern(new Object[]{42}, "42", "42"); + } + + @Test + public void testCreateTemplateVariablePatternWithMultipleNumbers() { + testCreateTemplateVariablePattern(new Object[]{1297, 1314}, "1297", "1297", "1314"); + } + + @Test + public void testCreateTemplateVariablePatternWithString() { + testCreateTemplateVariablePattern(new Object[]{"Supercalifragilisticexpialidocious"}, "Supercalifragilisticexpialidocious", + "Supercalifragilisticexpialidocious"); + } + + @Test + public void testCreateTemplateVariablePatternWithEmptyString() { + testCreateTemplateVariablePattern(new Object[]{""}, "", ""); + } + + @Test + public void testCreateTemplateVariablePatternWithStringContainingWhitespace() { + testCreateTemplateVariablePattern(new Object[]{"Lorem ipsum dolor sit amet"}, "Lorem ipsum dolor sit amet", + "Lorem ipsum dolor sit amet"); + } + + @Test + public void testCreateTemplateVariablePatternWithStringContainingSingleQuotes() { + testCreateTemplateVariablePattern(new Object[]{"Apostrophe's"}, "Apostrophe's", "Apostrophe's"); + } + + @Test + public void testCreateTemplateVariablePatternWithStringContainingDoubleQuotes() { + testCreateTemplateVariablePattern(new Object[]{"CHAIN \"CHUCKIE\""}, "CHAIN \\\"CHUCKIE\\\"", + "CHAIN \\\"CHUCKIE\\\""); + } + + @Test + public void testCreateTemplateVariablePatternWithStringContainingWhitespaceAndSingleAndDoubleQuotes() { + testCreateTemplateVariablePattern( + new Object[]{"\"Whoever thinks of going to bed before twelve o'clock is a scoundrel\" - Dr Johnson"}, + "\\\"Whoever thinks of going to bed before twelve o'clock is a scoundrel\\\" - Dr Johnson", + "\\\"Whoever thinks of going to bed before twelve o'clock is a scoundrel\\\" - Dr Johnson"); + } + + @Test + public void testCreateTemplateVariablePatternWithMultipleStrings() { + testCreateTemplateVariablePattern( + new Object[]{"Twas brillig and the slithy toves", "Did gyre and gimble in the wabe", "All mimsy were the borogroves", + "And the mome raths outgrabe"}, "Twas brillig and the slithy toves", + "Twas brillig and the slithy toves", "Did gyre and gimble in the wabe", "All mimsy were the borogroves", + "And the mome raths outgrabe"); + } + + /** + * Test createTemplateVariablePattern(). + * + * @param values values, may be {@code null} + * @param expectedResult expected result of applying a template containing the pattern, may be {@code null} + * @param expectedValues expected values offered by a template containing the pattern, may be {@code null} + */ + private void testCreateTemplateVariablePattern(final Object[] values, final String expectedResult, final String... expectedValues) { + try { + // ACT + final String pattern = helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, values); + + // Try using the pattern in a template + final Template template = new Template(TEMPLATE_NAME, TEMPLATE_DESCRIPTION, CONTEXT_TYPE_ID, pattern, IS_AUTO_INSERTABLE); + final TemplateBuffer templateBuffer = templateContext.evaluate(template); + final String actualResult = templateBuffer.getString(); + assertEquals(1, templateBuffer.getVariables().length); + final String[] actualValues = templateBuffer.getVariables()[0].getValues(); + + // ASSERT + assertEquals(expectedResult, actualResult, "Expected result"); + assertArrayEquals(expectedValues, actualValues, "Expected values"); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + // CHECKSTYLE:CONSTANTS-ON + +} diff --git a/com.avaloq.tools.ddk.xtext.ui.test/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelperTest.xtend b/com.avaloq.tools.ddk.xtext.ui.test/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelperTest.xtend deleted file mode 100644 index dd2add1d7c..0000000000 --- a/com.avaloq.tools.ddk.xtext.ui.test/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelperTest.xtend +++ /dev/null @@ -1,265 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Avaloq Group AG - initial API and implementation - *******************************************************************************/ - -package com.avaloq.tools.ddk.xtext.ui.templates - -import com.google.inject.Guice -import org.eclipse.jface.text.IDocument -import org.eclipse.jface.text.IRegion -import org.eclipse.jface.text.Position -import org.eclipse.jface.text.templates.Template -import org.eclipse.xtext.XtextRuntimeModule -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.eclipse.xtext.ui.editor.templates.XtextTemplateContext -import org.eclipse.xtext.ui.editor.templates.XtextTemplateContextType -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith - -import static org.junit.jupiter.api.Assertions.assertThrows -import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertArrayEquals -import static org.mockito.ArgumentMatchers.anyInt -import static org.mockito.Mockito.mock -import static org.mockito.Mockito.spy -import static org.mockito.Mockito.verify -import static org.mockito.Mockito.when -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.AfterAll - -@ExtendWith(InjectionExtension) -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class TemplateProposalProviderHelperTest { - - static val SIMPLE_ENUM_VARIABLE_TYPE = new SimpleEnumTemplateVariableResolver().type - static val VARIABLE_NAME = "variableName" - static val RETURNED_PATTERN = "Pattern returned from createSimpleEnumPattern()" - - static val TEMPLATE_NAME = "TemplateName" - static val TEMPLATE_DESCRIPTION = "Template description" - static val CONTEXT_TYPE_ID = "context.type.ID" - static val IS_AUTO_INSERTABLE = true - - static var IDocument mockDocument - static var Position mockPosition - static var IRegion mockRegion - - static var XtextTemplateContext templateContext - - static var TemplateProposalProviderHelper helper - - @BeforeAll - def void beforeAll() { - mockDocument = mock(IDocument) - mockPosition = mock(Position) - mockRegion = mock(IRegion) - - val templateContextType = new XtextTemplateContextType - templateContextType.addResolver(new SimpleEnumTemplateVariableResolver) - templateContext = new XtextTemplateContext(templateContextType, mockDocument, mockPosition, null, null) - - helper = Guice.createInjector(new XtextRuntimeModule).getInstance(TemplateProposalProviderHelper) - - when(mockDocument.getLineInformationOfOffset(anyInt)).thenReturn(mockRegion) - when(mockDocument.get(anyInt, anyInt)).thenReturn("") - } - - @AfterAll - def void afterAll() { - mockDocument = null - mockPosition = null - mockRegion = null - - templateContext = null - - helper = null - } - - @Test - def void testCreateLiteralValuePatternWithNullName() { - assertThrows(NullPointerException, [| helper.createLiteralValuePattern(null, 42)]) - } - - @Test - def void testCreateLiteralValuePatternWithNameContainingWhitespace() { - assertThrows(IllegalArgumentException, [| helper.createLiteralValuePattern("Contains whitespace", 42)]) - } - - @Test - def void testCreateLiteralValuePatternWithNullDefaultValue() { - testCreateLiteralValuePattern(null, RETURNED_PATTERN, RETURNED_PATTERN) - } - - @Test - def void testCreateLiteralValuePatternWithFalse() { - testCreateLiteralValuePattern(false, RETURNED_PATTERN, RETURNED_PATTERN) - } - - @Test - def void testCreateLiteralValuePatternWithTrue() { - testCreateLiteralValuePattern(true, RETURNED_PATTERN, RETURNED_PATTERN) - } - - @Test - def void testCreateLiteralValuePatternWithNumber() { - testCreateLiteralValuePattern(42, RETURNED_PATTERN, RETURNED_PATTERN) - } - - @Test - def void testCreateLiteralValuePatternWithString() { - testCreateLiteralValuePattern("Supercalifragilisticexpialidocious", RETURNED_PATTERN, '''"«RETURNED_PATTERN»"''') - } - - /** - * Test createLiteralValuePattern() using a mock createSimpleEnumPattern(). - * - * @param defaultValue default value to supply to createLiteralValuePattern(), may be {@code null} - * @param pattern pattern to return from createSimpleEnumPattern(), may be {@code null} - * @param expectedResult expected return value from createLiteralValuePattern(), may be {@code null} - */ - def void testCreateLiteralValuePattern(Object defaultValue, String pattern, String expectedResult) { - - // ARRANGE - val helperSpy = spy(helper) - when(helperSpy.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, defaultValue)). - thenReturn(pattern) - - // ACT - val actualResult = helperSpy.createLiteralValuePattern(VARIABLE_NAME, defaultValue) - - // ASSERT - verify(helperSpy).createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, defaultValue) - assertEquals(expectedResult, actualResult, "Expected result") - } - - @Test - def void testCreateTemplateVariablePatternWithNullType() { - assertThrows(NullPointerException, [| helper.createTemplateVariablePattern(null, VARIABLE_NAME)]) - } - - @Test - def void testCreateTemplateVariablePatternWithTypeContainingWhitespace() { - assertThrows(IllegalArgumentException, [|helper.createTemplateVariablePattern("Contains whitespace", VARIABLE_NAME)]) - } - - @Test - def void testCreateTemplateVariablePatternWithNullName() { - assertThrows(NullPointerException, [| helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, null)]) - } - - def void testCreateTemplateVariablePatternWithNameContainingWhitespace() { - assertThrows(IllegalArgumentException, [| helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, "Contains whitespace")]) - } - - @Test - def void testCreateTemplateVariablePatternWithNull() { - assertThrows(NullPointerException, [| helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, null)]) - } - - @Test - def void testCreateTemplateVariablePatternWithNoValues() { - assertThrows(IllegalArgumentException, [| helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME)]) - } - - @Test - def void testCreateTemplateVariablePatternWithFalse() { - testCreateTemplateVariablePattern(#[false], "false", #["false", "true"]) - } - - @Test - def void testCreateTemplateVariablePatternWithTrue() { - testCreateTemplateVariablePattern(#[true], "true", #["true", "false"]) - } - - @Test - def void testCreateTemplateVariablePatternWithMultipleBooleans() { - testCreateTemplateVariablePattern(#[false, false, true], "false", #["false", "false", "true"]) - } - - @Test - def void testCreateTemplateVariablePatternWithNumber() { - testCreateTemplateVariablePattern(#[42], "42", #["42"]) - } - - @Test - def void testCreateTemplateVariablePatternWithMultipleNumbers() { - testCreateTemplateVariablePattern(#[1297, 1314], "1297", #["1297", "1314"]) - } - - @Test - def void testCreateTemplateVariablePatternWithString() { - testCreateTemplateVariablePattern(#["Supercalifragilisticexpialidocious"], "Supercalifragilisticexpialidocious", - #["Supercalifragilisticexpialidocious"]) - } - - @Test - def void testCreateTemplateVariablePatternWithEmptyString() { - testCreateTemplateVariablePattern(#[""], "", #[""]) - } - - @Test - def void testCreateTemplateVariablePatternWithStringContainingWhitespace() { - testCreateTemplateVariablePattern(#["Lorem ipsum dolor sit amet"], "Lorem ipsum dolor sit amet", - #["Lorem ipsum dolor sit amet"]) - } - - @Test - def void testCreateTemplateVariablePatternWithStringContainingSingleQuotes() { - testCreateTemplateVariablePattern(#["Apostrophe's"], "Apostrophe's", #["Apostrophe's"]) - } - - @Test - def void testCreateTemplateVariablePatternWithStringContainingDoubleQuotes() { - testCreateTemplateVariablePattern(#['''CHAIN "CHUCKIE"'''.toString], '''CHAIN \"CHUCKIE\"''', - #['''CHAIN \"CHUCKIE\"''']) - } - - @Test - def void testCreateTemplateVariablePatternWithStringContainingWhitespaceAndSingleAndDoubleQuotes() { - testCreateTemplateVariablePattern( - #['''"Whoever thinks of going to bed before twelve o'clock is a scoundrel" - Dr Johnson'''. - toString], '''\"Whoever thinks of going to bed before twelve o'clock is a scoundrel\" - Dr Johnson''', - #['''\"Whoever thinks of going to bed before twelve o'clock is a scoundrel\" - Dr Johnson''']) - } - - @Test - def void testCreateTemplateVariablePatternWithMultipleStrings() { - testCreateTemplateVariablePattern( - #["Twas brillig and the slithy toves", "Did gyre and gimble in the wabe", "All mimsy were the borogroves", - "And the mome raths outgrabe"], "Twas brillig and the slithy toves", - #["Twas brillig and the slithy toves", "Did gyre and gimble in the wabe", "All mimsy were the borogroves", - "And the mome raths outgrabe"]) - } - - /** - * Test createTemplateVariablePattern(). - * - * @param values values, may be {@code null} - * @param expectedResult expected result of applying a template containing the pattern, may be {@code null} - * @param expectedValues expected values offered by a template containing the pattern, may be {@code null} - */ - private def void testCreateTemplateVariablePattern(Object[] values, String expectedResult, String[] expectedValues) { - // ACT - val pattern = helper.createTemplateVariablePattern(SIMPLE_ENUM_VARIABLE_TYPE, VARIABLE_NAME, values) - - // Try using the pattern in a template - val template = new Template(TEMPLATE_NAME, TEMPLATE_DESCRIPTION, CONTEXT_TYPE_ID, pattern, IS_AUTO_INSERTABLE) - val templateBuffer = templateContext.evaluate(template) - val actualResult = templateBuffer.string - assertEquals(1, templateBuffer.variables.length) - val actualValues = templateBuffer.variables.get(0).values - - // ASSERT - assertEquals(expectedResult, actualResult, "Expected result") - assertArrayEquals(expectedValues, actualValues, "Expected values") - } - -} diff --git a/com.avaloq.tools.ddk.xtext.ui.test/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.ui.test/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.ui/.classpath b/com.avaloq.tools.ddk.xtext.ui/.classpath index 7b0f1225c1..58a21f1484 100644 --- a/com.avaloq.tools.ddk.xtext.ui/.classpath +++ b/com.avaloq.tools.ddk.xtext.ui/.classpath @@ -1,7 +1,6 @@ - diff --git a/com.avaloq.tools.ddk.xtext.ui/build.properties b/com.avaloq.tools.ddk.xtext.ui/build.properties index 6d2b56e648..3466223bf3 100644 --- a/com.avaloq.tools.ddk.xtext.ui/build.properties +++ b/com.avaloq.tools.ddk.xtext.ui/build.properties @@ -1,5 +1,4 @@ -source.. = src/,\ - xtend-gen/ +source.. = src/ bin.includes = META-INF/,\ icons/,\ schema/,\ diff --git a/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/labeling/AbstractLabelProvider.java b/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/labeling/AbstractLabelProvider.java index a14651d210..59f719d124 100644 --- a/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/labeling/AbstractLabelProvider.java +++ b/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/labeling/AbstractLabelProvider.java @@ -230,9 +230,7 @@ protected Object getStyledLabel(final EObject modelElement, final EStructuralFea } } if (valueString != null && valueString.length() > MAX_FEATURE_VALUE_LENGTH) { - StringBuilder stringBuilder = new StringBuilder(valueString.substring(0, MAX_FEATURE_VALUE_LENGTH - CONTINUED.length())); - stringBuilder.append(CONTINUED); - valueString = stringBuilder.toString(); + valueString = valueString.substring(0, MAX_FEATURE_VALUE_LENGTH - CONTINUED.length()) + CONTINUED; } } return assignmentStyledString(name, valueString); diff --git a/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelper.java b/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelper.java new file mode 100644 index 0000000000..824c4d4e43 --- /dev/null +++ b/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelper.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2016 Avaloq Group AG and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * Contributors: + * Avaloq Group AG - initial API and implementation + */ +package com.avaloq.tools.ddk.xtext.ui.templates; + +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.google.inject.Singleton; +import org.apache.commons.text.StringEscapeUtils; +import org.apache.commons.lang3.Validate; +import org.eclipse.jface.text.templates.Template; +import org.eclipse.xtext.ui.editor.contentassist.ITemplateProposalProvider; + +/** + * Helper methods for {@link ITemplateProposalProvider} implementations. + */ +@Singleton +@SuppressWarnings("nls") +public class TemplateProposalProviderHelper { + + private static final String SIMPLE_ENUM_TYPE = new SimpleEnumTemplateVariableResolver().getType(); + + /** + * Create a literal value pattern, including quotes if necessary, for a {@link Template}. + * + * @param name the name of the variable, may not be {@code null} nor contain whitespace + * @param defaultValue default value, may be {@code null} + * @return pattern, never {@code null} + * @throws NullPointerException if name is null + * @throws IllegalArgumentException if name contains whitespace + */ + public String createLiteralValuePattern(final String name, final Object defaultValue) throws NullPointerException, IllegalArgumentException { + final String pattern = createTemplateVariablePattern(SIMPLE_ENUM_TYPE, name, defaultValue); + + if (defaultValue instanceof String) { + // Surround pattern with quotes + return "\"" + pattern + "\""; + } else { + return pattern; + } + } + + /** + * Create a variable pattern for a {@link Template}. + * + * @param type the type of the variable, may not be {@code null} nor contain whitespace + * @param name the name of the variable, may not be {@code null} nor contain whitespace + * @param values the values available at this variable, may not be {@code null} nor empty + * @return pattern, never {@code null} + * @throws NullPointerException if type, name or values is null + * @throws IllegalArgumentException if type or name contains whitespace or values is empty + */ + public String createTemplateVariablePattern(final String type, final String name, final Object... values) throws NullPointerException, IllegalArgumentException { + Objects.requireNonNull(type); + Objects.requireNonNull(name); + Objects.requireNonNull(values); + Validate.isTrue(!type.chars().anyMatch(Character::isWhitespace)); + Validate.isTrue(!name.chars().anyMatch(Character::isWhitespace)); + Validate.notEmpty(values); + + final Object[] useValues; + if (values.length == 1 && values[0] instanceof Boolean) { + // Offer both false and true as dropdown options + useValues = new Object[]{values[0], !((Boolean) values[0])}; + } else { + useValues = values; + } + + final String sanitisedValues = Arrays.stream(useValues) + .map(it -> + // Escape double-quotes to comply with string grammar. + // Double-up single-quotes to comply with template pattern grammar. + // Wrap each value in single quotes. + "'" + StringEscapeUtils.escapeJava(String.valueOf(it)).replace("'", "''") + "'") + .collect(Collectors.joining(", ")); + + return "${" + name + ":" + type + "(" + sanitisedValues + ")}"; + } + +} diff --git a/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelper.xtend b/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelper.xtend deleted file mode 100644 index 3f965f1487..0000000000 --- a/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/templates/TemplateProposalProviderHelper.xtend +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2016 Avaloq Group AG and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * Contributors: - * Avaloq Group AG - initial API and implementation - */ -package com.avaloq.tools.ddk.xtext.ui.templates - -import com.google.inject.Singleton -import java.util.Objects -import org.apache.commons.text.StringEscapeUtils -import org.apache.commons.lang3.Validate -import org.eclipse.jface.text.templates.Template -import org.eclipse.xtext.ui.editor.contentassist.ITemplateProposalProvider - -/** - * Helper methods for {@link ITemplateProposalProvider} implementations. - */ -@Singleton -class TemplateProposalProviderHelper { - - static val SIMPLE_ENUM_TYPE = new SimpleEnumTemplateVariableResolver().type - - /** - * Create a literal value pattern, including quotes if necessary, for a {@link Template}. - * - * @param name the name of the variable, may not be {@code null} nor contain whitespace - * @param defaultValue default value, may be {@code null} - * @return pattern, never {@code null} - * @throws {@link NullPointerException} if name is null - * @throws {@link IllegalArgumentException} if name contains whitespace - */ - def String createLiteralValuePattern(String name, Object defaultValue) throws NullPointerException, IllegalArgumentException { - val pattern = createTemplateVariablePattern(SIMPLE_ENUM_TYPE, name, defaultValue) - - return if (defaultValue instanceof String) { - // Surround pattern with quotes - '''"«pattern»"''' - } else { - pattern - } - } - - /** - * Create a variable pattern for a {@link Template}. - * - * @param type the type of the variable, may not be {@code null} nor contain whitespace - * @param name the name of the variable, may not be {@code null} nor contain whitespace - * @param values the values available at this variable, may not be {@code null} nor empty - * @return pattern, never {@code null} - * @throws {@link NullPointerException} if type, name or values is null - * @throws {@link IllegalArgumentException} if type or name contains whitespace or values is empty - */ - def String createTemplateVariablePattern(String type, String name, Object... values) throws NullPointerException, IllegalArgumentException { - Objects.requireNonNull(type); - Objects.requireNonNull(name); - Objects.requireNonNull(values); - Validate.isTrue(!type.chars().anyMatch[Character.isWhitespace(it)]); - Validate.isTrue(!name.chars().anyMatch[Character.isWhitespace(it)]); - Validate.notEmpty(values); - - val Object[] useValues = if (values.length == 1 && values.get(0) instanceof Boolean) { - // Offer both false and true as dropdown options - #[values.get(0), !(values.get(0) as Boolean)] - } else { - values - } - - val sanitisedValues = useValues.map [ - // Escape double-quotes to comply with string grammar. - // Double-up single-quotes to comply with template pattern grammar. - // Wrap each value in single quotes. - "'" + StringEscapeUtils::escapeJava(String.valueOf(it)).replace("'", "''") + "'" - ] - - return '''${«name»:«type»(«String.join(", ", sanitisedValues)»)}''' - } - -} diff --git a/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/validation/AbstractValidElementBase.java b/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/validation/AbstractValidElementBase.java index 692c7ebe58..574f288bf9 100644 --- a/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/validation/AbstractValidElementBase.java +++ b/com.avaloq.tools.ddk.xtext.ui/src/com/avaloq/tools/ddk/xtext/ui/validation/AbstractValidElementBase.java @@ -109,11 +109,7 @@ public IConfigurationElement getConfigurationElement() { @Override public String toString() { - StringBuilder b = new StringBuilder(this.getClass().getSimpleName()); - b.append("(\""); //$NON-NLS-1$ - b.append(getElementTypeName()); - b.append("\")"); //$NON-NLS-1$ - return b.toString(); + return this.getClass().getSimpleName() + "(\"" + getElementTypeName() + "\")"; //$NON-NLS-1$ //$NON-NLS-2$ } /** diff --git a/com.avaloq.tools.ddk.xtext.ui/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.ui/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext.valid.ide/.classpath b/com.avaloq.tools.ddk.xtext.valid.ide/.classpath index fc183f78b3..2de44c4bb9 100644 --- a/com.avaloq.tools.ddk.xtext.valid.ide/.classpath +++ b/com.avaloq.tools.ddk.xtext.valid.ide/.classpath @@ -6,11 +6,6 @@ - - - - - diff --git a/com.avaloq.tools.ddk.xtext.valid.ide/.project b/com.avaloq.tools.ddk.xtext.valid.ide/.project index e6cfc0cb64..95911a5396 100644 --- a/com.avaloq.tools.ddk.xtext.valid.ide/.project +++ b/com.avaloq.tools.ddk.xtext.valid.ide/.project @@ -65,35 +65,5 @@ 1 PARENT-1-PROJECT_LOC/ddk-configuration/.pmd - - .settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/edu.umd.cs.findbugs.plugin.eclipse.prefs - - - .settings/org.eclipse.core.resources.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.resources.prefs - - - .settings/org.eclipse.core.runtime.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.core.runtime.prefs - - - .settings/org.eclipse.jdt.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.core.prefs - - - .settings/org.eclipse.jdt.ui.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.jdt.ui.prefs - - - .settings/org.eclipse.pde.core.prefs - 1 - PARENT-1-PROJECT_LOC/ddk-configuration/.settings/org.eclipse.pde.core.prefs - diff --git a/com.avaloq.tools.ddk.xtext.valid.ide/build.properties b/com.avaloq.tools.ddk.xtext.valid.ide/build.properties index 3f5513de7b..2d6ac57da2 100644 --- a/com.avaloq.tools.ddk.xtext.valid.ide/build.properties +++ b/com.avaloq.tools.ddk.xtext.valid.ide/build.properties @@ -1,5 +1,4 @@ source.. = src/,\ - src-gen/,\ - xtend-gen/ + src-gen/ bin.includes = META-INF/,\ . diff --git a/com.avaloq.tools.ddk.xtext.valid.ide/xtend-gen/.gitignore b/com.avaloq.tools.ddk.xtext.valid.ide/xtend-gen/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/FixedCopiedResourceDescription.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/FixedCopiedResourceDescription.java index 200e85debd..56e715a6ef 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/FixedCopiedResourceDescription.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/FixedCopiedResourceDescription.java @@ -105,15 +105,7 @@ public Iterable getReferenceDescriptions() { @Override public String toString() { - StringBuilder result = new StringBuilder(getClass().getName()); - result.append('@'); - result.append(Integer.toHexString(hashCode())); - - result.append(" (URI: "); //$NON-NLS-1$ - result.append(uri); - result.append(')'); - - return result.toString(); + return String.format("%s@%s (URI: %s)", getClass().getName(), Integer.toHexString(hashCode()), uri); //$NON-NLS-1$ } @Override diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java index 5c95737318..d62a837cff 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/DirectLinkingResourceStorageLoadable.java @@ -108,12 +108,9 @@ protected void loadFeatureValue(final InternalEObject internalEObject, final ESt super.loadFeatureValue(internalEObject, eStructuralFeatureData); // CHECKSTYLE:OFF } catch (Exception e) { - StringBuilder infoMessage = new StringBuilder(100); // CHECKSTYLE:ON - infoMessage.append("Failed to load feature's value. Owner: ").append(internalEObject.eClass()); //$NON-NLS-1$ - if (eStructuralFeatureData.eStructuralFeature != null) { - infoMessage.append(", feature name: ").append(eStructuralFeatureData.eStructuralFeature.getName()); //$NON-NLS-1$ - } + String infoMessage = "Failed to load feature's value. Owner: " + internalEObject.eClass() //$NON-NLS-1$ + + (eStructuralFeatureData.eStructuralFeature != null ? ", feature name: " + eStructuralFeatureData.eStructuralFeature.getName() : ""); //$NON-NLS-1$ //$NON-NLS-2$ LOG.info(infoMessage); throw e; } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java index d1d0b56992..c2f226049c 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/resource/persistence/ProxyCompositeNode.java @@ -178,16 +178,11 @@ private CompositeNode delegate() { * @return the string */ private String toString(final EObject eObject) { - StringBuilder result = new StringBuilder(eObject.getClass().getName()); - result.append('@'); - result.append(Integer.toHexString(hashCode())); - + String result = String.format("%s@%s", eObject.getClass().getName(), Integer.toHexString(hashCode())); //$NON-NLS-1$ if (eObject.eIsProxy() && eObject instanceof InternalEObject internal) { - result.append(" (eProxyURI: "); //$NON-NLS-1$ - result.append(internal.eProxyURI()); - result.append(')'); + result += " (eProxyURI: " + internal.eProxyURI() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } - return result.toString(); + return result; } @Override diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/AbstractRecursiveScope.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/AbstractRecursiveScope.java index b826607984..89c8ef437d 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/AbstractRecursiveScope.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/AbstractRecursiveScope.java @@ -229,20 +229,12 @@ public String getId() { @SuppressWarnings("nls") @Override public String toString() { - final StringBuilder result = new StringBuilder(getClass().getName()); - result.append('@'); - result.append(Integer.toHexString(hashCode())); - - result.append(" (id: "); - result.append(getId()); - result.append(')'); - + String result = String.format("%s@%s (id: %s)", getClass().getName(), Integer.toHexString(hashCode()), getId()); final IScope outerScope = getParent(); if (outerScope != IScope.NULLSCOPE) { - result.append("\n >> "); - result.append(outerScope.toString().replaceAll("\\\n", "\n ")); + result += "\n >> " + outerScope.toString().replaceAll("\\\n", "\n "); } - return result.toString(); + return result; } } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ContainerBasedScope.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ContainerBasedScope.java index d2b719e15a..9fcdedfdd4 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ContainerBasedScope.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ContainerBasedScope.java @@ -135,26 +135,11 @@ protected Iterable getAllLocalElements() { @SuppressWarnings("nls") @Override public String toString() { - final StringBuilder result = new StringBuilder(getClass().getName()); - result.append('@'); - result.append(Integer.toHexString(hashCode())); - - result.append(" (id: "); - result.append(getId()); - - result.append(", query: "); - result.append(criteria); - - result.append(", container: "); - result.append(container); - - result.append(')'); - + String result = String.format("%s@%s (id: %s, query: %s, container: %s)", getClass().getName(), Integer.toHexString(hashCode()), getId(), criteria, container); final IScope parent = getParent(); if (parent != IScope.NULLSCOPE) { - result.append("\n >> "); - result.append(parent.toString().replaceAll("\\\n", "\n ")); + result += "\n >> " + parent.toString().replaceAll("\\\n", "\n "); } - return result.toString(); + return result; } } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/DelegatingScope.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/DelegatingScope.java index da2da8889b..3b16992f1a 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/DelegatingScope.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/DelegatingScope.java @@ -267,26 +267,13 @@ protected Iterable getLocalElementsByName(final QualifiedNa @SuppressWarnings("nls") @Override public String toString() { - final StringBuilder result = new StringBuilder(getClass().getName()); - result.append('@'); - result.append(Integer.toHexString(hashCode())); - - result.append(" (id: "); - result.append(getId()); - final Iterable delegateScopes = getDelegates(); - if (delegateScopes != null && !Iterables.isEmpty(delegateScopes)) { - result.append(", delegates: "); - result.append(Iterables.toString(delegateScopes)); - } - result.append(')'); - + String delegatesSuffix = delegateScopes != null && !Iterables.isEmpty(delegateScopes) ? ", delegates: " + Iterables.toString(delegateScopes) : ""; + String result = String.format("%s@%s (id: %s%s)", getClass().getName(), Integer.toHexString(hashCode()), getId(), delegatesSuffix); final IScope outerScope = getParent(); if (outerScope != IScope.NULLSCOPE) { - result.append("\n >> "); - result.append(outerScope.toString().replaceAll("\\\n", "\n ")); + result += "\n >> " + outerScope.toString().replaceAll("\\\n", "\n "); } - - return result.toString(); + return result; } } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/PrefixedContainerBasedScope.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/PrefixedContainerBasedScope.java index 04e8def8bb..cf8f2388e8 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/PrefixedContainerBasedScope.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/PrefixedContainerBasedScope.java @@ -134,29 +134,11 @@ public IEObjectDescription apply(final IEObjectDescription input) { @SuppressWarnings("nls") @Override public String toString() { - final StringBuilder result = new StringBuilder(getClass().getName()); - result.append('@'); - result.append(Integer.toHexString(hashCode())); - - result.append(" (id: "); - result.append(getId()); - - result.append(", prefix: "); - result.append(prefix); - - result.append(", query: "); - result.append(criteria); - - result.append(", container: "); - result.append(container); - - result.append(')'); - + String result = String.format("%s@%s (id: %s, prefix: %s, query: %s, container: %s)", getClass().getName(), Integer.toHexString(hashCode()), getId(), prefix, criteria, container); final IScope parent = getParent(); if (parent != IScope.NULLSCOPE) { - result.append("\n >> "); - result.append(parent.toString().replaceAll("\\\n", "\n ")); + result += "\n >> " + parent.toString().replaceAll("\\\n", "\n "); } - return result.toString(); + return result; } } diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ScopeTrace.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ScopeTrace.java index 86f478da21..e50a05070b 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ScopeTrace.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/scoping/ScopeTrace.java @@ -10,7 +10,6 @@ *******************************************************************************/ package com.avaloq.tools.ddk.xtext.scoping; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; @@ -47,20 +46,7 @@ public List getFullTrace() { @SuppressWarnings("nls") @Override public String toString() { - final StringBuilder builder = new StringBuilder(getClass().getName()); - builder.append('@'); - builder.append(Integer.toHexString(hashCode())); - builder.append(" ["); - - for (final Iterator i = elements.iterator(); i.hasNext();) { - builder.append(i.next()); - if (i.hasNext()) { - builder.append(" >> "); - } - } - - builder.append(']'); - return builder.toString(); + return String.format("%s@%s [%s]", getClass().getName(), Integer.toHexString(hashCode()), String.join(" >> ", elements)); } /** diff --git a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/util/EObjectUtil.java b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/util/EObjectUtil.java index 145d96009e..397a9ee47e 100644 --- a/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/util/EObjectUtil.java +++ b/com.avaloq.tools.ddk.xtext/src/com/avaloq/tools/ddk/xtext/util/EObjectUtil.java @@ -242,13 +242,8 @@ public static String getFileLocation(final EObject object) { // CHECKSTYLE:CHECK-OFF MagicNumber String path = uri.isPlatform() ? '/' + String.join("/", uri.segmentsList().subList(3, uri.segmentCount())) : uri.path(); //$NON-NLS-1$ // CHECKSTYLE:CHECK-ON MagicNumber - StringBuilder result = new StringBuilder(path); final ICompositeNode node = NodeModelUtils.getNode(object); - if (node != null) { - result.append(':').append(node.getStartLine()); - } - - return result.toString(); + return node != null ? path + ":" + node.getStartLine() : path; //$NON-NLS-1$ } } diff --git a/ddk-configuration/.checkstyle b/ddk-configuration/.checkstyle index 8248743687..ab5c6246d0 100644 --- a/ddk-configuration/.checkstyle +++ b/ddk-configuration/.checkstyle @@ -10,7 +10,6 @@ - diff --git a/ddk-configuration/pmd/ruleset.xml b/ddk-configuration/pmd/ruleset.xml index cdc4596bc5..e9f4b47520 100644 --- a/ddk-configuration/pmd/ruleset.xml +++ b/ddk-configuration/pmd/ruleset.xml @@ -9,7 +9,6 @@ .*/src-gen/.* .*/src-model/.* - .*/xtend-gen/.* .*/ddk/xtext/test/ui/quickfix/AbstractQuickFixTest.* diff --git a/ddk-parent/pom.xml b/ddk-parent/pom.xml index fcdc121434..081e6f4acb 100644 --- a/ddk-parent/pom.xml +++ b/ddk-parent/pom.xml @@ -55,7 +55,6 @@ 3.28.0 7.23.0 5.0.2 - 2.42.0 @@ -223,25 +222,6 @@ - - org.eclipse.xtext - xtend-maven-plugin - ${xtend.version} - - - xtend-generate - generate-sources - - compile - testCompile - - - ${basedir}/xtend-gen/ - ${basedir}/xtend-gen/ - - - - org.eclipse.tycho tycho-surefire-plugin @@ -312,7 +292,6 @@ ${basedir}/src-gen ${basedir}/src-model - ${basedir}/xtend-gen @@ -367,14 +346,6 @@ maven-clean-plugin ${clean.version} - - - xtend-gen - - ** - - - diff --git a/docs/xtend-migration.md b/docs/xtend-migration.md new file mode 100644 index 0000000000..1b1af28f89 --- /dev/null +++ b/docs/xtend-migration.md @@ -0,0 +1,337 @@ +# Xtend-to-Java Migration Tracker + +## Decisions + +- **Target**: Java 21 (pattern matching, switch expressions, text blocks, records) +- **Template strategy**: `StringBuilder` (pure Java, no Xtend runtime dependency) +- **No `var` keyword**: Always use explicit types (`final ExplicitType`, not `final var`) +- **Conversion prompt**: [`docs/xtend-to-java-conversion-prompt.md`](xtend-to-java-conversion-prompt.md) + +## Summary + +| Metric | Value | +|--------|-------| +| Total Xtend source files | 94 | +| Already migrated (Batch 1–9) | 94 | +| Remaining | 0 | +| Total remaining lines | 0 | +| Modules with remaining Xtend | 0 | + +--- + +## Module Overview + +| Module | Files | Lines | Status | +|--------|-------|-------|--------| +| `check.core` | 8 | ~1,848 | **DONE** (Batch 1) | +| `check.core.test` | 11 | ~1,760 | **DONE** (Batch 2–4, 9) | +| `check.test.runtime` | 1 | 22 | **DONE** (Batch 2) | +| `check.test.runtime.tests` | 3 | 202 | **DONE** (Batch 3–4) | +| `check.ui` | 2 | 113 | **DONE** (Batch 3) | +| `check.ui.test` | 1 | 200 | **DONE** (Batch 9) | +| `checkcfg.core` | 4 | 303 | **DONE** (Batch 2–4) | +| `checkcfg.core.test` | 7 | 460 | **DONE** (Batch 2–4) | +| `sample.helloworld.ui.test` | 3 | 203 | **DONE** (Batch 3–4) | +| `xtext.check.generator` | 2 | 113 | **DONE** (Batch 2–3) | +| `xtext.export` | 9 | 1,027 | **DONE** (Batch 5) | +| `xtext.export.generator` | 1 | 86 | **DONE** (Batch 4) | +| `xtext.expression` | 5 | 679 | **DONE** (Batch 2, 6) | +| `xtext.format` | 6 | 1,623 | **DONE** (Batch 2, 7) | +| `xtext.format.generator` | 1 | 239 | **DONE** (Batch 7) | +| `xtext.format.ide` | 2 | 31 | **DONE** (Batch 2) | +| `xtext.format.test` | 1 | 40 | **DONE** (Batch 3) | +| `xtext.format.ui` | 1 | 47 | **DONE** (Batch 2) | +| `xtext.generator` | 18 | 3,450 | **DONE** (Batch 2, 8) | +| `xtext.generator.test` | 1 | 200 | **DONE** (Batch 8) | +| `xtext.scope` | 4 | 852 | **DONE** (Batch 6) | +| `xtext.scope.generator` | 1 | 47 | **DONE** (Batch 4) | +| `xtext.test.core` | 2 | 221 | **DONE** (Batch 4, 6) | +| `xtext.ui` | 1 | 82 | **DONE** (Batch 6) | +| `xtext.ui.test` | 1 | 265 | **DONE** (Batch 6) | + +All module names are prefixed with `com.avaloq.tools.ddk.` (omitted for brevity). + +--- + +## Batch 1 — `check.core` (8 files) — DONE + +- [x] `CheckGeneratorConfig.xtend` (20 lines) — Trivial +- [x] `CheckGeneratorNaming.xtend` (~80 lines) — Easy +- [x] `CheckTypeComputer.xtend` (~80 lines) — Easy +- [x] `CheckScopeProvider.xtend` (~100 lines) — Easy +- [x] `CheckGenerator.xtend` (216 lines) — Hard — templates, `@Inject extension` +- [x] `CheckGeneratorExtensions.xtend` (268 lines) — Hard — dispatch, templates +- [x] `CheckFormatter.xtend` (302 lines) — Hard — dispatch +- [x] `CheckJvmModelInferrer.xtend` (647 lines) — Very Hard — dispatch, templates, extensions + +--- + +## Batch 2 — Trivial files ≤50 lines (~14 files) — DONE + +Small setup classes, empty modules, simple overrides. + +### `check.test.runtime` (1 file) +- [x] `TestLanguageGenerator.xtend` (22 lines) — Trivial — override + +### `xtext.format.ide` (2 files) +- [x] `FormatIdeModule.xtend` (11 lines) — Trivial — no complex features +- [x] `FormatIdeSetup.xtend` (20 lines) — Trivial — override + +### `xtext.format` (1 file) +- [x] `FormatStandaloneSetup.xtend` (15 lines) — Trivial — extension + +### `xtext.expression` (2 files) +- [x] `GeneratorUtilX.xtend` (29 lines) — Trivial — no complex features +- [x] `Naming.xtend` (30 lines) — Trivial — no complex features + +### `xtext.check.generator` (1 file) +- [x] `CheckValidatorFragment2.xtend` (31 lines) — Trivial — extension, !==, override + +### `checkcfg.core.test` (2 files) +- [x] `CheckCfgTestUtil.xtend` (32 lines) — Trivial — override +- [x] `CheckCfgModelUtil.xtend` (42 lines) — Trivial — templates + +### `checkcfg.core` (1 file) +- [x] `CheckCfgJvmModelInferrer.xtend` (45 lines) — Trivial — templates, extension, @Inject + +### `xtext.format.test` (1 file) +- [x] `FormatParsingTest.xtend` (40 lines) — Trivial — templates, @Inject + +### `xtext.format.ui` (1 file) +- [x] `FormatUiModule.xtend` (47 lines) — Trivial — override + +### `xtext.generator` (2 files) +- [x] `BundleVersionStripperFragment.xtend` (47 lines) — Trivial — typeof, @Accessors +- [x] `ProjectConfig.xtend` (48 lines) — Trivial — templates, @Accessors, switch + +--- + +## Batch 3 — Easy files 50–100 lines (~14 files) — DONE + +Simple test files, utilities, small production code. + +### `check.core.test` (3 files) +- [x] `BugAig830.xtend` (56 lines) — Easy — templates, @Inject +- [x] `CheckTestUtil.xtend` (72 lines) — Easy — ===, !== +- [x] `CheckScopingTest.xtend` (81 lines) — Easy — extension, typeof, @Inject + +### `check.test.runtime.tests` (2 files) +- [x] `IssueLabelTest.xtend` (56 lines) — Easy — #{, override +- [x] `CheckConfigurationIsAppliedTest.xtend` (64 lines) — Easy — extension, typeof, @Inject, override + +### `check.ui` (2 files) +- [x] `CheckNewProject.xtend` (50 lines) — Easy — templates, !== +- [x] `CheckQuickfixProvider.xtend` (63 lines) — Easy — templates + +### `checkcfg.core` (2 files) +- [x] `CheckCfgGenerator.xtend` (53 lines) — Easy — templates, typeof, @Inject, override +- [x] `ConfiguredParameterChecks.xtend` (66 lines) — Easy — templates, ===, !==, ?. + +### `checkcfg.core.test` (2 files) +- [x] `CheckCfgConfiguredParameterValidationsTest.xtend` (63 lines) — Easy — templates, extension, override +- [x] `CheckCfgTest.xtend` (63 lines) — Easy — templates, typeof, @Inject + +### `sample.helloworld.ui.test` (2 files) +- [x] `IssueLabelTest.xtend` (56 lines) — Easy — #{, override +- [x] `CheckConfigurationIsAppliedTest.xtend` (64 lines) — Easy — extension, typeof, @Inject, override + +### `xtext.check.generator` (1 file) +- [x] `CheckQuickfixProviderFragment2.xtend` (82 lines) — Easy — templates, extension, @Inject + +--- + +## Batch 4 — Medium prod + test files 80–140 lines (13 files) — DONE + +### `check.core.test` (4 files) +- [x] `ProjectBasedTests.xtend` (88 lines) — Easy — extension, typeof, @Inject, override +- [x] `IssueCodeValueTest.xtend` (104 lines) — Medium — templates, #{, switch +- [x] `CheckApiAccessValidationsTest.xtend` (61 lines) — Easy — templates, @Inject +- [x] `BasicModelTest.xtend` (116 lines) — Medium — extension, typeof, @Inject + +### `check.test.runtime.tests` (1 file) +- [x] `CheckExecutionEnvironmentProjectTest.xtend` (82 lines) — Easy — extension, typeof, @Inject, override + +### `checkcfg.core` (1 file) +- [x] `PropertiesInferenceHelper.xtend` (139 lines) — Medium — typeof, ===, !==, switch, create + +### `checkcfg.core.test` (3 files) +- [x] `CheckCfgContentAssistTest.xtend` (84 lines) — Easy — templates, extension, @Inject, override +- [x] `CheckCfgScopeProviderTest.xtend` (77 lines) — Easy — templates, ===, #[, override +- [x] `CheckCfgSyntaxTest.xtend` (99 lines) — Easy — templates, #[, override + +### `sample.helloworld.ui.test` (1 file) +- [x] `CheckExecutionEnvironmentProjectTest.xtend` (83 lines) — Easy — extension, typeof, @Inject, override + +### `xtext.export.generator` (1 file) +- [x] `ExportFragment2.xtend` (86 lines) — Medium — templates, extension, !==, @Inject, override + +### `xtext.scope.generator` (1 file) +- [x] `ScopingFragment2.xtend` (47 lines) — Trivial — extension, !==, override + +### `xtext.test.core` (1 file) +- [x] `Tag.xtend` (23 lines) — Trivial — typeof + +--- + +## Batch 5 — `xtext.export` module (9 files, 1,027 lines) — DONE + +Code generators with templates and some dispatch methods. + +- [x] `ResourceDescriptionConstantsGenerator.xtend` (54 lines) — Easy — templates, extension, @Inject +- [x] `ExportFeatureExtensionGenerator.xtend` (77 lines) — Easy — templates, extension, @Inject +- [x] `ResourceDescriptionManagerGenerator.xtend` (59 lines) — Easy — templates, extension, !==, @Inject +- [x] `ExportedNamesProviderGenerator.xtend` (96 lines) — Medium — templates, extension, !==, ?., switch +- [x] `FragmentProviderGenerator.xtend` (94 lines) — Medium — templates, extension, !==, switch +- [x] `FingerprintComputerGenerator.xtend` (134 lines) — Medium — **dispatch**, templates, extension, @Inject +- [x] `ExportGenerator.xtend` (136 lines) — Medium — templates, extension, ===, !==, @Inject, override +- [x] `ExportGeneratorX.xtend` (187 lines) — Hard — **dispatch**, extension, !==, ?., @Inject +- [x] `ResourceDescriptionStrategyGenerator.xtend` (190 lines) — Hard — templates, extension, ===, !==, @Inject + +--- + +## Batch 6 — `xtext.expression` + `xtext.scope` + remaining small files (10 files) — DONE + +### `xtext.expression` (3 files) +- [x] `ExpressionExtensionsX.xtend` (87 lines) — Medium — **dispatch**, === +- [x] `GenModelUtilX.xtend` (160 lines) — Hard — **dispatch**, extension, !==, create +- [x] `CodeGenerationX.xtend` (373 lines) — Hard — **dispatch**, extension, ===, !==, #[ + +### `xtext.scope` (4 files) +- [x] `ScopeGenerator.xtend` (83 lines) — Medium — extension, ===, !==, @Inject, override +- [x] `ScopeNameProviderGenerator.xtend` (136 lines) — Medium — **dispatch**, templates, extension, switch +- [x] `ScopeProviderX.xtend` (247 lines) — Hard — **dispatch**, extension, ===, !==, @Inject +- [x] `ScopeProviderGenerator.xtend` (386 lines) — Hard — **dispatch**, templates, extension, ===, !==, #[, switch + +### `xtext.test.core` (1 file) +- [x] `AbstractResourceDescriptionManagerTest.xtend` (198 lines) — Medium — ===, override, create + +### `xtext.ui` (1 file) +- [x] `TemplateProposalProviderHelper.xtend` (82 lines) — Medium — templates, #[ + +### `xtext.ui.test` (1 file) +- [x] `TemplateProposalProviderHelperTest.xtend` (265 lines) — Medium — templates, #[ + +--- + +## Batch 7 — `xtext.format` module (6 files, 1,847 lines) — DONE + +Includes the largest file in the project. Heavy use of dispatch, templates, create methods. + +### `xtext.format` (5 files) +- [x] `FormatRuntimeModule.xtend` (115 lines) — Medium — extension, override +- [x] `FormatGenerator.xtend` (93 lines) — Medium — **dispatch**, templates, extension, typeof, @Inject, override +- [x] `FormatScopeProvider.xtend` (258 lines) — Hard — **dispatch**, typeof, ===, !==, create +- [x] `FormatValidator.xtend` (376 lines) — Hard — ===, !==, override +- [x] **`FormatJvmModelInferrer.xtend` (766 lines) — Very Hard** — dispatch, templates, extension, typeof, ===, !==, ?., #[, switch, create + +### `xtext.format.generator` (1 file) +- [x] `FormatFragment2.xtend` (239 lines) — Hard — templates, extension, typeof, !==, ?., @Inject, override + +--- + +## Batch 8 — `xtext.generator` module (17 files, 3,555 lines) — DONE + +The largest module. Includes ANTLR grammar generators — the hardest files in the project. + +### Simple (4 files) +- [x] `PredicatesNaming.xtend` (35 lines) — Trivial — extension, @Inject +- [x] `ModelInferenceFragment2.xtend` (49 lines) — Trivial — extension, !==, override +- [x] `DefaultFragmentWithOverride.xtend` (54 lines) — Easy — ?., override, @Accessors +- [x] `BuilderIntegrationFragment2.xtend` (60 lines) — Easy — templates, extension, !==, override + +### Medium (5 files) +- [x] `ResourceFactoryFragment2.xtend` (76 lines) — Medium — templates, extension, !==, ?., @Accessors +- [x] `CompareFragment2.xtend` (99 lines) — Medium — templates, extension, !==, @Inject +- [x] `LanguageConstantsFragment2.xtend` (144 lines) — Medium — templates, extension, !==, ?., @Accessors +- [x] `FormatterFragment2.xtend` (147 lines) — Medium — templates, extension, typeof, !==, ?., @Inject +- [x] `XbaseGeneratorFragmentTest.xtend` (200 lines) — Medium — extension + +### Hard - Builder fragments (2 files) +- [x] `StandaloneBuilderIntegrationFragment2.xtend` (165 lines) — Hard — templates, extension, @Inject +- [x] `LspBuilderIntegrationFragment2.xtend` (174 lines) — Hard — templates, extension, @Inject + +### Hard - Content assist (1 file) +- [x] `AnnotationAwareContentAssistFragment2.xtend` (226 lines) — Hard — **dispatch**, templates, extension, !==, ?., @Accessors + +### Very Hard - ANTLR generators (4 files) +- [x] `AbstractAnnotationAwareAntlrGrammarGenerator.xtend` (159 lines) — Hard — templates, extension, @Inject +- [x] `GrammarRuleAnnotations.xtend` (406 lines) — Very Hard — templates, ===, !==, ?., @Data +- [x] `AnnotationAwareAntlrContentAssistGrammarGenerator.xtend` (489 lines) — Very Hard — **dispatch**, templates, extension, === +- [x] `AnnotationAwareAntlrGrammarGenerator.xtend` (543 lines) — Very Hard — **dispatch**, templates, extension, !==, @Accessors, switch, create +- [x] `AnnotationAwareXtextAntlrGeneratorFragment2.xtend` (529 lines) — Very Hard — templates, extension, ===, !==, #[, @Accessors, create + +--- + +## Batch 9 — Remaining test files (5 files) — DONE + +### `check.core.test` (4 files) +- [x] `CheckModelUtil.xtend` (113 lines) — Medium — templates +- [x] `IssueCodeToLabelMapGenerationTest.xtend` (130 lines) — Medium — templates, #[, switch +- [x] `CheckValidationTest.xtend` (342 lines) — Hard — extension, typeof, create +- [x] `CheckFormattingTest.xtend` (554 lines) — Very Hard — templates, extension, typeof, !==, ?. + +### `check.ui.test` (1 file) +- [x] `CheckQuickfixTest.xtend` (200 lines) — Medium — templates, #[, override + +--- + +## Build Config Cleanup (after all files migrated) + +- [ ] Remove `xtend-maven-plugin` from `ddk-parent/pom.xml` +- [ ] Remove `xtend.version` property from `ddk-parent/pom.xml` +- [ ] Remove PMD `excludeRoot` for xtend-gen from `ddk-parent/pom.xml` +- [ ] Remove clean plugin xtend-gen fileset from `ddk-parent/pom.xml` +- [ ] Remove `org.eclipse.xtend.lib` from ~10 MANIFEST.MF files +- [ ] Remove `xtend-gen` source entries from ~31 `.classpath` files +- [ ] Remove `xtend-gen` from ~31 `build.properties` files +- [ ] Update `.gitignore` to remove xtend-gen patterns +- [ ] Delete all `xtend-gen/` directories +- [ ] Final full build + test verification + +--- + +## Verification Protocol + +After each batch: + +1. **Compile**: `mvn clean compile -f ./ddk-parent/pom.xml --batch-mode` — must pass +2. **Test**: `mvn clean verify -f ./ddk-parent/pom.xml --batch-mode --fail-at-end` — must pass (except known UI test on macOS) +3. **Update** this checklist — mark converted files as done +4. **Commit** the batch + +--- + +## Conversion Rules Quick Reference + +| Xtend | Java | +|-------|------| +| `val x = expr` | `final ExplicitType x = expr;` | +| `var x = expr` | `ExplicitType x = expr;` | +| `def method()` | `public ReturnType method()` | +| `override method()` | `@Override public ReturnType method()` | +| `typeof(X)` | `X.class` | +| `===` / `!==` | `==` / `!=` | +| `obj?.method()` | null check or ternary | +| `[x \| body]` | `(x) -> body` | +| `dispatch method(T x)` | `_method(T x)` + dispatcher with `instanceof` | +| `@Inject extension Foo` | `@Inject private Foo foo;` + rewrite call sites | +| `'''template «expr»'''` | `StringBuilder` with `.append()` | +| `#[]` / `#{}` | `List.of()` / `Set.of()` | +| `obj.name` (property) | `obj.getName()` | +| `list += x` | `list.add(x)` | +| `a ?: b` | `a != null ? a : b` | +| `expr as Type` | `(Type) expr` or pattern matching | + +Full conversion prompt: [`docs/xtend-to-java-conversion-prompt.md`](xtend-to-java-conversion-prompt.md) + +--- + +## Complexity Legend + +| Rating | Criteria | +|--------|----------| +| **Trivial** | ≤30 lines, no complex Xtend features | +| **Easy** | ≤100 lines, basic features (val, override, @Inject, simple templates) | +| **Medium** | 100–200 lines, or uses templates + extension methods + switch | +| **Hard** | 200–400 lines with dispatch/complex templates/extension methods | +| **Very Hard** | 400+ lines with dispatch + templates + extensions + create methods | diff --git a/docs/xtend-to-java-conversion-prompt.md b/docs/xtend-to-java-conversion-prompt.md new file mode 100644 index 0000000000..ecdcd5e2aa --- /dev/null +++ b/docs/xtend-to-java-conversion-prompt.md @@ -0,0 +1,631 @@ +# Xtend-to-Java Conversion Prompt + +You are an expert Java and Xtend developer. Your task is to convert a single Xtend (.xtend) file into idiomatic Java (.java) targeting Java 21. The file belongs to the dsl-devkit project, an Eclipse/Xtext-based DSL development kit that uses Google Guice for dependency injection. + +## INPUT + +You will receive the complete contents of one `.xtend` file. + +## OUTPUT + +Return the complete, compilable `.java` file. The output must: +- Be valid Java 21 code +- Compile without errors in the context of the dsl-devkit project +- Preserve all original functionality exactly +- Be idiomatic Java (NOT a mechanical translation) +- Include all necessary imports + +--- + +## DECISIONS + +- **Target: Java 21** (pattern matching, switch expressions, text blocks, records) +- **Template strategy: `StringBuilder`** (pure Java, no Xtend runtime dependency) +- **No `var` keyword**: Always use explicit types (no Java 10 `var` / `final var`) + - `val catalog = ...` -> `final CheckCatalog catalog = ...;` (NOT `final var catalog = ...;`) + - `var skip = ...` -> `int skip = ...;` (NOT `var skip = ...;`) + - All local variables, fields, and parameters must have explicit type declarations. + +--- + +## CONVERSION RULES + +Apply these rules systematically, in order. Every rule is mandatory. + +### 1. FILE STRUCTURE AND BASICS + +1.1. **Package declaration**: Keep identical. Add semicolon if missing. + +1.2. **Imports**: Convert all Xtend imports to Java imports. +- `import com.foo.Bar` stays as `import com.foo.Bar;` +- `import static com.foo.Bar.*` stays as `import static com.foo.Bar.*;` +- `import static extension com.foo.Bar.*` becomes `import static com.foo.Bar.*;` (the `extension` keyword is dropped; see Rule 8 for call-site conversion) +- Remove imports for Xtend-specific types that are no longer needed (e.g., `org.eclipse.xtend2.lib.StringConcatenation` if you use StringBuilder instead) +- Add any new imports needed by the Java code (e.g., `java.util.List`, `java.util.ArrayList`, `java.util.stream.*`, `java.util.Objects`) +- Do NOT add wildcard imports. Use explicit imports. +- Remove unused imports. + +1.3. **Class declaration**: +- Xtend classes are `public` by default. Add `public` explicitly. +- `class Foo extends Bar` becomes `public class Foo extends Bar` +- Xtend `interface` stays as `interface` (already public by default in Java too). +- Add `{` and `}` braces as normal Java. + +1.4. **Semicolons**: Add semicolons to all statements. Xtend allows omitting them; Java requires them. + +### 2. VARIABLE DECLARATIONS + +2.1. **`val` (final local variable)**: +- Always use explicit types: `final ExplicitType name = expr;` +- Example: `val catalog = EcoreUtil2.getContainerOfType(context, CheckCatalog.class)` becomes `final CheckCatalog catalog = EcoreUtil2.getContainerOfType(context, CheckCatalog.class);` + +2.2. **`var` (mutable local variable)**: +- Always use explicit types: `ExplicitType name = expr;` +- Example: `var skip = instance - 1` becomes `int skip = instance - 1;` + +2.3. **`val` fields (class-level final fields)**: +- `val String FOO = "bar"` becomes `private final String FOO = "bar";` +- `val static Logger LOGGER = ...` becomes `private static final Logger LOGGER = ...;` +- Always add explicit visibility (`private` unless a different visibility is needed). + +2.4. **`var` fields (class-level mutable fields)**: +- `var String name` becomes `private String name;` +- Add explicit visibility. + +### 3. METHOD DECLARATIONS + +3.1. **`def` methods**: +- `def` means `public` by default. Add explicit `public`. +- `def private`, `def protected`, `def package` keep their visibility. +- Add explicit return type. If the Xtend method omits the return type, infer it from the method body. +- Example: `def outputPath()` with body `'.settings'` becomes `public String outputPath()` + +3.2. **`override` keyword**: +- Replace `override` with `@Override` annotation plus the appropriate visibility modifier. +- `override void doGenerate(...)` becomes: + ```java + @Override + public void doGenerate(...) { + ``` +- `override protected doGenerate()` becomes: + ```java + @Override + protected void doGenerate() { + ``` + +3.3. **Return types and implicit returns**: +- Xtend methods return the value of the last expression. In Java, add explicit `return` statements. +- If a method's last expression is a value, wrap it in `return`. +- For `void` methods, no return is needed. +- Example: Xtend `def foo() { bar }` becomes Java `public SomeType foo() { return bar; }` + +3.4. **Method parameters**: +- Add `final` to parameters in methods that do not reassign them (follow the convention of the existing Java code in the project). +- `extension` parameters: see Rule 8. + +### 4. TYPE REFERENCES + +4.1. **`typeof(X)` to `X.class`**: +- Replace all `typeof(ClassName)` with `ClassName.class`. +- Example: `typeof(CheckCatalog)` becomes `CheckCatalog.class` + +4.2. **Generic type syntax**: Xtend and Java use the same generic syntax. Keep as-is. + +4.3. **Type casting**: `expr as Type` becomes `(Type) expr` or use pattern matching with `instanceof` (Java 21). + +### 5. OPERATORS AND EXPRESSIONS + +5.1. **Identity comparison**: +- `===` (identity equals) becomes `==` in Java. +- `!==` (identity not-equals) becomes `!=` in Java. +- `==` in Xtend is `.equals()`. Convert to `.equals()` or `Objects.equals()` in Java (use `Objects.equals()` when either operand could be null). + +5.2. **Null-safe navigation `?.`**: +- `obj?.method()` becomes a null check. Use one of: + - Ternary: `obj != null ? obj.method() : null` + - If-statement for complex cases + - For chained null-safe calls, nest the ternaries or use local variables: + ```java + // Xtend: resource?.URI + // Java: + final URI uri = resource != null ? resource.getURI() : null; + ``` + - IMPORTANT: If the result of `?.` is used in a comparison against null (e.g., `resource?.URI !== null`), then use: `resource != null && resource.getURI() != null` + +5.3. **Elvis operator `?:`**: +- `a ?: b` becomes `a != null ? a : b` (or `Objects.requireNonNullElse(a, b)` if appropriate). + +5.4. **`=>` operator (with/apply)**: +- `obj => [ body ]` executes `body` with `obj` as `it`, then returns `obj`. +- Convert to: + ```java + { // inline block or extract to a method + ExplicitType temp = obj; + // body, replacing `it` references with `temp` + // return temp; // if the result is used + } + ``` +- For simple cases like `new Foo() => [bar = "baz"]`, convert to: + ```java + Foo foo = new Foo(); + foo.setBar("baz"); + // use foo + ``` + +5.5. **String concatenation `+`**: Same in Java. + +5.6. **Range operator `..`**: `0..n` becomes `IntStream.rangeClosed(0, n)` or a for-loop. + +5.7. **Power operator `**`**: Use `Math.pow()`. + +### 6. LAMBDA EXPRESSIONS + +6.1. **Xtend `[...]` lambda to Java `(...) -> {...}`**: +- `[x | x.name]` becomes `(x) -> x.getName()` or `x -> x.getName()` +- `[it | name]` becomes `(it) -> it.getName()` or simply use a method reference +- `[ body ]` (no parameters, implicit `it`) becomes `(it) -> { body }` where `it` is used, or `() -> { body }` if `it` is not used. +- Single-expression lambdas do not need braces: `x -> x.getName()` +- Multi-statement lambdas need braces and explicit `return`: `(x) -> { doSomething(); return x.getName(); }` + +6.2. **Lambda with `it` as implicit parameter**: +- When a lambda uses properties/methods without a receiver, these reference the implicit `it` parameter. +- `[name]` on a `Function1` becomes `(Foo it) -> it.getName()` or `Foo::getName` +- Xtend: `checks.filter[name !== null]` becomes Java: `checks.stream().filter(c -> c.getName() != null).toList()` + +6.3. **Procedure (void lambda) vs Function (returning lambda)**: +- Xtend uses the same `[...]` syntax for both. +- In Java, determine from context whether it is `Consumer`, `Predicate`, `Function`, etc. +- For Xtext-specific cases: `Procedure1` lambdas like `[noSpace]` become `(IHiddenRegionFormatter it) -> { it.noSpace(); }` or `IHiddenRegionFormatter::noSpace`. + +### 7. DISPATCH METHODS + +This is one of the most complex Xtend features. Dispatch methods implement multiple dispatch (method selection based on runtime type of arguments). + +7.1. **Pattern**: A set of `def dispatch` methods with the same name but different parameter types: +```xtend +def dispatch void format(CheckCatalog c, IFormattableDocument doc) { ... } +def dispatch void format(Category c, IFormattableDocument doc) { ... } +def dispatch void format(EObject obj, IFormattableDocument doc) { ... } +``` + +7.2. **Conversion strategy**: Convert to individual `protected` methods prefixed with `_` (underscore) plus a public dispatcher method: + +```java +protected void _format(CheckCatalog c, IFormattableDocument doc) { ... } +protected void _format(Category c, IFormattableDocument doc) { ... } +protected void _format(EObject obj, IFormattableDocument doc) { ... } + +public void format(Object obj, IFormattableDocument doc) { + if (obj instanceof CheckCatalog c) { + _format(c, doc); + return; + } else if (obj instanceof Category c) { + _format(c, doc); + return; + } else if (obj instanceof EObject e) { + _format(e, doc); + return; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + obj); + } +} +``` + +7.3. **Important dispatch rules**: +- Order type checks from most specific to least specific. +- If a dispatch method has `override` keyword, add `@Override` to the dispatcher method, not the individual `_` methods. +- The dispatcher parameter type should be the common supertype (usually `Object` or `EObject`). +- If the parent class also has dispatch methods with the same name, the dispatcher must call `super._methodName()` for types not handled locally. +- Use Java 21 pattern matching for instanceof (`if (obj instanceof Foo f)`) in the dispatcher. + +### 8. EXTENSION METHODS + +8.1. **`@Inject extension ClassName fieldName`**: +- Convert to: `@Inject private ClassName fieldName;` +- At every call site where the extension's methods were called as `obj.extensionMethod(args)`, convert to `fieldName.extensionMethod(obj, args)`. +- If the extension field was used without a name (e.g., `@Inject extension CheckGeneratorNaming`), generate a field name following the convention: `_checkGeneratorNaming` (underscore + camelCase class name starting lowercase). + +8.2. **`extension` method parameters** (e.g., `def foo(extension IFormattableDocument document)`): +- Drop the `extension` keyword from the parameter. +- At call sites within the method body, calls that were dispatched to the extension parameter need to be converted to explicit calls: + - `prepend(checkcatalog)[noSpace]` becomes `document.prepend(checkcatalog, (IHiddenRegionFormatter it) -> it.noSpace())` + +8.3. **`static extension` imports** (e.g., `import static extension com.foo.Bar.*`): +- Convert to `import static com.foo.Bar.*;` +- At call sites: `obj.staticExtensionMethod(args)` becomes `Bar.staticExtensionMethod(obj, args)`. +- Example: `import static extension org.eclipse.xtext.GrammarUtil.*` then `grammar.simpleName` becomes `GrammarUtil.getSimpleName(grammar)`. + +8.4. **`extension` keyword on `val`/`var`** (e.g., `val extension naming = contentAssistNaming`): +- Drop `extension` keyword. Keep as a local variable. +- Convert call sites within scope to explicit calls on the variable. + +### 9. TEMPLATE EXPRESSIONS (GUILLEMETS) + +This is the MOST COMPLEX feature. Template expressions use triple single quotes and guillemets (French quotes). + +9.1. **Simple templates** (no control flow, just interpolation): +- If the template is a single line or very short, use string concatenation or `String.format()`: + ```xtend + '''{predicates.«predicate.name»(parserContext)}?=>''' + ``` + becomes: + ```java + "{predicates." + predicate.getName() + "(parserContext)}?=>" + ``` + +9.2. **Multi-line templates generating code/text**: Use `StringBuilder`: +```xtend +def compile(CheckConfiguration config) { + val properties = propertiesGenerator.convertToProperties(config); + ''' + «FOR k:properties.keySet» + «k»=«properties.get(k)» + «ENDFOR» + ''' +} +``` +becomes: +```java +public CharSequence compile(CheckConfiguration config) { + final Properties properties = propertiesGenerator.convertToProperties(config); + final StringBuilder builder = new StringBuilder(); + for (final String k : properties.keySet()) { + builder.append(k).append("=").append(properties.get(k)).append("\n"); + } + return builder; +} +``` + +9.3. **Template control flow**: +- `«IF condition»...«ENDIF»` becomes `if (condition) { builder.append(...); }` +- `«IF condition»...«ELSE»...«ENDIF»` becomes `if-else` +- `«ELSEIF condition»` becomes `else if (condition)` +- `«FOR item : collection»...«ENDFOR»` becomes `for (Type item : collection) { builder.append(...); }` +- `«FOR item : collection SEPARATOR sep»...«ENDFOR»` -- use a boolean flag or `String.join()` or `Collectors.joining()`: + ```java + builder.append(collection.stream() + .map(item -> /* expression */) + .collect(Collectors.joining(sep))); + ``` +- `«val x = expr»` inside a template is a local variable declaration. Declare it before use. + +9.4. **Template indentation**: +- Xtend templates preserve indentation relative to the insertion point. In the Java conversion, you do NOT need to replicate this Xtend-specific whitespace behavior exactly. Instead: + - For code generators producing source code: use explicit `\n` and string indentation as appropriate. + - For simple cases, inline `\n` in the StringBuilder appends. + - For complex generators, consider creating a helper method or using a `StringJoiner`. + +9.5. **Return type**: Methods returning template expressions should return `CharSequence` (to match Xtend's `StringConcatenation` return type, which implements `CharSequence`). Alternatively, return `String` if all callers use it as `String` (add `.toString()` call on the `StringBuilder`). + +9.6. **`«expression»` interpolation**: Convert `«expr»` to the corresponding Java expression inside a `.append()` call. +- `«catalog.name»` becomes `.append(catalog.getName())` +- `«IF grammar !== null»GRAMMAR_NAME,«ENDIF»` becomes: + ```java + if (grammar != null) { + builder.append("GRAMMAR_NAME,"); + } + ``` + +### 10. COLLECTION LITERALS AND OPERATIONS + +10.1. **`#[]` (list literal)**: +- `#["a", "b", "c"]` becomes `List.of("a", "b", "c")` (immutable) or `new ArrayList<>(List.of("a", "b", "c"))` (mutable). +- Empty: `#[]` becomes `List.of()` or `new ArrayList<>()`. +- `newArrayList` becomes `new ArrayList<>()` or `new ArrayList<>(...)`. +- `newArrayList("a", "b")` becomes `new ArrayList<>(List.of("a", "b"))` or `Lists.newArrayList("a", "b")` (if Google Guava is available, which it is in this project). + +10.2. **`#{}` (set literal)**: +- `#{"a", "b"}` becomes `Set.of("a", "b")` or `new HashSet<>(Set.of("a", "b"))`. +- `newHashSet` becomes `new HashSet<>()` or `Sets.newHashSet(...)`. + +10.3. **Collection operations** (Xtend extension methods on Iterable/Collection): +- `.map[expr]` becomes `.stream().map(x -> expr).toList()` or `.stream().map(x -> expr).collect(Collectors.toList())` +- `.filter[expr]` becomes `.stream().filter(x -> expr).toList()` +- `.filter(Type)` becomes `.stream().filter(Type.class::isInstance).map(Type.class::cast).toList()` OR use Guava `Iterables.filter(collection, Type.class)` +- `.exists[expr]` becomes `.stream().anyMatch(x -> expr)` +- `.forall[expr]` becomes `.stream().allMatch(x -> expr)` +- `.findFirst[expr]` becomes `.stream().filter(x -> expr).findFirst().orElse(null)` +- `.head` becomes `.get(0)` (for List) or `.iterator().next()` (for Iterable), with null safety if needed +- `.tail` becomes `.subList(1, list.size())` or `.stream().skip(1).toList()` +- `.toList` becomes `.stream().toList()` or `new ArrayList<>(iterable)` or `IterableExtensions.toList(iterable)` +- `.toSet` becomes `new HashSet<>(collection)` or `.stream().collect(Collectors.toSet())` +- `.flatten` becomes `.stream().flatMap(Collection::stream).toList()` +- `.sortBy[expr]` becomes `.stream().sorted(Comparator.comparing(x -> expr)).toList()` +- `.sort` becomes `.stream().sorted().toList()` or `Collections.sort(list)` for in-place +- `.join(',')` becomes `String.join(",", collection)` or `.stream().collect(Collectors.joining(","))` +- `.indexed` becomes use `IntStream.range(0, list.size())` with index access, or keep Guava if present +- `.reverse` becomes `Collections.reverse(new ArrayList<>(list))` or use `Lists.reverse(list)` (Guava) +- `.isEmpty` / `.empty` becomes `.isEmpty()` +- `.size` becomes `.size()` +- `.forEach[action]` becomes `.forEach(x -> action)` (Java Iterable.forEach or Stream.forEach) +- `.filterNull` becomes `.stream().filter(Objects::nonNull).toList()` + +10.4. **`isNullOrEmpty`**: +- `StringExtensions.isNullOrEmpty(s)` or `s.isNullOrEmpty` becomes `s == null || s.isEmpty()` +- Or use a utility method if the project has one. + +10.5. **`toIterable(iterator)`**: `IteratorExtensions.toIterable(resource.getAllContents())` -- keep this call as-is since it is an Xtext utility, OR convert to: `() -> resource.getAllContents()` (creating an Iterable from Iterator). + +10.6. **`Iterables.filter(iterable, Class)`**: Keep this Guava call as-is -- it is idiomatic in Eclipse/Xtext projects. + +10.7. **Operator overloading on collections**: +- `list += element` becomes `list.add(element)` +- `list += otherList` becomes `list.addAll(otherList)` +- `list -= element` becomes `list.remove(element)` +- `map.get(key)` -- same in Java (Xtend allows `map[key]` syntax which becomes `map.get(key)`) + +### 11. PROPERTY ACCESS SYNTAX + +11.1. Xtend allows property-style access for getters/setters: +- `obj.name` may mean `obj.getName()` -- convert to explicit getter call +- `obj.name = value` may mean `obj.setName(value)` -- convert to explicit setter call +- `obj.isActive` may mean `obj.isActive()` or `obj.getIsActive()` -- determine from context + +11.2. Boolean property access: +- `field.final` means `field.isFinal()` +- `field.static` means `field.isStatic()` + +11.3. **IMPORTANT**: Not all dot-access is property access. If the object actually has a public field, keep field access. Determine from the types involved. + +### 12. SWITCH EXPRESSIONS + +12.1. **Basic switch**: +```xtend +switch(x) { + case "a": doA() + case "b": doB() + default: doDefault() +} +``` +becomes Java switch expression or statement depending on context. + +12.2. **Switch with type guards**: +```xtend +switch obj { + CheckCatalog: obj.name + Category case obj.name !== null: obj.label + default: "unknown" +} +``` +becomes: +```java +if (obj instanceof CheckCatalog checkCatalog) { + return checkCatalog.getName(); +} else if (obj instanceof Category category && category.getName() != null) { + return category.getLabel(); +} else { + return "unknown"; +} +``` + +12.3. Use Java 21 pattern matching for instanceof where applicable. + +### 13. ACTIVE ANNOTATIONS + +13.1. **`@Data`**: This generates `equals()`, `hashCode()`, `toString()`, and getters for all fields (which are final). Convert to a Java `record` if the class has no mutable state and no superclass. Otherwise, manually add: +- All-args constructor +- Getter methods for each field +- `equals()`, `hashCode()`, `toString()` +- Or use `@Override` of these methods if the class extends something. + +For inner static classes annotated with `@Data` that have `val` fields and no superclass (like this project's `NoBacktrack`, `SemanticPredicate`, `GrammarAnnotations`), prefer Java records: +```java +public record SemanticPredicate(String name, String message, String grammar, List keywords) {} +``` + +13.2. **`@Accessors`**: Generates getters (and setters for `var` fields). +- `@Accessors boolean foo` generates `getFoo()` and `setFoo(boolean)`. +- `@Accessors(PUBLIC_SETTER) String bar` generates only a public setter. +- `@Accessors(PROTECTED_GETTER) Foo baz` generates only a protected getter. +- Convert by manually writing the getter/setter methods with the specified visibility. + +13.3. **`@FinalFieldsConstructor`**: Generates a constructor taking all final fields as parameters. Manually write the constructor. + +### 14. SPECIAL XTEND PATTERNS + +14.1. **`it` implicit parameter**: +- When a method declares `Type it` as its first parameter (e.g., `def generate(ExportModel it, ...)`), all unqualified method/property calls in the body refer to `it`. +- Convert: Add the parameter with a proper name (e.g., `exportModel`) and qualify all calls: + - `exports` becomes `exportModel.getExports()` + - `grammar` becomes `exportModel.getGrammar()` (if it's a property of ExportModel) + - `extension` becomes `exportModel.isExtension()` + +14.2. **`this` vs receiver**: In Xtend, method calls without a receiver may go to `this`, an extension, or `it`. You must determine which based on the type hierarchy. Check: + 1. Is it a method on the current class or its superclass? -> `this.method()` or just `method()` + 2. Is it an extension method from an `@Inject extension` field? -> `field.method(obj)` + 3. Is it a static extension method? -> `ExtClass.method(obj)` + 4. Is it on the `it` implicit receiver? -> `it.method()` using the renamed parameter + +14.3. **Multiple return statements**: Xtend methods implicitly return the last expression. You MUST add explicit `return` for ALL non-void return paths. + +14.4. **`class` keyword access**: In Xtend, `SomeClass` by itself in certain contexts refers to the class literal. In Java, use `SomeClass.class`. + +14.5. **Static method access with `::`**: `ClassName::methodName` or `ClassName::FIELD` becomes `ClassName.methodName()` or `ClassName.FIELD` in Java. + +14.6. **Pairs**: `key -> value` becomes `Pair.of(key, value)` or `Map.entry(key, value)` depending on context. + +### 15. GUICE DEPENDENCY INJECTION + +15.1. **`@Inject` fields**: Keep as-is. These are standard Guice annotations. +- `@Inject ClassName fieldName` becomes `@Inject private ClassName fieldName;` +- Add `private` visibility if not already present. +- If the field was an `extension`, see Rule 8. + +15.2. **`@Inject extension`**: See Rule 8.1. + +### 16. COMMENTS AND DOCUMENTATION + +16.1. **Preserve ALL comments**: Copy Javadoc (`/** */`), block comments (`/* */`), and line comments (`//`) exactly as they appear. + +16.2. **Copyright headers**: Keep the exact copyright header from the original file. + +16.3. **`@SuppressWarnings("all")`**: The Xtend compiler adds this. Do NOT add it to the converted Java file unless it was explicitly in the Xtend source. + +### 17. XTEND LIBRARY REPLACEMENTS + +Replace Xtend runtime library calls with Java standard library or Guava equivalents: + +| Xtend Library Call | Java Replacement | +|---|---| +| `IterableExtensions.map(iter, fn)` | `iter.stream().map(fn).toList()` | +| `IterableExtensions.filter(iter, fn)` | `iter.stream().filter(fn).toList()` | +| `IterableExtensions.toList(iter)` | `Lists.newArrayList(iter)` or stream | +| `IterableExtensions.toSet(iter)` | `Sets.newHashSet(iter)` or stream | +| `IterableExtensions.head(iter)` | `iter.iterator().next()` with null check, or `Iterables.getFirst(iter, null)` | +| `IterableExtensions.join(iter, sep)` | `String.join(sep, iter)` or `Joiner.on(sep).join(iter)` | +| `IterableExtensions.exists(iter, fn)` | `iter.stream().anyMatch(fn)` | +| `IterableExtensions.forall(iter, fn)` | `iter.stream().allMatch(fn)` | +| `IterableExtensions.findFirst(iter, fn)` | `iter.stream().filter(fn).findFirst().orElse(null)` | +| `IterableExtensions.sortBy(iter, fn)` | `iter.stream().sorted(Comparator.comparing(fn)).toList()` | +| `IterableExtensions.sort(iter)` | `iter.stream().sorted().toList()` | +| `IterableExtensions.isEmpty(iter)` | `!iter.iterator().hasNext()` or `Iterables.isEmpty(iter)` | +| `IterableExtensions.toMap(iter, keyFn, valFn)` | `iter.stream().collect(Collectors.toMap(keyFn, valFn))` | +| `IteratorExtensions.toIterable(iter)` | Keep as utility or wrap: `(Iterable) () -> iter` | +| `StringExtensions.isNullOrEmpty(s)` | `s == null \|\| s.isEmpty()` | +| `CollectionLiterals.newArrayList(...)` | `new ArrayList<>(List.of(...))` or `Lists.newArrayList(...)` | +| `CollectionLiterals.newHashSet(...)` | `new HashSet<>(Set.of(...))` or `Sets.newHashSet(...)` | +| `CollectionLiterals.newHashMap(...)` | `new HashMap<>(Map.of(...))` or `Maps.newHashMap()` | +| `ObjectExtensions.operator_doubleArrow(obj, fn)` | inline (see Rule 5.4) | +| `Functions.Function1` | `java.util.function.Function` | +| `Procedures.Procedure1` | `java.util.function.Consumer` | + +**IMPORTANT**: If the existing Java code in the project uses Guava (which this project does extensively), prefer Guava utilities over Java streams for consistency. For example, prefer `Iterables.filter(iter, Type.class)` over `iter.stream().filter(...)`. + +### 18. FORMATTING AND STYLE + +18.1. Use standard Java formatting: +- 2-space indentation (to match this project's convention) +- Opening brace on same line +- Spaces around operators +- Blank line between methods + +18.2. Keep the original method ordering from the Xtend file. + +18.3. Use `this.` qualifier only when needed for disambiguation (e.g., with injected fields sharing names with parameters). + +### 19. CHECKED EXCEPTIONS + +19.1. Xtend does not enforce checked exceptions. When converting to Java, methods that call APIs that throw checked exceptions need proper handling: +- Add `throws` declarations to the method signature, OR +- Wrap in try-catch blocks +- Check the actual APIs being called to determine which checked exceptions need handling +- Example: `CoreException` from Eclipse APIs, `IOException` from I/O operations + +--- + +## CHECKLIST + +Before returning the converted file, verify: + +- [ ] All `val` converted to `final ExplicitType` (no `var` keyword) +- [ ] All `var` converted to `ExplicitType` (no `var` keyword) +- [ ] All `def` converted to proper Java method with visibility, return type, and `return` statements +- [ ] All `override` converted to `@Override` annotation +- [ ] All `typeof(X)` converted to `X.class` +- [ ] All `===` / `!==` converted to `==` / `!=` +- [ ] All `?.` null-safe navigation converted to null checks +- [ ] All `[...]` lambdas converted to `(...) -> {...}` +- [ ] All `dispatch` methods converted to dispatcher pattern +- [ ] All `extension` methods converted to explicit calls +- [ ] All `static extension` imports converted to static calls +- [ ] All template expressions `'''...«»...'''` converted to StringBuilder +- [ ] All `«IF»/«FOR»/«SEPARATOR»` converted to Java control flow +- [ ] All `#[]` / `#{}` collection literals converted +- [ ] All `=>` operator usages converted +- [ ] All `@Data` / `@Accessors` active annotations expanded +- [ ] All property access (`.name`) converted to getter/setter calls (`.getName()`) +- [ ] All `isNullOrEmpty` and Xtend library calls replaced +- [ ] All `+=` on collections converted to `.add()` / `.addAll()` +- [ ] All `::` static access converted to `.` +- [ ] Semicolons added to all statements +- [ ] Explicit visibility modifiers on all classes, methods, fields +- [ ] All imports updated (Xtend-specific removed, Java ones added) +- [ ] All comments and Javadoc preserved +- [ ] Copyright header preserved exactly +- [ ] No `@SuppressWarnings("all")` added (unless in original source) +- [ ] Checked exceptions properly handled (throws or try-catch) +- [ ] File compiles as valid Java 21 + +--- + +## EXAMPLE CONVERSION + +### Xtend Input: +```xtend +package com.example + +import com.google.inject.Inject +import org.eclipse.emf.ecore.resource.Resource +import static org.eclipse.xtext.xbase.lib.IteratorExtensions.* +import static extension com.example.NamingExtensions.* + +class MyGenerator { + @Inject extension MyHelper helper + + override void doGenerate(Resource resource) { + val config = getConfig(resource?.URI) + for (model : toIterable(resource.allContents).filter(typeof(MyModel))) { + model.compile + } + } + + def compile(MyModel it) ''' + package «packageName»; + «IF !imports.isNullOrEmpty» + + «FOR imp : imports» + import «imp»; + «ENDFOR» + «ENDIF» + + public class «name» { + } + ''' +} +``` + +### Java Output: +```java +package com.example; + +import com.google.inject.Inject; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.xbase.lib.IteratorExtensions; + +import com.google.common.collect.Iterables; + +public class MyGenerator { + + @Inject + private MyHelper helper; + + @Override + public void doGenerate(final Resource resource) { + final URI uri = resource != null ? resource.getURI() : null; + final MyConfig config = getConfig(uri); + for (final MyModel model : Iterables.filter(IteratorExtensions.toIterable(resource.getAllContents()), MyModel.class)) { + compile(model); + } + } + + public CharSequence compile(final MyModel model) { + final StringBuilder builder = new StringBuilder(); + builder.append("package ").append(model.getPackageName()).append(";\n"); + if (!(model.getImports() == null || model.getImports().isEmpty())) { + builder.append("\n"); + for (final String imp : model.getImports()) { + builder.append("import ").append(imp).append(";\n"); + } + } + builder.append("\n"); + builder.append("public class ").append(NamingExtensions.getName(model)).append(" {\n"); + builder.append("}\n"); + return builder; + } +} +``` + +--- + +Now convert the following Xtend file to idiomatic Java following ALL the rules above: