diff --git a/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/StandaloneBuilderTest.java b/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/StandaloneBuilderTest.java index 779d8b1950a..34c73515262 100644 --- a/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/StandaloneBuilderTest.java +++ b/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/StandaloneBuilderTest.java @@ -12,10 +12,13 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; import org.eclipse.emf.common.util.URI; @@ -165,6 +168,57 @@ public void testJarToPlatformMapping() { uri.toString().endsWith("test-data/model.in.eclipse.project.jar!/")); } + @Test + public void testWriteClassPathConfiguration() throws IOException { + initBuilder(new TestLanguageConfiguration(false)); + testBuilder.setSourceDirs(ImmutableList.of("test-data/standalone.with.reference/model")); + testBuilder.setClassPathEntries(ImmutableList.of("test-data/standalone.with.reference/target/classes/", + "test-data/model.in.eclipse.project.jar")); + + testBuilder.setTempDir(TMP_DIR); + TMP_DIR.mkdir(); + + File configFile = new File(TMP_DIR, "classpath.config"); + assertFalse(configFile.exists()); + testBuilder.setClasspathConfigurationLocation(configFile.getAbsolutePath(), "prod", "prod-out"); + + assertTrue("Builder launch returned false", testBuilder.launch()); + assertTrue(configFile.exists()); + + Properties onlyProd = new Properties(); + try(FileReader reader = new FileReader(configFile, StandardCharsets.UTF_8)) { + onlyProd.load(reader); + } + assertEquals(7, onlyProd.size()); + + testBuilder.setClassPathEntries(ImmutableList.of("test-data/standalone.with.reference/target/classes/", + "test-data/missing.jar")); + testBuilder.setClasspathConfigurationLocation(configFile.getAbsolutePath(), "test", "test-out"); + + assertFalse("Builder launch returned true", testBuilder.launch()); + + assertTrue(configFile.exists()); + + Properties alsoTest = new Properties(); + try(FileReader reader = new FileReader(configFile, StandardCharsets.UTF_8)) { + alsoTest.load(reader); + } + + assertTrue(alsoTest.entrySet().containsAll(onlyProd.entrySet())); + assertEquals(14, alsoTest.size()); + + testBuilder.setClassPathEntries(ImmutableList.of("test-data/standalone.with.reference/target/classes/")); + testBuilder.setClasspathConfigurationLocation(configFile.getAbsolutePath(), "prod", "prod-out"); + + assertFalse("Builder launch returned true", testBuilder.launch()); + + alsoTest = new Properties(); + try(FileReader reader = new FileReader(configFile, StandardCharsets.UTF_8)) { + alsoTest.load(reader); + } + assertEquals(12, alsoTest.size()); + } + @Test public void testDuplicateSourceEntries() { TestLanguageConfiguration config = new TestLanguageConfiguration(false); @@ -224,17 +278,17 @@ private StandaloneBuilder initBuilder(ILanguageConfiguration config) { } private StandaloneBuilder initBuilder(ILanguageConfiguration config, String... srcDirs) { - List patthes = new ArrayList(); + List pathes = new ArrayList(); for (String srcDir : srcDirs) { - patthes.add(new File(PROJECT_DIR, srcDir).getAbsolutePath()); + pathes.add(new File(PROJECT_DIR, srcDir).getAbsolutePath()); } - testBuilder.setSourceDirs(patthes); + testBuilder.setSourceDirs(pathes); testBuilder.resetCallStatistic(); - Map languages = new LanguageAccessFactory().createLanguageAccess( - ImmutableList.of(config), getClass().getClassLoader()); + Map languages = new LanguageAccessFactory() + .createLanguageAccess(ImmutableList.of(config), getClass().getClassLoader()); testBuilder.setBaseDir(PROJECT_DIR.getAbsolutePath()); testBuilder.setLanguages(languages); - testBuilder.setClassPathEntries(ImmutableList. of()); + testBuilder.setClassPathEntries(ImmutableList.of()); return testBuilder; } diff --git a/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/incremental/IncrementalStandaloneBuilderWithJavaTest.java b/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/incremental/IncrementalStandaloneBuilderWithJavaTest.java index 228a0b62c39..80ad4dd201c 100644 --- a/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/incremental/IncrementalStandaloneBuilderWithJavaTest.java +++ b/org.eclipse.xtext.builder.standalone.tests/src/org/eclipse/xtext/builder/standalone/incremental/IncrementalStandaloneBuilderWithJavaTest.java @@ -335,11 +335,11 @@ private StandaloneBuilder initBuilder(ILanguageConfiguration... configs) { } private StandaloneBuilder initBuilder(ILanguageConfiguration[] configs, String... srcDirs) { - List patthes = new ArrayList(); + List pathes = new ArrayList(); for (String srcDir : srcDirs) { - patthes.add(new File(PROJECT_DIR, srcDir).getAbsolutePath()); + pathes.add(new File(PROJECT_DIR, srcDir).getAbsolutePath()); } - testBuilder.setSourceDirs(patthes); + testBuilder.setSourceDirs(pathes); testBuilder.resetCallStatistic(); Map languages = new LanguageAccessFactory() .createLanguageAccess(ImmutableList.copyOf(configs), getClass().getClassLoader()); diff --git a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/StandaloneBuilder.java b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/StandaloneBuilder.java index da5c3a361a9..7e958a539a8 100644 --- a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/StandaloneBuilder.java +++ b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/StandaloneBuilder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2020 itemis AG (http://www.itemis.eu) and others. + * Copyright (c) 2020, 2023 itemis AG (http://www.itemis.eu) and others. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. @@ -11,15 +11,20 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.Writer; import java.net.URL; import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -27,12 +32,17 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Properties; import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Predicate; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.regex.Pattern; @@ -84,6 +94,7 @@ import org.eclipse.xtext.resource.persistence.SourceLevelURIsAdapter; import org.eclipse.xtext.resource.persistence.StorageAwareResource; import org.eclipse.xtext.util.CancelIndicator; +import org.eclipse.xtext.util.TailWriter; import org.eclipse.xtext.util.UriUtil; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; @@ -94,6 +105,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Stopwatch; import com.google.common.collect.FluentIterable; +import com.google.common.collect.ForwardingSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; @@ -180,6 +192,14 @@ public class StandaloneBuilder { private final Map configuredFsas = new HashMap<>(); private boolean incremental = false; + + /** + * Location to which the class-path configuration shall be written. The file format is internal. + * Must be configured along with the {@link #classpathKey}. + */ + private String classpathConfigurationLocation; + private String classpathKey; + private String classOutputDirectory; public StandaloneBuilder() { try { @@ -192,6 +212,14 @@ public StandaloneBuilder() { public void setIncrementalBuild(boolean enable) { incremental = enable; } + + public void setClasspathConfigurationLocation(String location, String key, String outputDirectory) { + this.classpathConfigurationLocation = location; + if (location != null) { + this.classpathKey = Objects.requireNonNull(key); + this.classOutputDirectory = Objects.requireNonNull(outputDirectory); + } + } public void setTempDir(String pathAsString) { if (pathAsString != null) { @@ -230,6 +258,10 @@ public boolean launch() { forceDebugLog("Collected source models. Took: " + rootStopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms."); + if (classpathConfigurationLocation != null) { + writeClassPathConfiguration(rootsToTravers, stubsDirectory != null); + } + XtextResourceSet resourceSet = resourceSetProvider.get(); Iterable allClassPathEntries = Iterables.concat(sourceDirs, classPathEntries); if (stubsDirectory != null) { @@ -312,6 +344,57 @@ public boolean launch() { } } + private void writeClassPathConfiguration(Iterable modelRoots, boolean classpath) { + try { + File file = new File(classpathConfigurationLocation); + Properties properties = new Properties(); + if (file.exists()) { + try (FileReader reader = new FileReader(file, StandardCharsets.UTF_8)) { + properties.load(reader); + } + } + String prefix = classpathKey + "."; + properties.entrySet().removeIf(existing -> { + String key = String.valueOf(existing.getKey()); + return key.startsWith(prefix); + }); + intoProperties(modelRoots, prefix + "model.", properties, true); + intoProperties(sourceDirs, prefix + "src.", properties, false); + if (classpath) { + intoProperties(List.of(classOutputDirectory), prefix + "bin.", properties, false); + intoProperties(classPathEntries, prefix + "cp.", properties, true); + } + try (Writer writer = new TailWriter(new FileWriter(file, StandardCharsets.UTF_8), 1)) { + new Properties() { + private static final long serialVersionUID = 1L; + @Override + public Set> entrySet() { + TreeSet> result = new TreeSet<>( + Comparator.comparing(e -> String.valueOf(e.getKey()))); + result.addAll(properties.entrySet()); + return result; + } + }.store(writer, null); + } + } catch (IOException e) { + LOG.error("Failed to write class-path configuration", e); + } + } + + private void intoProperties(Iterable values, String prefix, Properties target, boolean hash) { + int i = 0; + for(String value: values) { + String key = prefix + i; + target.put(key, new File(value).getAbsolutePath()); + if (hash) { + IPath path = new Path(value); + target.put(key + ".hash", classpathInfos.hashClassesOrJar(path).asString()); + } + i++; + } + target.put(prefix + "count", String.valueOf(i)); + } + private String generateStubs(File stubsDirectory, Set changedSourceFiles, Map allDeltas) { if (stubsDirectory == null) { @@ -439,10 +522,10 @@ private void aggregateDelta(Delta delta, Map allDeltas) { }); } - private HashCode hashClasspath(Iterable filteredClasspath) { + private HashCode hashClasspath(Iterable classpathEntries) { NameBasedFilter nameFilter = dslFileNamePattern(); List> hashCodes = new ArrayList<>(); - for (String classpathEntry : filteredClasspath) { + for (String classpathEntry : classpathEntries) { IPath path = new Path(classpathEntry); if ("jar".equalsIgnoreCase(path.getFileExtension())) { hashCodes.add(ForkJoinPool.commonPool().submit(() -> { @@ -988,4 +1071,5 @@ public ClusteringConfig getClusteringConfig() { public void setClusteringConfig(ClusteringConfig clusteringConfig) { this.clusteringConfig = clusteringConfig; } + } diff --git a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/ClasspathEntryHash.java b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/ClasspathEntryHash.java index 813c8b91bfa..fd820df518f 100644 --- a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/ClasspathEntryHash.java +++ b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/ClasspathEntryHash.java @@ -24,4 +24,6 @@ public interface ClasspathEntryHash { void accept(ClasspathEntryHashVisitor visitor); + String asString(); + } \ No newline at end of file diff --git a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/CoarseGrainedEntryHash.java b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/CoarseGrainedEntryHash.java index 62b880a7cd9..83bc5d49a0a 100644 --- a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/CoarseGrainedEntryHash.java +++ b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/CoarseGrainedEntryHash.java @@ -26,5 +26,9 @@ public void accept(ClasspathEntryHashVisitor visitor) { public byte[] asBytes() { return hashCode.asBytes(); } + + public String asString() { + return hashCode.toString(); + } } \ No newline at end of file diff --git a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/FineGrainedEntryHash.java b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/FineGrainedEntryHash.java index d0d27d26879..85dd5039508 100644 --- a/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/FineGrainedEntryHash.java +++ b/org.eclipse.xtext.builder.standalone/src/org/eclipse/xtext/builder/standalone/incremental/FineGrainedEntryHash.java @@ -9,11 +9,15 @@ package org.eclipse.xtext.builder.standalone.incremental; import java.util.Collections; +import java.util.Comparator; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Stream; import org.eclipse.core.runtime.IPath; import com.google.common.hash.HashCode; +import com.google.common.hash.Hasher; public class FineGrainedEntryHash implements ClasspathEntryHash { private final Map classHashes; @@ -33,5 +37,14 @@ public void accept(ClasspathEntryHashVisitor visitor) { public Map classHashes() { return Collections.unmodifiableMap(classHashes); } + + @Override + public String asString() { + Stream> sorted = classHashes.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, Comparator.comparing(IPath::toString))); + Stream bytes = sorted.map(Map.Entry::getValue).map(HashCode::asBytes); + Hasher hasher = BinaryFileHashing.hashFunction().newHasher(); + bytes.forEachOrdered(hasher::putBytes); + return hasher.hash().toString(); + } } \ No newline at end of file diff --git a/org.eclipse.xtext.builder.tests/.classpath b/org.eclipse.xtext.builder.tests/.classpath index 610fcff0486..383ef1f531f 100644 --- a/org.eclipse.xtext.builder.tests/.classpath +++ b/org.eclipse.xtext.builder.tests/.classpath @@ -17,10 +17,5 @@ - - - - - diff --git a/org.eclipse.xtext.builder.tests/build.properties b/org.eclipse.xtext.builder.tests/build.properties index 2a0a19760ec..9f22f62bf35 100644 --- a/org.eclipse.xtext.builder.tests/build.properties +++ b/org.eclipse.xtext.builder.tests/build.properties @@ -1,15 +1,13 @@ source.. = src/,\ src-gen/,\ - src-no-jdt/,\ - src-standalone/ + src-no-jdt/ output.. = bin/ bin.includes = model/generated/,\ model/,\ META-INF/,\ .,\ plugin.xml,\ - about.html,\ - test-data/ + about.html src.includes = about.html additional.bundles = org.eclipse.emf.mwe2.launch,\ org.eclipse.xtext.xtext.generator diff --git a/org.eclipse.xtext.builder.tests/pom.xml b/org.eclipse.xtext.builder.tests/pom.xml index a4d472799f3..57aa1513bdc 100644 --- a/org.eclipse.xtext.builder.tests/pom.xml +++ b/org.eclipse.xtext.builder.tests/pom.xml @@ -11,19 +11,4 @@ org.eclipse.xtext.builder.tests eclipse-test-plugin - diff --git a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/StandaloneBuilderInjectorProvider.java b/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/StandaloneBuilderInjectorProvider.java deleted file mode 100644 index 628d0026885..00000000000 --- a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/StandaloneBuilderInjectorProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2017 itemis AG (http://www.itemis.eu) and others. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.xtext.builder.standalone; - -import org.eclipse.xtext.builder.tests.BuilderTestLanguageStandaloneSetup; -import org.eclipse.xtext.testing.GlobalRegistries; -import org.eclipse.xtext.testing.GlobalRegistries.GlobalStateMemento; -import org.eclipse.xtext.testing.IInjectorProvider; -import org.eclipse.xtext.testing.IRegistryConfigurator; - -import com.google.inject.Guice; -import com.google.inject.Injector; - -/** - * @author Stefan Oehme - Initial contribution and API - */ -public class StandaloneBuilderInjectorProvider implements IInjectorProvider, IRegistryConfigurator { - - protected GlobalStateMemento stateBeforeInjectorCreation; - protected GlobalStateMemento stateAfterInjectorCreation; - protected Injector injector; - - static { - GlobalRegistries.initializeDefaults(); - } - - @Override - public Injector getInjector() { - if (injector == null) { - stateBeforeInjectorCreation = GlobalRegistries.makeCopyOfGlobalState(); - this.injector = internalCreateInjector(); - stateAfterInjectorCreation = GlobalRegistries.makeCopyOfGlobalState(); - } - return injector; - } - - protected Injector internalCreateInjector() { - new BuilderTestLanguageStandaloneSetup().createInjectorAndDoEMFRegistration(); - return Guice.createInjector(new StandaloneBuilderModule() { - @Override - protected void configure() { - super.configure(); - bind(StandaloneBuilder.class).to(bindStandaloneBuilder()); - } - - protected Class bindStandaloneBuilder() { - return TestableStandaloneBuilder.class; - } - - }); - } - - @Override - public void restoreRegistry() { - stateBeforeInjectorCreation.restoreGlobalState(); - } - - @Override - public void setupRegistry() { - getInjector(); - stateAfterInjectorCreation.restoreGlobalState(); - } - -} diff --git a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/StandaloneBuilderTest.java b/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/StandaloneBuilderTest.java deleted file mode 100644 index 25b16d6208b..00000000000 --- a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/StandaloneBuilderTest.java +++ /dev/null @@ -1,283 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014, 2017 itemis AG (http://www.itemis.eu) and others. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.xtext.builder.standalone; - -import static org.junit.Assert.*; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.plugin.EcorePlugin; -import org.eclipse.xtext.generator.IFileSystemAccess; -import org.eclipse.xtext.generator.OutputConfiguration; -import org.eclipse.xtext.generator.OutputConfiguration.SourceMapping; -import org.eclipse.xtext.testing.InjectWith; -import org.eclipse.xtext.testing.XtextRunner; -import org.eclipse.xtext.util.Files; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.inject.Inject; - -/** - * @author Stefan Oehme - Initial contribution and API - */ -@RunWith(XtextRunner.class) -@InjectWith(StandaloneBuilderInjectorProvider.class) -public class StandaloneBuilderTest { - - private static final File PROJECT_DIR = new File("test-data/standalone"); - private static final File TMP_DIR = new File(PROJECT_DIR, "tmp"); - - @Inject - private TestableStandaloneBuilder testBuilder; - - @Before - public void setUp() { - testBuilder.resetCallStatistic(); - testBuilder.resetTestSetup(); - } - - @After - public void cleanup() throws IOException { - deleteFolder("src-gen"); - deleteFolder("src2-gen"); - if (TMP_DIR.exists()) { - Files.sweepFolder(TMP_DIR); - TMP_DIR.delete(); - } - } - - @Test - public void testDifferentOutputFolders() { - initBuilder(new TestLanguageConfiguration(true)); - assertTrue(testBuilder.launch()); - - File generatedFile = getFile("src-gen/Foo.txt"); - assertTrue(generatedFile.exists()); - generatedFile = getFile("src2-gen/Bar.txt"); - assertTrue(generatedFile.exists()); - - File unexpectedFile = getFile("src-gen/Bar.txt"); - assertFalse(unexpectedFile.exists()); - unexpectedFile = getFile("src2-gen/Foo.txt"); - assertFalse(unexpectedFile.exists()); - } - - @Test - public void testWriteStorageResource() { - initBuilder(new TestLanguageConfiguration(true)); - testBuilder.setWriteStorageResources(true); - assertTrue(testBuilder.launch()); - - File generatedFile = getFile("src-gen/.Foo.buildertestlanguagebin"); - assertTrue(generatedFile.exists()); - } - - @Test - public void testNoWriteStorageResource() { - initBuilder(new TestLanguageConfiguration(true)); - testBuilder.setWriteStorageResources(false); - assertTrue(testBuilder.launch()); - - File generatedFile = getFile("src-gen/.Foo.buildertestlanguagebin"); - assertFalse(generatedFile.exists()); - } - - @Test - public void testSameOutputFolder() { - initBuilder(new TestLanguageConfiguration(false)); - assertTrue(testBuilder.launch()); - - File generatedFile = getFile("src-gen/Foo.txt"); - assertTrue(generatedFile.exists()); - generatedFile = getFile("src-gen/Bar.txt"); - assertTrue(generatedFile.exists()); - - File unexpectedFile = getFile("src2-gen/Bar.txt"); - assertFalse(unexpectedFile.exists()); - unexpectedFile = getFile("src2-gen/Foo.txt"); - assertFalse(unexpectedFile.exists()); - } - - @Test - public void testOnlyOneSourceFolder() { - initBuilder(new TestLanguageConfiguration(false)); - testBuilder.setSourceDirs(ImmutableList.of(new File(PROJECT_DIR, "src").getAbsolutePath())); - assertTrue(testBuilder.launch()); - - File generatedFile = getFile("src-gen/Foo.txt"); - assertTrue(generatedFile.exists()); - - File unexpectedFile = getFile("src-gen/Bar.txt"); - assertFalse(unexpectedFile.exists()); - unexpectedFile = getFile("src2-gen/Bar.txt"); - assertFalse(unexpectedFile.exists()); - unexpectedFile = getFile("src2-gen/Foo.txt"); - assertFalse(unexpectedFile.exists()); - } - - @Test - public void testRelativeSourceFolder() { - initBuilder(new TestLanguageConfiguration(false)); - testBuilder.setSourceDirs(ImmutableList.of("test-data/standalone/src")); - assertTrue(testBuilder.launch()); - - File generatedFile = getFile("src-gen/Foo.txt"); - assertTrue(generatedFile.exists()); - - File unexpectedFile = getFile("src-gen/Bar.txt"); - assertFalse(unexpectedFile.exists()); - unexpectedFile = getFile("src2-gen/Bar.txt"); - assertFalse(unexpectedFile.exists()); - unexpectedFile = getFile("src2-gen/Foo.txt"); - assertFalse(unexpectedFile.exists()); - } - - @Test - public void testJarToPlatformMapping() { - initBuilder(new TestLanguageConfiguration(false)); - testBuilder.setSourceDirs(ImmutableList.of("test-data/standalone.with.reference/model")); - testBuilder.setClassPathEntries(ImmutableList.of("test-data/standalone.with.reference/target/classes/", - "test-data/model.in.eclipse.project.jar")); - - assertTrue("Builder launch returned false", testBuilder.launch()); - URI uri = EcorePlugin.getPlatformResourceMap().get("model.in.eclipse.project"); - assertNotNull("No platform mapping found for 'model.in.eclipse.project'", uri); - assertTrue("Platform mapping is archive", uri.toString().startsWith("archive:file:/")); - assertTrue("Platform mapping points to jared project", - uri.toString().endsWith("test-data/model.in.eclipse.project.jar!/")); - } - - @Test - public void testDuplicateSourceEntries() { - TestLanguageConfiguration config = new TestLanguageConfiguration(false); - config.setJavaSupport(true); - initBuilder(config); - testBuilder.setJavaSourceDirs(ImmutableList.of(new File(PROJECT_DIR, "src2").getPath())); - testBuilder.setTempDir(TMP_DIR); - testBuilder.setDebugLog(true); - assertTrue("Builder launch returned false", testBuilder.launch()); - File compiledClazz = getFile("tmp/stub-classes/JavaClass.class"); - assertTrue("java compilation failed", compiledClazz.exists()); - - } - - @Test - public void testValidateMultipleResources() { - TestLanguageConfiguration config = new TestLanguageConfiguration(false); - initBuilder(config, "src", "src-error"); - testBuilder.setTempDir(TMP_DIR); - testBuilder.setDebugLog(true); - testBuilder.setMockGeneration(true); - - assertFalse("Build should return false, but returned -success-", testBuilder.launch()); - assertEquals("Build should fail early, but validate all resources", 2, testBuilder.getValidateCalled()); - assertEquals("Build should fail early", 0, testBuilder.getGenerateCalled()); - - // revert resource process order - https://bugs.eclipse.org/bugs/show_bug.cgi?id=464663 - initBuilder(config, "src-error", "src"); - - assertFalse("Build should fail, but returned -success-", testBuilder.launch()); - assertEquals("Build should fail early, but validation was executed", 2, testBuilder.getValidateCalled()); - assertEquals("Build should fail early", 0, testBuilder.getGenerateCalled()); - - // allow errors, generator should run in spite validation errors, but launch should return "validation error was found" - initBuilder(config, "src-error", "src"); - testBuilder.setFailOnValidationError(false); - assertFalse("Build should fail, but returned -success-", testBuilder.launch()); - - assertEquals("Validation was executed", 2, testBuilder.getValidateCalled()); - assertEquals("Generator was executed in spite of validation errors", 1, testBuilder.getGenerateCalled()); - - } - - private File getFile(String projectRelativePath) { - return new File(PROJECT_DIR, projectRelativePath); - } - - private void deleteFolder(String projectRelativePath) throws FileNotFoundException { - File folder = getFile(projectRelativePath); - if (folder.exists()) { - Files.sweepFolder(folder); - folder.delete(); - } - } - - private StandaloneBuilder initBuilder(ILanguageConfiguration config) { - return initBuilder(config, "src", "src2"); - } - - /** - * @param srcDirs - * source dirs from {@value #PROJECT_DIR} - */ - private StandaloneBuilder initBuilder(ILanguageConfiguration config, String... srcDirs) { - List patthes = new ArrayList(); - for (String srcDir : srcDirs) { - patthes.add(new File(PROJECT_DIR, srcDir).getAbsolutePath()); - } - testBuilder.setSourceDirs(patthes); - testBuilder.resetCallStatistic(); - Map languages = new LanguageAccessFactory().createLanguageAccess( - ImmutableList.of(config), getClass().getClassLoader()); - testBuilder.setBaseDir(PROJECT_DIR.getAbsolutePath()); - testBuilder.setLanguages(languages); - testBuilder.setClassPathEntries(ImmutableList. of()); - return testBuilder; - } - - public static class TestLanguageConfiguration implements ILanguageConfiguration { - - private boolean useOutputPerSource; - private boolean javaSupport = false; - - public TestLanguageConfiguration(boolean useOutputPerSource) { - this.useOutputPerSource = useOutputPerSource; - } - - /* @NonNull */ - @Override - public String getSetup() { - return "org.eclipse.xtext.builder.tests.BuilderTestLanguageStandaloneSetup"; - } - - @Override - public Set getOutputConfigurations() { - OutputConfiguration config = new OutputConfiguration(IFileSystemAccess.DEFAULT_OUTPUT); - config.setOutputDirectory("src-gen"); - if (useOutputPerSource) { - SourceMapping sourceMapping = new OutputConfiguration.SourceMapping("src2"); - sourceMapping.setOutputDirectory("src2-gen"); - config.getSourceMappings().add(sourceMapping); - config.setUseOutputPerSourceFolder(true); - } - return ImmutableSet.of(config); - } - - public void setJavaSupport(boolean javaSupport) { - this.javaSupport = javaSupport; - } - - @Override - public boolean isJavaSupport() { - return javaSupport; - } - } -} diff --git a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/TestEclipseCompiler.java b/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/TestEclipseCompiler.java deleted file mode 100644 index 5d5de275f79..00000000000 --- a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/TestEclipseCompiler.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2022 itemis AG (http://www.itemis.eu) and others. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.xtext.builder.standalone; - -import static org.junit.Assert.*; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.eclipse.emf.common.util.URI; -import org.eclipse.xtext.builder.standalone.compiler.IJavaCompiler; -import org.eclipse.xtext.builder.standalone.compiler.IJavaCompiler.CompilationResult; -import org.eclipse.xtext.mwe.PathTraverser; -import org.eclipse.xtext.util.Files; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.google.common.base.Predicate; -import com.google.common.collect.Lists; -import com.google.inject.Guice; -import com.google.inject.Injector; - -public class TestEclipseCompiler { - private final class ClassFileFilter implements Predicate { - @Override - public boolean apply(URI input) { - return "class".equals(input.fileExtension()); - } - } - - private static final String SRC_TEST_RESOURCES = "test-data/ec-test"; - private static final String DOES_NOT_EXISTS = "src/test/resources/test"; - private IJavaCompiler compiler; - private File outputClassDirectory; - private static Injector injector; - - @BeforeClass - public static void setUpOnce() { - injector = Guice.createInjector(new StandaloneBuilderModule()); - } - - @Before - public void setUp() { - compiler = injector.getInstance(IJavaCompiler.class); - compiler.getConfiguration().setVerbose(true); - outputClassDirectory = new File("target/temp"); - } - - @After - public void tearDown() throws FileNotFoundException { - if (outputClassDirectory != null && outputClassDirectory.exists()) { - assertTrue("Unable to delete test directory: " + outputClassDirectory.getAbsolutePath(), - Files.sweepFolder(outputClassDirectory)); - } - } - - @Test - public void testEmptySrcDirs() { - List sourceRoots = new ArrayList(); - sourceRoots.add(DOES_NOT_EXISTS); - assertEquals(CompilationResult.SKIPPED, compiler.compile(sourceRoots, outputClassDirectory)); - } - - @Test - public void testNonEmptySrcDirs() { - List sourceRoots = new ArrayList(); - sourceRoots.add(SRC_TEST_RESOURCES + "/test-class"); - sourceRoots.add(DOES_NOT_EXISTS); - assertEquals(CompilationResult.SUCCEEDED, compiler.compile(sourceRoots, new File("target/temp"))); - } - - @Test - public void testNoJavaSrcDirs() { - List sourceRoots = new ArrayList(); - sourceRoots.add(SRC_TEST_RESOURCES + "/test-nojava"); - assertEquals(CompilationResult.SKIPPED, compiler.compile(sourceRoots, new File("target/temp"))); - } - - @Test - public void testMultiSrcDirs() { - List sourceRoots = new ArrayList(); - sourceRoots.add(SRC_TEST_RESOURCES + "/test-class"); - sourceRoots.add(SRC_TEST_RESOURCES + "/test-class2"); - assertEquals(CompilationResult.SUCCEEDED, compiler.compile(sourceRoots, new File("target/temp"))); - Collection resolvePathes = collectOutputFiles(); - assertEquals("Should found 2 class files, but was: " + resolvePathes, 2, resolvePathes.size()); - } - - private Collection collectOutputFiles() { - return new PathTraverser().resolvePathes(Lists.newArrayList(outputClassDirectory.getAbsolutePath()), - new ClassFileFilter()).values(); - } - -} diff --git a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/TestableStandaloneBuilder.java b/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/TestableStandaloneBuilder.java deleted file mode 100644 index e09135d5836..00000000000 --- a/org.eclipse.xtext.builder.tests/src-standalone/org/eclipse/xtext/builder/standalone/TestableStandaloneBuilder.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 itemis AG (http://www.itemis.eu) and others. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -package org.eclipse.xtext.builder.standalone; - -import java.util.List; - -import org.eclipse.emf.ecore.resource.Resource; - -/** - * @author dhuebner - Initial contribution and API - */ -public class TestableStandaloneBuilder extends StandaloneBuilder { - private int validateCalled = 0; - private int generateCalled = 0; - private boolean mockGeneration = false; - - @Override - protected boolean validate(Resource resource) { - boolean validated = super.validate(resource); - validateCalled++; - return validated; - } - - public void resetTestSetup() { - mockGeneration = false; - } - - @Override - protected void generate(List sourceResources) { - if (!mockGeneration) { - super.generate(sourceResources); - } - generateCalled++; - } - - public void setMockGeneration(boolean mockGeneration) { - this.mockGeneration = mockGeneration; - } - - public void resetCallStatistic() { - validateCalled = 0; - generateCalled = 0; - } - - public int getValidateCalled() { - return validateCalled; - } - - public int getGenerateCalled() { - return generateCalled; - } - -} diff --git a/org.eclipse.xtext.builder.tests/test-data/ec-test/test-class/TestClass.java b/org.eclipse.xtext.builder.tests/test-data/ec-test/test-class/TestClass.java deleted file mode 100644 index 48cf7bcef7a..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/ec-test/test-class/TestClass.java +++ /dev/null @@ -1,11 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2022 itemis AG (http://www.itemis.eu) and others. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -public class TestClass { - -} diff --git a/org.eclipse.xtext.builder.tests/test-data/ec-test/test-class2/TestClass2.java b/org.eclipse.xtext.builder.tests/test-data/ec-test/test-class2/TestClass2.java deleted file mode 100644 index ee1c68a0700..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/ec-test/test-class2/TestClass2.java +++ /dev/null @@ -1,11 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2022 itemis AG (http://www.itemis.eu) and others. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -public class TestClass2 { - -} diff --git a/org.eclipse.xtext.builder.tests/test-data/ec-test/test-nojava/not-a-java.file b/org.eclipse.xtext.builder.tests/test-data/ec-test/test-nojava/not-a-java.file deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project.jar b/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project.jar deleted file mode 100644 index 8bd90775630..00000000000 Binary files a/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project.jar and /dev/null differ diff --git a/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project/META-INF/MANIFEST.MF b/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project/META-INF/MANIFEST.MF deleted file mode 100644 index 7ffaa392daa..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Bundle-SymbolicName: model.in.eclipse.project -Bundle-Version: 1.0.0.qualifier diff --git a/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project/model/ModelInJar.buildertestlanguage b/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project/model/ModelInJar.buildertestlanguage deleted file mode 100644 index e7cfbbb7874..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/model.in.eclipse.project/model/ModelInJar.buildertestlanguage +++ /dev/null @@ -1 +0,0 @@ -object ModelInJar \ No newline at end of file diff --git a/org.eclipse.xtext.builder.tests/test-data/standalone.with.reference/model/RefToJar.buildertestlanguage b/org.eclipse.xtext.builder.tests/test-data/standalone.with.reference/model/RefToJar.buildertestlanguage deleted file mode 100644 index e1d3f94c87b..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/standalone.with.reference/model/RefToJar.buildertestlanguage +++ /dev/null @@ -1,2 +0,0 @@ - -object MyMainModel references ModelInJar \ No newline at end of file diff --git a/org.eclipse.xtext.builder.tests/test-data/standalone/src-error/Bar.buildertestlanguage b/org.eclipse.xtext.builder.tests/test-data/standalone/src-error/Bar.buildertestlanguage deleted file mode 100644 index 23fba4d9ff7..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/standalone/src-error/Bar.buildertestlanguage +++ /dev/null @@ -1,2 +0,0 @@ -object Bar -objectERRORFoo \ No newline at end of file diff --git a/org.eclipse.xtext.builder.tests/test-data/standalone/src/Foo.buildertestlanguage b/org.eclipse.xtext.builder.tests/test-data/standalone/src/Foo.buildertestlanguage deleted file mode 100644 index d37c1045635..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/standalone/src/Foo.buildertestlanguage +++ /dev/null @@ -1 +0,0 @@ -object Foo \ No newline at end of file diff --git a/org.eclipse.xtext.builder.tests/test-data/standalone/src2/Bar.buildertestlanguage b/org.eclipse.xtext.builder.tests/test-data/standalone/src2/Bar.buildertestlanguage deleted file mode 100644 index 61a3a96a152..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/standalone/src2/Bar.buildertestlanguage +++ /dev/null @@ -1 +0,0 @@ -object Bar \ No newline at end of file diff --git a/org.eclipse.xtext.builder.tests/test-data/standalone/src2/JavaClass.java b/org.eclipse.xtext.builder.tests/test-data/standalone/src2/JavaClass.java deleted file mode 100644 index a42b6884905..00000000000 --- a/org.eclipse.xtext.builder.tests/test-data/standalone/src2/JavaClass.java +++ /dev/null @@ -1,11 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2022 itemis AG (http://www.itemis.eu) and others. - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *******************************************************************************/ -public class JavaClass { - -} diff --git a/org.eclipse.xtext.builder.tests/xtext.builder.tests.fast.launch b/org.eclipse.xtext.builder.tests/xtext.builder.tests.fast.launch index c1a9f6f1894..329e550031c 100644 --- a/org.eclipse.xtext.builder.tests/xtext.builder.tests.fast.launch +++ b/org.eclipse.xtext.builder.tests/xtext.builder.tests.fast.launch @@ -1,43 +1,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.xtext.builder.tests/xtext.builder.tests.nojdt.launch b/org.eclipse.xtext.builder.tests/xtext.builder.tests.nojdt.launch index effdf20825e..c8052d5f5c7 100644 --- a/org.eclipse.xtext.builder.tests/xtext.builder.tests.nojdt.launch +++ b/org.eclipse.xtext.builder.tests/xtext.builder.tests.nojdt.launch @@ -1,61 +1,64 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.xtext.builder.tests/xtext.standalone.builder.junit.launch b/org.eclipse.xtext.builder.tests/xtext.standalone.builder.junit.launch deleted file mode 100644 index ecaea3cd6a4..00000000000 --- a/org.eclipse.xtext.builder.tests/xtext.standalone.builder.junit.launch +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/org.eclipse.xtext.common.types.ui/src/org/eclipse/xtext/common/types/access/jdt/JdtTypeMirror.java b/org.eclipse.xtext.common.types.ui/src/org/eclipse/xtext/common/types/access/jdt/JdtTypeMirror.java index 7140424eeb6..4a1eaa86585 100644 --- a/org.eclipse.xtext.common.types.ui/src/org/eclipse/xtext/common/types/access/jdt/JdtTypeMirror.java +++ b/org.eclipse.xtext.common.types.ui/src/org/eclipse/xtext/common/types/access/jdt/JdtTypeMirror.java @@ -11,9 +11,12 @@ import java.util.Map; import org.apache.log4j.Logger; +import org.eclipse.core.runtime.IPath; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.jdt.core.IType; import org.eclipse.xtext.common.types.JvmDeclaredType; import org.eclipse.xtext.common.types.access.IMirrorOptionsAware; @@ -79,6 +82,16 @@ public void initialize(TypeResource typeResource, Map options) { } this.typeResource = typeResource; } + + @Override + public URI getLocationURI(Resource resource) { + IPath path = mirroredType.getPath(); + if (mirroredType.getResource() != null) { + return URI.createPlatformResourceURI(path.toString(), true); + } else { + return URI.createFileURI(path.toString()); + } + } @Override protected String getTypeName() { diff --git a/org.eclipse.xtext.common.types/META-INF/MANIFEST.MF b/org.eclipse.xtext.common.types/META-INF/MANIFEST.MF index 559be68da80..abf9af14ee4 100644 --- a/org.eclipse.xtext.common.types/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext.common.types/META-INF/MANIFEST.MF @@ -30,8 +30,9 @@ Export-Package: org.eclipse.xtext.common.types;version="2.35.0", org.eclipse.xtend.core.tests, org.eclipse.xtend.ide.tests, org.eclipse.xtext.xbase.ui.testing, - org.eclipse.xtext.xbase.tests", - org.eclipse.xtext.common.types.access.binary;version="2.35.0";x-friends:="org.eclipse.xtext.common.types.tests,org.eclipse.xtext.java", + org.eclipse.xtext.xbase.tests, + org.eclipse.xtext.xbase.ide", + org.eclipse.xtext.common.types.access.binary;version="2.35.0";x-friends:="org.eclipse.xtext.common.types.tests,org.eclipse.xtext.java,org.eclipse.xtext.xbase.ide", org.eclipse.xtext.common.types.access.binary.asm;version="2.35.0";x-friends:="org.eclipse.xtext.common.types.tests,org.eclipse.xtext.common.types.ui,org.eclipse.xtext.java", org.eclipse.xtext.common.types.access.impl;version="2.35.0"; x-friends:="org.eclipse.xtext.common.types.tests, @@ -47,7 +48,8 @@ Export-Package: org.eclipse.xtext.common.types;version="2.35.0", org.eclipse.xtend.core, org.eclipse.xtend.core.tests, org.eclipse.xtend.ide.tests, - org.eclipse.xtext.xbase.tests", + org.eclipse.xtext.xbase.tests, + org.eclipse.xtext.xbase.ide", org.eclipse.xtext.common.types.access.reflect;version="2.35.0";x-friends:="org.eclipse.xtext.common.types.tests", org.eclipse.xtext.common.types.descriptions;version="2.35.0"; x-friends:="org.eclipse.xtext.builder.standalone, diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/IJvmTypeProvider.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/IJvmTypeProvider.java index 3d42128c482..bbd56d15e4b 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/IJvmTypeProvider.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/IJvmTypeProvider.java @@ -51,6 +51,13 @@ public interface IJvmTypeProvider { */ ResourceSet getResourceSet(); + /** + * @since 2.35 + */ + default void clearCache() { + // nothing to do by default + } + interface Factory { IJvmTypeProvider createTypeProvider(ResourceSet resourceSet); diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClass.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClass.java index ff868ddb408..680cc44c27e 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClass.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClass.java @@ -12,6 +12,7 @@ import java.io.InputStream; import java.net.URL; +import org.apache.log4j.Logger; import org.eclipse.emf.common.util.URI; import org.eclipse.xtext.common.types.access.impl.URIHelperConstants; @@ -31,6 +32,8 @@ * @author Sebastian Zarnekow - Initial contribution and API */ public class BinaryClass { + + private static final Logger logger = Logger.getLogger(BinaryClass.class); private final String name; private final ClassLoader classLoader; @@ -68,6 +71,22 @@ private String getOutermostClassName(int offset) { public String getName() { return name; } + + /** + * @since 2.35 + */ + public URI getLocationURI() { + try { + URL resource = classLoader.getResource(toClassFile(name)); + if (resource != null) { + return URI.createURI(resource.toString(), true); + } + return null; + } catch(Exception e) { + logger.error(e.getMessage(), e); + return null; + } + } public byte[] getBytes() { InputStream stream = null; diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClassMirror.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClassMirror.java index 7e26e94fe5b..dfe96f420ae 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClassMirror.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/BinaryClassMirror.java @@ -8,6 +8,8 @@ *******************************************************************************/ package org.eclipse.xtext.common.types.access.binary; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.common.types.JvmDeclaredType; import org.eclipse.xtext.common.types.access.TypeResource; import org.eclipse.xtext.common.types.access.impl.AbstractClassMirror; @@ -19,18 +21,35 @@ public class BinaryClassMirror extends AbstractClassMirror { private final BinaryClass binaryClass; + private final boolean sealed; private final ITypeFactory typeFactory; public static BinaryClassMirror createClassMirror(BinaryClass binaryClass, ITypeFactory typeFactory) { + return createClassMirror(binaryClass, typeFactory, true); + } + + /** + * @since 2.35 + */ + public static BinaryClassMirror createClassMirror(BinaryClass binaryClass, ITypeFactory typeFactory, boolean sealed) { if (binaryClass.isPrimitive() || binaryClass.isArray()) throw new IllegalArgumentException("Cannot create class mirror for " + binaryClass.getName()); - return new BinaryClassMirror(binaryClass, typeFactory); + return new BinaryClassMirror(binaryClass, typeFactory, sealed); } - protected BinaryClassMirror(BinaryClass binaryClass, ITypeFactory typeFactory) { + /** + * @since 2.35 + */ + protected BinaryClassMirror(BinaryClass binaryClass, ITypeFactory typeFactory, boolean sealed) { this.binaryClass = binaryClass; this.typeFactory = typeFactory; + this.sealed = sealed; + } + + @Deprecated + protected BinaryClassMirror(BinaryClass binaryClass, ITypeFactory typeFactory) { + this(binaryClass, typeFactory, true); } @Override @@ -47,6 +66,11 @@ public BinaryClass getMirroredBinaryClass() { return binaryClass; } + @Override + public URI getLocationURI(Resource resource) { + return binaryClass.getLocationURI(); + } + public Class getMirroredClass() { try { return Class.forName(binaryClass.getName(), false, binaryClass.getClassLoader()); @@ -57,6 +81,6 @@ public Class getMirroredClass() { @Override public boolean isSealed() { - return true; + return sealed; } } diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/asm/ClassFileBytesAccess.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/asm/ClassFileBytesAccess.java index 38b8bb64988..1e9430ff870 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/asm/ClassFileBytesAccess.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/binary/asm/ClassFileBytesAccess.java @@ -60,4 +60,10 @@ public byte[] getBytes(BinaryClass clazz) { return result; } + /** + * @since 2.35 + */ + public void clearCache() { + cache.clear(); + } } diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/AbstractClassFinder.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/AbstractClassFinder.java index 8667fee9852..f2e67a5ccc8 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/AbstractClassFinder.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/AbstractClassFinder.java @@ -50,7 +50,14 @@ public C forName(String name) throws ClassNotFoundException { } } - protected abstract C forName(String name, ClassLoader classLoader) throws ClassNotFoundException ; + protected abstract C forName(String name, ClassLoader classLoader) throws ClassNotFoundException; + + /** + * @since 2.35 + */ + public void clearCache() { + cache.clear(); + } } diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingClasspathTypeProvider.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingClasspathTypeProvider.java index 4605383fc59..8ceab90b171 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingClasspathTypeProvider.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingClasspathTypeProvider.java @@ -49,5 +49,17 @@ public ITypeFactory getDeclaredTypeFactory() { public BinaryClassMirror createMirror(BinaryClass clazz) { return BinaryClassMirror.createClassMirror(clazz, reusedFactory); } + + /** + * @since 2.35 + */ + @Override + public void clearCache() { + super.clearCache(); + if (reusedFactory instanceof CachingDeclaredTypeFactory) { + ((CachingDeclaredTypeFactory)reusedFactory).clearCache(); + } + + } } diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingDeclaredTypeFactory.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingDeclaredTypeFactory.java index 950d9cf9a2a..471926b887e 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingDeclaredTypeFactory.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/CachingDeclaredTypeFactory.java @@ -67,6 +67,13 @@ public JvmDeclaredType createType(BinaryClass clazz) { return delegate.createType(clazz); } } + + /** + * @since 2.35 + */ + public void clearCache() { + typeCache.clear(); + } private JvmDeclaredType get(BinaryClass clazz) { String name = clazz.getName(); diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/ClasspathTypeProvider.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/ClasspathTypeProvider.java index 60448d19311..4cbea6a0ddb 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/ClasspathTypeProvider.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/ClasspathTypeProvider.java @@ -184,6 +184,15 @@ public BinaryClassFinder getClassFinder() { return classFinder; } + /** + * @since 2.35 + */ + @Override + public void clearCache() { + classFinder.clearCache(); + readerAccess.clearCache(); + } + @Override public JvmType findTypeByName(String name) { try { diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/DeclaredTypeFactory.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/DeclaredTypeFactory.java index 58457b28236..611fab38c01 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/DeclaredTypeFactory.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/DeclaredTypeFactory.java @@ -104,7 +104,7 @@ public JvmDeclaredType createType(BinaryClass binaryClass) { try { ReflectURIHelper uriHelper = new ReflectURIHelper(); ReflectionTypeFactory reflectionBased = new ReflectionTypeFactory(uriHelper); - Class clazz = Class.forName(binaryClass.getName(), false, classLoader); + Class clazz = Class.forName(binaryClass.getName(), false, getClassLoader()); return reflectionBased.createType(clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); @@ -113,8 +113,22 @@ public JvmDeclaredType createType(BinaryClass binaryClass) { } protected JvmDeclaredType doCreateType(BinaryClass binaryClass) { - JvmDeclaredTypeBuilder builder = new JvmDeclaredTypeBuilder(binaryClass, bytesAccess, classLoader); + JvmDeclaredTypeBuilder builder = new JvmDeclaredTypeBuilder(binaryClass, getBytesAccess(), getClassLoader()); return builder.buildType(); } + + /** + * @since 2.35 + */ + protected ClassFileBytesAccess getBytesAccess() { + return bytesAccess; + } + + /** + * @since 2.35 + */ + protected ClassLoader getClassLoader() { + return classLoader; + } } diff --git a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/IClassMirror.java b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/IClassMirror.java index d4de1535c42..fb9fcce0f97 100644 --- a/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/IClassMirror.java +++ b/org.eclipse.xtext.common.types/src/org/eclipse/xtext/common/types/access/impl/IClassMirror.java @@ -8,6 +8,8 @@ *******************************************************************************/ package org.eclipse.xtext.common.types.access.impl; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.common.types.access.IMirror; import org.eclipse.xtext.common.types.access.IMirrorExtension; @@ -15,4 +17,13 @@ * @author Sebastian Zarnekow - Initial contribution and API */ public interface IClassMirror extends IMirror, IMirrorExtension { + + /** + * Return the location URI of this class mirror. Returns null if none is available. + * @since 2.35 + */ + default URI getLocationURI(Resource resource) { + return null; + } + } diff --git a/org.eclipse.xtext.ide/META-INF/MANIFEST.MF b/org.eclipse.xtext.ide/META-INF/MANIFEST.MF index 6ffb380ab84..f682f13e417 100644 --- a/org.eclipse.xtext.ide/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext.ide/META-INF/MANIFEST.MF @@ -38,7 +38,7 @@ Export-Package: org.eclipse.xtext.ide;version="2.35.0"; org.eclipse.xtext.ide.editor.partialEditing;version="2.35.0", org.eclipse.xtext.ide.editor.quickfix;version="2.35.0", org.eclipse.xtext.ide.editor.syntaxcoloring;version="2.35.0", - org.eclipse.xtext.ide.labels;version="2.35.0";x-friends:="org.eclipse.xtext.web", + org.eclipse.xtext.ide.labels;version="2.35.0";x-friends:="org.eclipse.xtext.web,org.eclipse.xtext.xbase.ide", org.eclipse.xtext.ide.refactoring;version="2.35.0";x-friends:="org.eclipse.xtext.testlanguages.ide,org.eclipse.xtext.ui", org.eclipse.xtext.ide.serializer;version="2.35.0";x-friends:="org.eclipse.xtext.ide.tests,org.eclipse.xtext.ui", org.eclipse.xtext.ide.serializer.debug;version="2.35.0";x-friends:="org.eclipse.xtext.ide.tests,org.eclipse.xtext.testing", @@ -60,8 +60,8 @@ Export-Package: org.eclipse.xtext.ide;version="2.35.0"; org.eclipse.xtext.ide.server.hover;version="2.35.0", org.eclipse.xtext.ide.server.occurrences;version="2.35.0", org.eclipse.xtext.ide.server.rename;version="2.35.0";x-internal:=true, - org.eclipse.xtext.ide.server.signatureHelp;version="2.35.0", org.eclipse.xtext.ide.server.semantictokens;version="2.35.0", + org.eclipse.xtext.ide.server.signatureHelp;version="2.35.0", org.eclipse.xtext.ide.server.symbol;version="2.35.0", org.eclipse.xtext.ide.util;version="2.35.0" Automatic-Module-Name: org.eclipse.xtext.ide diff --git a/org.eclipse.xtext.java/src/org/eclipse/xtext/java/resource/JavaResource.java b/org.eclipse.xtext.java/src/org/eclipse/xtext/java/resource/JavaResource.java index 6eaabd71431..ac76f6113e6 100644 --- a/org.eclipse.xtext.java/src/org/eclipse/xtext/java/resource/JavaResource.java +++ b/org.eclipse.xtext.java/src/org/eclipse/xtext/java/resource/JavaResource.java @@ -109,6 +109,11 @@ public void initialize(TypeResource typeResource) { public boolean isSealed() { return true; } + + @Override + public URI getLocationURI(Resource resource) { + return resource.getURI(); + } } public static final String OPTION_ENCODING = JavaResource.class.getName() + ".DEFAULT_ENCODING"; @@ -308,6 +313,11 @@ public void initialize(TypeResource typeResource) { public boolean isSealed() { throw new UnsupportedOperationException("TODO: auto-generated method stub"); } + + @Override + public URI getLocationURI(Resource resource) { + return resource.getURI(); + } }; @Override diff --git a/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/AbstractXtextGeneratorMojo.java b/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/AbstractXtextGeneratorMojo.java index 96887271e2b..2de82c92557 100644 --- a/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/AbstractXtextGeneratorMojo.java +++ b/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/AbstractXtextGeneratorMojo.java @@ -15,6 +15,7 @@ import java.util.stream.Collectors; import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Parameter; @@ -159,6 +160,25 @@ public String getEncoding() { */ @Parameter(defaultValue = "false") private boolean includePluginDependencies; + + /** + * Location to which the class-path configuration shall be written. The file format is internal + * to the {@link StandaloneBuilder}. + * @see #writeClassPathConfigurationLocation + */ + @Parameter(defaultValue = "${project.build.directory}/xtext.classpath") + private String classpathConfigurationLocation; + + /** + * Allows to write the class-path configuration to a file. The file format is internal + * to the {@link StandaloneBuilder}. + * @see #classpathConfigurationLocation + */ + @Parameter(defaultValue = "false") + private boolean writeClasspathConfiguration = false; + + @Parameter( defaultValue = "${mojoExecution}", readonly = true ) + private MojoExecution mojoExecution; /* * (non-Javadoc) @@ -198,8 +218,12 @@ protected void internalExecute() throws MojoExecutionException { builder.setDebugLog(getLog().isDebugEnabled()); builder.setIncrementalBuild(incrementalXtextBuild); builder.setWriteStorageResources(writeStorageResources); - if (clusteringConfig != null) + if (writeClasspathConfiguration) { + builder.setClasspathConfigurationLocation(classpathConfigurationLocation, mojoExecution.getGoal(), getClassOutputDirectory()); + } + if (clusteringConfig != null) { builder.setClusteringConfig(clusteringConfig.convertToStandaloneConfig()); + } configureCompiler(builder.getCompiler()); logState(); boolean errorDetected = !builder.launch(); @@ -208,6 +232,8 @@ protected void internalExecute() throws MojoExecutionException { } } + protected abstract String getClassOutputDirectory(); + protected abstract List getSourceRoots(); private void configureCompiler(IJavaCompiler compiler) { diff --git a/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextGenerateMojo.java b/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextGenerateMojo.java index d874df8cb3c..abbb4abfbc2 100644 --- a/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextGenerateMojo.java +++ b/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextGenerateMojo.java @@ -38,7 +38,7 @@ public class XtextGenerateMojo extends AbstractXtextGeneratorMojo { public Set getClasspathElements() { Set classpathElements = newLinkedHashSet(); classpathElements.addAll(this.classpathElements); - classpathElements.remove(getProject().getBuild().getOutputDirectory()); + classpathElements.remove(getClassOutputDirectory()); classpathElements.remove(getProject().getBuild().getTestOutputDirectory()); Set nonEmptyElements = newLinkedHashSet(filter(classpathElements, emptyStringFilter())); return nonEmptyElements; @@ -51,6 +51,11 @@ protected void configureMavenOutputs() { } } + @Override + protected String getClassOutputDirectory() { + return getProject().getBuild().getOutputDirectory(); + } + /** * Project source roots. List of folders, where the source models are * located.
diff --git a/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextTestGenerateMojo.java b/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextTestGenerateMojo.java index 4d8490309bc..20bdbc0b3b9 100644 --- a/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextTestGenerateMojo.java +++ b/org.eclipse.xtext.maven.plugin/src/main/java/org/eclipse/xtext/maven/XtextTestGenerateMojo.java @@ -38,7 +38,7 @@ public class XtextTestGenerateMojo extends AbstractXtextGeneratorMojo { public Set getClasspathElements() { Set classpathElementSet = newLinkedHashSet(); classpathElementSet.addAll(this.classpathElements); - classpathElementSet.remove(getProject().getBuild().getTestOutputDirectory()); + classpathElementSet.remove(getClassOutputDirectory()); return newLinkedHashSet(filter(classpathElementSet, emptyStringFilter())); } @@ -53,6 +53,11 @@ protected String tmpDirSuffix() { return "-test"; } + @Override + protected String getClassOutputDirectory() { + return getProject().getBuild().getTestOutputDirectory(); + } + /** * Project test source roots. List of folders, where the test source models are * located.
diff --git a/org.eclipse.xtext.xbase.ide/META-INF/MANIFEST.MF b/org.eclipse.xtext.xbase.ide/META-INF/MANIFEST.MF index 754f62ba73e..832c31a2ddb 100644 --- a/org.eclipse.xtext.xbase.ide/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext.xbase.ide/META-INF/MANIFEST.MF @@ -10,16 +10,21 @@ Require-Bundle: org.eclipse.xtext.ide;bundle-version="2.35.0", org.eclipse.xtext.xbase;bundle-version="2.35.0";visibility:=reexport, org.eclipse.xtext.xbase.lib;bundle-version="2.35.0", org.objectweb.asm;bundle-version="[9.7.0,9.8.0)", - org.antlr.runtime;bundle-version="[3.2.0,3.2.1)" + org.antlr.runtime;bundle-version="[3.2.0,3.2.1)", + org.eclipse.lsp4j;bundle-version="[0.22.0,0.23.0)";resolution:=optional Export-Package: org.eclipse.xtext.xbase.annotations.ide.contentassist.antlr;version="2.35.0";x-friends:="org.eclipse.xtext.xbase.ui", org.eclipse.xtext.xbase.annotations.ide.contentassist.antlr.internal;version="2.35.0";x-friends:="org.eclipse.xtext.xbase.ui", org.eclipse.xtext.xbase.ide;version="2.35.0", org.eclipse.xtext.xbase.ide.contentassist;version="2.35.0";x-friends:="org.eclipse.xtext.xbase.ui,org.eclipse.xtend.ide", org.eclipse.xtext.xbase.ide.contentassist.antlr;version="2.35.0";x-friends:="org.eclipse.xtext.xbase.ui", org.eclipse.xtext.xbase.ide.contentassist.antlr.internal;version="2.35.0";x-friends:="org.eclipse.xtext.xbase.ui", - org.eclipse.xtext.xbase.ide.highlighting;version="2.35.0";x-friends:="org.eclipse.xtext.xbase.ui, + org.eclipse.xtext.xbase.ide.highlighting;version="2.35.0"; + x-friends:="org.eclipse.xtext.xbase.ui, org.eclipse.xtend.ide, org.eclipse.xtend.ide.common, - org.eclipse.xtend.ide.tests" + org.eclipse.xtend.ide.tests", + org.eclipse.xtext.xbase.ide.hover;version="2.35.0";x-friends:="org.eclipse.xtext.xbase.ui", + org.eclipse.xtext.xbase.lsp;version="2.35.0";x-internal:=true +Import-Package: org.apache.log4j;version="1.2.25" Automatic-Module-Name: org.eclipse.xtext.xbase.ide Eclipse-SourceReferences: eclipseSourceReferences diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/ide/hover/HoverIdeStrings.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/ide/hover/HoverIdeStrings.java new file mode 100644 index 00000000000..5281f91ea9c --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/ide/hover/HoverIdeStrings.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.ide.hover; + +import static com.google.common.collect.Iterables.*; + +import java.util.List; + +import org.eclipse.xtext.common.types.JvmFormalParameter; +import org.eclipse.xtext.common.types.JvmType; +import org.eclipse.xtext.common.types.JvmTypeConstraint; +import org.eclipse.xtext.common.types.JvmTypeParameter; +import org.eclipse.xtext.xbase.validation.UIStrings; + +/** + * @since 2.35 + */ +public class HoverIdeStrings extends UIStrings { + + @Override + public String typeParameters(Iterable typeParams) { + if (!isEmpty(typeParams)) { + StringBuilder result = new StringBuilder("<"); + boolean needsSeparator = false; + OUTER: for (JvmTypeParameter typeParam : typeParams) { + if (needsSeparator) + result.append(", "); + needsSeparator = true; + if(typeParam != null) { + result.append(typeParam.getSimpleName()); + List constraints = typeParam.getConstraints(); + if (!constraints.isEmpty()) { + if (constraints.size() == 1) { + JvmType singleConstraint = constraints.get(0).getTypeReference().getType(); + if (Object.class.getName().equals(singleConstraint.getIdentifier())) { + continue OUTER; + } + } + result.append(" extends "); + for(int i = 0; i < constraints.size(); i++) { + if (i != 0) { + result.append(" & "); + } + result.append(constraints.get(i).getTypeReference().getSimpleName()); + } + } + } else + result.append("[null]"); + } + return result.append(">").toString(); + } + return ""; + } + + @Override + protected String parameterTypes(Iterable parameters, boolean isVarArgs) { + return parametersToString(parameters, isVarArgs, true); + } +} \ No newline at end of file diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/ide/hover/XbaseHoverService.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/ide/hover/XbaseHoverService.java new file mode 100644 index 00000000000..7a05a0ec9dd --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/ide/hover/XbaseHoverService.java @@ -0,0 +1,373 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.ide.hover; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.common.types.JvmAnnotationType; +import org.eclipse.xtext.common.types.JvmAnyTypeReference; +import org.eclipse.xtext.common.types.JvmConstructor; +import org.eclipse.xtext.common.types.JvmEnumerationType; +import org.eclipse.xtext.common.types.JvmExecutable; +import org.eclipse.xtext.common.types.JvmField; +import org.eclipse.xtext.common.types.JvmFormalParameter; +import org.eclipse.xtext.common.types.JvmGenericType; +import org.eclipse.xtext.common.types.JvmIdentifiableElement; +import org.eclipse.xtext.common.types.JvmMember; +import org.eclipse.xtext.common.types.JvmOperation; +import org.eclipse.xtext.common.types.JvmTypeParameter; +import org.eclipse.xtext.common.types.JvmTypeReference; +import org.eclipse.xtext.documentation.IEObjectDocumentationProvider; +import org.eclipse.xtext.ide.labels.INameLabelProvider; +import org.eclipse.xtext.ide.server.hover.HoverService; +import org.eclipse.xtext.util.PolymorphicDispatcher; +import org.eclipse.xtext.util.PolymorphicDispatcher.ErrorHandler; +import org.eclipse.xtext.xbase.XAbstractFeatureCall; +import org.eclipse.xtext.xbase.XConstructorCall; +import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver; +import org.eclipse.xtext.xbase.typesystem.IResolvedTypes; +import org.eclipse.xtext.xbase.typesystem.override.InvokedResolvedOperation; +import org.eclipse.xtext.xbase.typesystem.override.ResolvedConstructor; +import org.eclipse.xtext.xbase.typesystem.references.LightweightMergedBoundTypeArgument; +import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference; +import org.eclipse.xtext.xbase.typesystem.util.VarianceInfo; +import org.eclipse.xtext.xbase.validation.UIStrings; + +import com.google.inject.Inject; + +/* + * Implementation note: + * Initially adopted from XbaseDeclarativeHoverSignatureProvider + */ + +/** + * @since 2.35 + */ +public class XbaseHoverService extends HoverService { + @Inject + private IEObjectDocumentationProvider eObjectDocumentationProvider; + + @Inject + private INameLabelProvider nameLabelProvider; + + @Inject + protected UIStrings uiStrings; + + @Inject + private IBatchTypeResolver typeResolver; + + @Inject + private InvokedResolvedOperation.Provider invokedOperationProvider; + + @Override + public String getContents(EObject element) { + String documentation = eObjectDocumentationProvider.getDocumentation(element); + if (documentation == null) { + return getSignature(element); + } else { + return getSignature(element) + " \n" + documentation; + } + } + + public String getSignature(EObject object) { + return internalGetSignature(object, true); + } + + protected String _signature(XConstructorCall constructorCall, boolean typeAtEnd) { + if (typeAtEnd) { + throw new UnsupportedOperationException(); + } + IResolvedTypes resolvedTypes = typeResolver.resolveTypes(constructorCall); + LightweightTypeReference createdType = resolvedTypes.getActualType(constructorCall); + final List typeArguments = resolvedTypes.getActualTypeArguments(constructorCall); + final int typeArgumentCount = createdType.getTypeArguments().size(); + final int constructorTypeArgumentCount = typeArguments.size(); + final JvmConstructor constructor = constructorCall.getConstructor(); + ResolvedConstructor resolvedConstructor = new ResolvedConstructor(constructor, createdType) { + @Override + protected Map computeContextTypeParameterMapping() { + Map result = super.computeContextTypeParameterMapping(); + if (typeArgumentCount == constructorTypeArgumentCount) + return result; + List constructorTypeParameters = getDeclaration().getTypeParameters(); + for (int i = 0; i < constructorTypeParameters.size(); i++) { + result.put(constructorTypeParameters.get(i), + new LightweightMergedBoundTypeArgument(typeArguments.get(i), VarianceInfo.INVARIANT)); + } + return result; + } + }; + StringBuilder result = new StringBuilder(250); + if (typeArgumentCount != constructorTypeArgumentCount) { + result.append("<"); + for (int i = 0; i < constructorTypeArgumentCount - typeArgumentCount; i++) { + if (i != 0) { + result.append(", "); + } + result.append(typeArguments.get(i).getHumanReadableName()); + } + result.append("> "); + } + result.append(constructor.getDeclaringType().getSimpleName()); + if (typeArgumentCount != 0) { + result.append("<"); + for (int i = constructorTypeArgumentCount - typeArgumentCount; i < constructorTypeArgumentCount; i++) { + if (i != constructorTypeArgumentCount - typeArgumentCount) { + result.append(", "); + } + result.append(typeArguments.get(i).getHumanReadableName()); + } + result.append(">"); + } + result.append('('); + List parameterTypes = resolvedConstructor.getResolvedParameterTypes(); + for (int i = 0; i < parameterTypes.size(); i++) { + if (i != 0) { + result.append(", "); + } + result.append(parameterTypes.get(i).getHumanReadableName()); + result.append(' ').append(constructor.getParameters().get(i).getSimpleName()); + } + result.append(')'); + List exceptions = resolvedConstructor.getResolvedExceptions(); + if (!exceptions.isEmpty()) { + result.append(" throws "); + for (int i = 0; i < exceptions.size(); i++) { + if (i != 0) { + result.append(", "); + } + result.append(exceptions.get(i).getHumanReadableName()); + } + } + return result.toString(); + } + + protected String _signature(XAbstractFeatureCall featureCall, boolean typeAtEnd) { + if (typeAtEnd) { + throw new UnsupportedOperationException(); + } + JvmIdentifiableElement feature = featureCall.getFeature(); + if (feature instanceof JvmOperation) { + InvokedResolvedOperation resolvedOperation = invokedOperationProvider.resolve(featureCall); + StringBuilder result = new StringBuilder(250); + List typeArguments = resolvedOperation.getResolvedTypeArguments(); + if (!typeArguments.isEmpty()) { + result.append("<"); + for (int i = 0; i < typeArguments.size(); i++) { + if (i != 0) { + result.append(", "); + } + result.append(typeArguments.get(i).getHumanReadableName()); + } + result.append("> "); + } + result.append(resolvedOperation.getResolvedReturnType().getHumanReadableName()).append(' '); + JvmOperation operation = resolvedOperation.getDeclaration(); + result.append(getDeclaratorName(operation)).append('.'); + result.append(operation.getSimpleName()).append('('); + List parameterTypes = resolvedOperation.getResolvedParameterTypes(); + for (int i = 0; i < parameterTypes.size(); i++) { + if (i != 0) { + result.append(", "); + } + result.append(parameterTypes.get(i).getHumanReadableName()); + result.append(' ').append(operation.getParameters().get(i).getSimpleName()); + } + result.append(')'); + List exceptions = resolvedOperation.getResolvedExceptions(); + if (!exceptions.isEmpty()) { + result.append(" throws "); + for (int i = 0; i < exceptions.size(); i++) { + if (i != 0) { + result.append(", "); + } + result.append(exceptions.get(i).getHumanReadableName()); + } + } + return result.toString(); + } else if (feature instanceof JvmConstructor) { + // TODO this or super + // see ignored tests in + } else if (feature instanceof JvmField) { + LightweightTypeReference referenceType = typeResolver.resolveTypes(featureCall).getActualType(featureCall); + StringBuilder result = new StringBuilder(250); + result.append(referenceType.getHumanReadableName()).append(' '); + JvmField field = (JvmField) feature; + result.append(getDeclaratorName(field)).append('.'); + result.append(field.getSimpleName()); + return result.toString(); + } else { + String simpleName = feature.getSimpleName(); + String type = typeResolver.resolveTypes(featureCall).getActualType(featureCall).getHumanReadableName(); + if (simpleName != null) { + return type + ' ' + simpleName; + } else { + return type; + } + } + return getSignature(feature); + } + + protected String getDeclaratorName(JvmMember member) { + return member.getDeclaringType().getSimpleName(); + } + + public String getDerivedOrSourceSignature(EObject object) { + return internalGetSignature(object, true); + } + + protected String internalGetSignature(EObject object, boolean typeAtEnd) { + PolymorphicDispatcher polymorphicDispatcher = new PolymorphicDispatcher("_signature", 2, 2, + Collections.singletonList(this), new ErrorHandler() { + @Override + public String handle(Object[] params, Throwable throwable) { + return null; + } + }); + String result = polymorphicDispatcher.invoke(object, typeAtEnd); + if (result != null) + return result; + if (object instanceof JvmIdentifiableElement) { + return getLabel(object); + } + return getLabel(object); + } + + protected String _signature(JvmGenericType clazz, boolean typeAtEnd) { + return clazz.getSimpleName() + uiStrings.typeParameters(clazz.getTypeParameters()); + } + + protected String _signature(JvmOperation jvmOperation, boolean typeAtEnd) { + String returnTypeString = "void"; + // TODO resolved operations? + JvmTypeReference returnType = jvmOperation.getReturnType(); + if (returnType != null) { + if (returnType instanceof JvmAnyTypeReference) { + throw new IllegalStateException(); +// returnTypeString = "Object"; + } else { + returnTypeString = returnType.getSimpleName(); + } + } + + String signature = jvmOperation.getSimpleName() + uiStrings.parameters(jvmOperation) + + getThrowsDeclaration(jvmOperation); + String typeParameter = uiStrings.typeParameters(jvmOperation.getTypeParameters()); + if (typeParameter != null && typeParameter.length() > 0) { + if (typeAtEnd) + return signature + " " + typeParameter + " : " + returnTypeString; + return typeParameter + " " + returnTypeString + " " + signature; + } + if (typeAtEnd) + return signature + " : " + returnTypeString; + return returnTypeString + " " + enrichWithDeclarator(signature, jvmOperation); + } + + protected String _signature(JvmField jvmField, boolean typeAtEnd) { + JvmTypeReference type = jvmField.getType(); + if (type != null) { + String signature = jvmField.getSimpleName(); + if (typeAtEnd) + return signature + " : " + type.getSimpleName(); + return type.getSimpleName() + " " + enrichWithDeclarator(signature, jvmField); + } + return ""; + } + + protected String enrichWithDeclarator(String signature, EObject o) { + if (o instanceof JvmMember && ((JvmMember) o).getDeclaringType() != null) { + String parentsName = getDeclaratorName((JvmMember) o); + return parentsName + "." + signature; + } + return signature; + } + + protected String _signature(JvmConstructor constructor, boolean typeAtEnd) { + return constructor.getSimpleName() + uiStrings.typeParameters(constructor.getDeclaringType()) + + uiStrings.parameters(constructor) + getThrowsDeclaration(constructor); + } + + protected String _signature(JvmFormalParameter parameter, boolean typeAtEnd) { + EObject container = parameter.eContainer(); + LightweightTypeReference parameterType = typeResolver.resolveTypes(parameter).getActualType(parameter); + if (parameterType != null) { + String signature = parameter.getName(); + String signatureOfFather = getSimpleSignature(container); + if (signatureOfFather != null) { + signature += " - " + signatureOfFather; + } + if (typeAtEnd) + return signature + " : " + parameterType.getHumanReadableName(); + return parameterType.getHumanReadableName() + " " + signature; + } + return parameter.getName(); + } + + protected String _signature(JvmTypeParameter parameter, boolean typeAtEnd) { + EObject container = parameter.eContainer(); + String signature = parameter.getName(); + String signatureOfFather = getSimpleSignature(container); + if (signatureOfFather != null) { + signature += " - " + signatureOfFather; + } + return signature; + } + + protected String _signature(JvmEnumerationType jvmEnumerationType, boolean typeAtEnd) { + return jvmEnumerationType.getSimpleName(); + } + + protected String _signature(JvmAnnotationType jvmAnnotationType, boolean typeAtEnd) { + return jvmAnnotationType.getSimpleName(); + } + + protected String getThrowsDeclaration(JvmExecutable executable) { + String result = ""; + EList exceptions = executable.getExceptions(); + if (exceptions.size() > 0) { + result += " throws "; + Iterator iterator = exceptions.iterator(); + while (iterator.hasNext()) { + JvmTypeReference next = iterator.next(); + result += next.getSimpleName(); + if (iterator.hasNext()) + result += ", "; + } + } + return result; + } + + protected String getSimpleSignature(EObject container) { + if (container instanceof JvmOperation) { + return getSimpleSignature((JvmOperation) container); + } else if (container instanceof JvmConstructor) { + return getSimpleSignature((JvmConstructor) container); + } + return getLabel(container); + } + + protected String getSimpleSignature(JvmConstructor contructor) { + return contructor.getSimpleName() + " " + uiStrings.parameters(contructor); + } + + protected String getSimpleSignature(JvmOperation jvmOperation) { + return jvmOperation.getSimpleName() + uiStrings.parameters(jvmOperation); + } + + protected String getLabel(EObject object) { + String label = nameLabelProvider.getNameLabel(object); + return object.eClass().getName() + (label != null ? " **" + label + "**" : ""); + } + +} diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ClasspathPropertiesBasedFileSystemScanner.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ClasspathPropertiesBasedFileSystemScanner.java new file mode 100644 index 00000000000..e1a0ae6985b --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ClasspathPropertiesBasedFileSystemScanner.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.lsp; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Properties; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.util.URI; +import org.eclipse.xtext.mwe.PathTraverser; +import org.eclipse.xtext.resource.IResourceServiceProvider; +import org.eclipse.xtext.resource.IResourceServiceProvider.Registry; +import org.eclipse.xtext.util.IAcceptor; +import org.eclipse.xtext.util.IFileSystemScanner; +import org.eclipse.xtext.util.UriExtensions; + +import com.google.common.collect.Multimap; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * @since 2.35 + */ +@Singleton +public class ClasspathPropertiesBasedFileSystemScanner implements IFileSystemScanner { + + private static final Logger LOGGER = Logger.getLogger(ClasspathPropertiesBasedFileSystemScanner.class); + + private static final Predicate MODEL = Pattern.compile("^\\w+\\.(model|src).\\d+$").asPredicate(); + + static final String XTEXT_CLASSPATH = "xtext.classpath"; + + @Inject + private UriExtensions uriExtensions; + + @Override + public void scan(URI root, IAcceptor acceptor) { + Properties classpath = getProjectClasspath(root); + List modelPaths = new ArrayList<>(); + if (classpath != null) { + classpath.forEach((k, v)->{ + String key = (String) k; + String value = (String) v; + if (MODEL.test(key)) { + modelPaths.add(value); + } + }); + } else { + modelPaths.add(new File(root.toFileString()).getAbsolutePath()); + }; + + Registry registry = IResourceServiceProvider.Registry.INSTANCE; + Multimap byPath = new PathTraverser().resolvePathes(modelPaths, uri->{ + return registry.getResourceServiceProvider(uri) != null; + }); + LOGGER.debug("Initial files:"); + byPath.values().forEach(uri->{ + URI lspCompliant = uriExtensions.withEmptyAuthority(uri); + LOGGER.debug(lspCompliant); + acceptor.accept(lspCompliant); + }); + } + + protected Properties getProjectClasspath(URI root) { + return Optional.ofNullable(root).map(URI::toFileString).map(File::new).map(base->{ + File classPathInfo = configFile(base); + if (classPathInfo.exists()) { + Properties properties = new Properties(); + try (FileReader reader = new FileReader(classPathInfo, StandardCharsets.UTF_8)) { + properties.load(reader); + return properties; + } catch(IOException e) { + LOGGER.error(e.getMessage(), e); + } + } + return null; + }).orElse(null); + } + + protected File configFile(File base) { + return new File(new File(base, "target"), XTEXT_CLASSPATH); + } + +} diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ClasspathPropertiesBasedXbaseProjectManager.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ClasspathPropertiesBasedXbaseProjectManager.java new file mode 100644 index 00000000000..b866dc1f891 --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ClasspathPropertiesBasedXbaseProjectManager.java @@ -0,0 +1,245 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.lsp; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.impl.NotificationImpl; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.build.BuildRequest; +import org.eclipse.xtext.common.types.access.IJvmTypeProvider; +import org.eclipse.xtext.common.types.access.IMirror; +import org.eclipse.xtext.common.types.access.JvmTypeChangeDispatcher; +import org.eclipse.xtext.common.types.access.TypeResource; +import org.eclipse.xtext.common.types.access.impl.IClassMirror; +import org.eclipse.xtext.common.types.descriptions.TypeResourceDescription; +import org.eclipse.xtext.ide.server.ProjectManager; +import org.eclipse.xtext.ide.server.UriExtensions; +import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.resource.IExternalContentSupport.IExternalContentProvider; +import org.eclipse.xtext.resource.IResourceDescription.Delta; +import org.eclipse.xtext.resource.XtextResourceSet; +import org.eclipse.xtext.resource.impl.ProjectDescription; +import org.eclipse.xtext.resource.impl.ResourceDescriptionsData; +import org.eclipse.xtext.util.CancelIndicator; +import org.eclipse.xtext.validation.Issue; +import org.eclipse.xtext.workspace.IProjectConfig; +import org.eclipse.xtext.xbase.lib.Procedures.Procedure2; + +import com.google.common.collect.MapDifference; +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Provider; + +/** + * @since 2.35 + */ +public class ClasspathPropertiesBasedXbaseProjectManager extends ProjectManager { + + private static final Predicate CP = Pattern.compile("^\\w+\\.(bin|cp).\\d+$").asPredicate(); + + private static final Logger LOGGER = Logger.getLogger(ClasspathPropertiesBasedFileSystemScanner.class); + + @Inject + private ClasspathPropertiesBasedFileSystemScanner classpathScanner; + + @Inject + private IJvmTypeProvider.Factory typeProviderFactory; + + @Inject + private IQualifiedNameConverter qualifiedNameConverter; + + private final ForwardingClassLoader classpathURIContext = new ForwardingClassLoader(); + + private Properties projectClasspath; + + @Override + public void initialize(ProjectDescription description, IProjectConfig projectConfig, + Procedure2> acceptor, + IExternalContentProvider openedDocumentsContentProvider, + Provider> indexProvider, CancelIndicator cancelIndicator) { + super.initialize(description, projectConfig, acceptor, openedDocumentsContentProvider, indexProvider, cancelIndicator); + + projectClasspath = classpathScanner.getProjectClasspath(projectConfig.getPath()); + updateClassLoader(); + } + + protected void updateClassLoader() { + if (projectClasspath != null) { + Set urls = new LinkedHashSet<>(); + projectClasspath.forEach((k, v) -> { + String key = (String) k; + String value = (String) v; + if (CP.test(key)) { + urls.add(value); + } + }); + URL[] cp = urls.stream().map(p -> { + try { + return new File(p).toURI().toURL(); + } catch (MalformedURLException e) { + LOGGER.error(e.getMessage(), e); + return null; + } + }).filter(Objects::nonNull).toArray(URL[]::new); + classpathURIContext.setDelegate(new URLClassLoader(cp, ClassLoader.getPlatformClassLoader())); + clearTypesCache(); + } + } + + protected void clearTypesCache() { + XtextResourceSet resourceSet = getResourceSet(); + if (resourceSet != null) { + IJvmTypeProvider typeProvider = typeProviderFactory.findTypeProvider(resourceSet); + if (typeProvider != null) { + typeProvider.clearCache(); + } + JvmTypeChangeDispatcher dispatcher = JvmTypeChangeDispatcher.findResourceChangeDispatcher(resourceSet); + EObject dummy = EcoreFactory.eINSTANCE.createEObject(); + dispatcher.requestNotificationOnChange(dummy, ()->{}); + dummy.eNotify(new NotificationImpl(Notification.ADD, true, false)); + } + } + + @Override + protected BuildRequest newBuildRequest(List changedFiles, List deletedFiles, List externalDeltas, + CancelIndicator cancelIndicator) { + Map> locationToTypeURI = null; + List changedTypeResources = new ArrayList<>(); + for(URI changedFile: changedFiles) { + if (ClasspathPropertiesBasedFileSystemScanner.XTEXT_CLASSPATH.equals(changedFile.lastSegment())) { + Properties prev = projectClasspath; + projectClasspath = classpathScanner.getProjectClasspath(getProjectConfig().getPath()); + updateClassLoader(); + + if (locationToTypeURI == null) { + locationToTypeURI = collectLocationURIs(); + } + changedTypeResources.addAll(collectAffectedTypeResources(prev, projectClasspath, locationToTypeURI)); + } else if ("class".equals(changedFile.fileExtension())) { + if (locationToTypeURI == null) { + locationToTypeURI = collectLocationURIs(); + } + List resources = locationToTypeURI.get(changedFile.toFileString()); + if (resources != null) { + changedTypeResources.addAll(resources); + } + } + } + if (!changedTypeResources.isEmpty()) { + changedFiles.addAll(changedTypeResources); + for(URI changedTypeResource: changedTypeResources) { + QualifiedName qn = qualifiedNameConverter.toQualifiedName(changedTypeResource.lastSegment()); + externalDeltas.add(new TypeResourceDescription.ChangedDelta(qn)); + } + clearTypesCache(); + } + return super.newBuildRequest(changedFiles, deletedFiles, externalDeltas, cancelIndicator); + } + + private Map> collectLocationURIs() { + Map> result = new HashMap<>(); + XtextResourceSet resourceSet = getResourceSet(); + if (resourceSet != null) { + for(Resource resource: resourceSet.getResources()) { + if (resource instanceof TypeResource) { + IMirror mirror = ((TypeResource) resource).getMirror(); + if (mirror instanceof IClassMirror) { + URI locationURI = ((IClassMirror) mirror).getLocationURI(resource); + if (locationURI != null) { + String locationString; + if (locationURI.isArchive()) { + String authority = locationURI.authority(); + locationString = URI.createURI(authority.substring(0, authority.length() - 1), true).toFileString(); + } else { + locationString = new UriExtensions().withEmptyAuthority(locationURI).toFileString(); + } + if (locationString != null) { + result.computeIfAbsent(locationString, any->new ArrayList<>()).add(resource.getURI()); + } + } + } + } + } + } + return result; + } + + protected List collectAffectedTypeResources(Properties oldClasspath, Properties newClasspath, + Map> locationToTypeURI) { + List result = new ArrayList<>(); + if (locationToTypeURI != null && oldClasspath != null) { + Map oldHashes = locationHashes(oldClasspath); + Map newHashes = locationHashes(newClasspath); + MapDifference difference = Maps.difference(oldHashes, newHashes); + for(String diff: difference.entriesDiffering().keySet()) { + List resources = locationToTypeURI.get(diff); + if (resources != null) { + result.addAll(resources); + } + } + for(String diff: difference.entriesOnlyOnLeft().keySet()) { + List resources = locationToTypeURI.get(diff); + if (resources != null) { + result.addAll(resources); + } + } + } + return result; + } + + protected static Map locationHashes(Properties config) { + Map result = new HashMap<>(); + config.forEach((k, v) -> { + String key = (String) k; + String value = (String) v; + if (CP.test(key)) { + String hash = (String) config.get(k + ".hash"); + if (hash != null) { + result.put(value, hash); + } + } + }); + return result; + } + + @Override + protected XtextResourceSet createFreshResourceSet(ResourceDescriptionsData newIndex) { + XtextResourceSet result = super.createFreshResourceSet(newIndex); + return result; + } + + @Override + public XtextResourceSet createNewResourceSet(ResourceDescriptionsData newIndex) { + XtextResourceSet result = super.createNewResourceSet(newIndex); + result.setClasspathURIContext(classpathURIContext); + typeProviderFactory.createTypeProvider(result); + return result; + } + +} diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ForwardingClassLoader.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ForwardingClassLoader.java new file mode 100644 index 00000000000..6579b7e352a --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/ForwardingClassLoader.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.lsp; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; + +import org.apache.log4j.Logger; + +/** + * @since 2.35 + */ +public class ForwardingClassLoader extends ClassLoader { + + private static final Logger logger = Logger.getLogger(ForwardingClassLoader.class); + + private URLClassLoader delegate; + + public ForwardingClassLoader() { + super(ClassLoader.getPlatformClassLoader()); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (delegate != null) { + return delegate.loadClass(name); + } + return super.loadClass(name); + } + + @Override + public URL getResource(String name) { + if (delegate != null) { + return delegate.getResource(name); + } + return super.getResource(name); + } + + @Override + public InputStream getResourceAsStream(String name) { + if (delegate != null) { + return delegate.getResourceAsStream(name); + } + return super.getResourceAsStream(name); + } + + public void setDelegate(URLClassLoader delegate) { + if (this.delegate != null) { + try { + this.delegate.close(); + } catch (IOException e) { + logger.error(e.getMessage(), e); + this.delegate = null; + } + } + this.delegate = delegate; + } + + public ClassLoader getDelegate() { + if (this.delegate != null) { + return delegate; + } + return getParent(); + } + +} diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/LspFeatureScopeTrackerProvider.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/LspFeatureScopeTrackerProvider.java new file mode 100644 index 00000000000..f58c8e35417 --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/LspFeatureScopeTrackerProvider.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.lsp; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.xbase.typesystem.internal.FeatureScopeTracker; +import org.eclipse.xtext.xbase.typesystem.internal.IFeatureScopeTracker; +import org.eclipse.xtext.xbase.typesystem.internal.OptimizingFeatureScopeTrackerProvider; + +/** + * @since 2.35 + */ +public class LspFeatureScopeTrackerProvider extends OptimizingFeatureScopeTrackerProvider { + + @Override + public IFeatureScopeTracker track(EObject root) { + // Always track the feature scopes since we don't distinguish between open documents and regular files + return new FeatureScopeTracker(); + } + +} \ No newline at end of file diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/LspTypesProposalProvider.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/LspTypesProposalProvider.java new file mode 100644 index 00000000000..6bf29408f88 --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/LspTypesProposalProvider.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.lsp; + +import org.eclipse.xtext.ide.editor.contentassist.ContentAssistContext; +import org.eclipse.xtext.xbase.ide.contentassist.ClasspathBasedIdeTypesProposalProvider; + +/** + * @since 2.35 + */ +public class LspTypesProposalProvider extends ClasspathBasedIdeTypesProposalProvider { + + @Override + protected ClassLoader getClassLoader(ContentAssistContext context) { + ClassLoader result = super.getClassLoader(context); + if (result instanceof ForwardingClassLoader) { + result = ((ForwardingClassLoader) result).getDelegate(); + } + return result; + } + +} diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/NonSealedClasspathTypeProviderFactory.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/NonSealedClasspathTypeProviderFactory.java new file mode 100644 index 00000000000..522dbb912a5 --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/NonSealedClasspathTypeProviderFactory.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.lsp; + +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.xtext.common.types.access.ClasspathTypeProviderFactory; +import org.eclipse.xtext.common.types.access.binary.BinaryClass; +import org.eclipse.xtext.common.types.access.binary.BinaryClassMirror; +import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider; +import org.eclipse.xtext.common.types.access.impl.IndexedJvmTypeAccess; +import org.eclipse.xtext.common.types.access.impl.TypeResourceServices; + +import com.google.inject.Inject; + +/** + * @since 2.35 + */ +public class NonSealedClasspathTypeProviderFactory extends ClasspathTypeProviderFactory { + + @Inject + public NonSealedClasspathTypeProviderFactory(ClassLoader classLoader, TypeResourceServices services) { + super(classLoader, services); + } + + @Override + protected ClasspathTypeProvider createClasspathTypeProvider(ResourceSet resourceSet) { + return new NonSealedClasspathTypeProvider(getClassLoader(resourceSet), resourceSet, getIndexedJvmTypeAccess(), services); + } + + protected static class NonSealedClasspathTypeProvider extends ClasspathTypeProvider { + + protected NonSealedClasspathTypeProvider(ClassLoader classLoader, ResourceSet resourceSet, + IndexedJvmTypeAccess indexedJvmTypeAccess, TypeResourceServices services) { + super(classLoader, resourceSet, indexedJvmTypeAccess, services); + } + + @Override + public BinaryClassMirror createMirror(BinaryClass clazz) { + return BinaryClassMirror.createClassMirror(clazz, getDeclaredTypeFactory(), false); + } + + } +} diff --git a/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/XbaseLspServerModule.java b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/XbaseLspServerModule.java new file mode 100644 index 00000000000..e82bfc77472 --- /dev/null +++ b/org.eclipse.xtext.xbase.ide/src/org/eclipse/xtext/xbase/lsp/XbaseLspServerModule.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2024 Sebastian Zarnekow and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.xbase.lsp; + +import org.eclipse.xtext.common.types.access.IJvmTypeProvider; +import org.eclipse.xtext.ide.server.ProjectManager; +import org.eclipse.xtext.util.IFileSystemScanner; + +import com.google.inject.AbstractModule; + +/** + * @since 2.35 + */ +public class XbaseLspServerModule extends AbstractModule { + @Override + protected void configure() { + bind(ProjectManager.class).to(ClasspathPropertiesBasedXbaseProjectManager.class); + bind(IFileSystemScanner.class).to(ClasspathPropertiesBasedFileSystemScanner.class); + + bind(ClassLoader.class).toInstance(ClassLoader.getPlatformClassLoader()); + bind(IJvmTypeProvider.Factory.class).to(NonSealedClasspathTypeProviderFactory.class); + } +} \ No newline at end of file diff --git a/org.eclipse.xtext.xbase.ui/src/org/eclipse/xtext/xbase/ui/hover/HoverUiStrings.java b/org.eclipse.xtext.xbase.ui/src/org/eclipse/xtext/xbase/ui/hover/HoverUiStrings.java index 11aba3f312c..fbe2c204f6f 100644 --- a/org.eclipse.xtext.xbase.ui/src/org/eclipse/xtext/xbase/ui/hover/HoverUiStrings.java +++ b/org.eclipse.xtext.xbase.ui/src/org/eclipse/xtext/xbase/ui/hover/HoverUiStrings.java @@ -8,59 +8,12 @@ *******************************************************************************/ package org.eclipse.xtext.xbase.ui.hover; -import static com.google.common.collect.Iterables.*; - -import java.util.List; - -import org.eclipse.xtext.common.types.JvmFormalParameter; -import org.eclipse.xtext.common.types.JvmType; -import org.eclipse.xtext.common.types.JvmTypeConstraint; -import org.eclipse.xtext.common.types.JvmTypeParameter; -import org.eclipse.xtext.xbase.validation.UIStrings; +import org.eclipse.xtext.xbase.ide.hover.HoverIdeStrings; /** * @author Holger Schill - Initial contribution and API * @since 2.3 */ -public class HoverUiStrings extends UIStrings { - - @Override - public String typeParameters(Iterable typeParams) { - if (!isEmpty(typeParams)) { - StringBuilder result = new StringBuilder("<"); - boolean needsSeparator = false; - OUTER: for (JvmTypeParameter typeParam : typeParams) { - if (needsSeparator) - result.append(", "); - needsSeparator = true; - if(typeParam != null) { - result.append(typeParam.getSimpleName()); - List constraints = typeParam.getConstraints(); - if (!constraints.isEmpty()) { - if (constraints.size() == 1) { - JvmType singleConstraint = constraints.get(0).getTypeReference().getType(); - if (Object.class.getName().equals(singleConstraint.getIdentifier())) { - continue OUTER; - } - } - result.append(" extends "); - for(int i = 0; i < constraints.size(); i++) { - if (i != 0) { - result.append(" & "); - } - result.append(constraints.get(i).getTypeReference().getSimpleName()); - } - } - } else - result.append("[null]"); - } - return result.append(">").toString(); - } - return ""; - } +public class HoverUiStrings extends HoverIdeStrings { - @Override - protected String parameterTypes(Iterable parameters, boolean isVarArgs) { - return parametersToString(parameters, isVarArgs, true); - } } diff --git a/org.eclipse.xtext.xbase/META-INF/MANIFEST.MF b/org.eclipse.xtext.xbase/META-INF/MANIFEST.MF index 37cd9273ae6..381c945a6bd 100644 --- a/org.eclipse.xtext.xbase/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext.xbase/META-INF/MANIFEST.MF @@ -61,7 +61,11 @@ Export-Package: org.eclipse.xtext.xbase;version="2.35.0", org.eclipse.xtext.xbase.debug;version="2.35.0";x-internal:=true, org.eclipse.xtext.xbase.featurecalls;version="2.35.0";x-internal:=true, org.eclipse.xtext.xbase.formatting;version="2.35.0";x-friends:="org.eclipse.xtend.ide,org.eclipse.xtend.ide.common,org.eclipse.xtext.xbase.junit", - org.eclipse.xtext.xbase.formatting2;version="2.35.0";x-friends:="org.eclipse.xtext.purexbase,org.eclipse.xtend.core,org.eclipse.xtend.core.tests,org.eclipse.xtext.xbase.tests", + org.eclipse.xtext.xbase.formatting2;version="2.35.0"; + x-friends:="org.eclipse.xtext.purexbase, + org.eclipse.xtend.core, + org.eclipse.xtend.core.tests, + org.eclipse.xtext.xbase.tests", org.eclipse.xtext.xbase.impl;version="2.35.0";x-internal:=true, org.eclipse.xtext.xbase.imports;version="2.35.0"; x-friends:="org.eclipse.xtext.xbase.ide, @@ -204,7 +208,8 @@ Export-Package: org.eclipse.xtext.xbase;version="2.35.0", org.eclipse.xtend.ide, org.eclipse.xtend.ide.tests, org.eclipse.xtend.ide.common, - org.eclipse.xtend.core.tests", + org.eclipse.xtend.core.tests, + org.eclipse.xtext.xbase.ide", org.eclipse.xtext.xtype;version="2.35.0", org.eclipse.xtext.xtype.impl;version="2.35.0";x-friends:="org.eclipse.xtend.core", org.eclipse.xtext.xtype.util;version="2.35.0";x-friends:="org.eclipse.xtend.core,org.eclipse.xtext.xbase.tests" diff --git a/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF b/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF index ad9b2e21600..3d1cce97490 100644 --- a/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF @@ -17,7 +17,8 @@ Require-Bundle: org.eclipse.xtext;bundle-version="2.35.0";x-installation:=greedy org.eclipse.equinox.common;bundle-version="3.16.0", org.antlr.runtime;bundle-version="[3.2.0,3.2.1)", de.itemis.xtext.antlr;bundle-version="2.0.0";resolution:=optional;visibility:=reexport, - org.eclipse.jdt.core;bundle-version="3.29.0";resolution:=optional + org.eclipse.jdt.core;bundle-version="3.29.0";resolution:=optional, + com.google.gson;bundle-version="2.10.1";resolution:=optional Import-Package: org.apache.log4j;version="1.2.24" Export-Package: org.eclipse.xtext.xtext.generator;version="2.35.0", org.eclipse.xtext.xtext.generator.builder;version="2.35.0", @@ -46,6 +47,7 @@ Export-Package: org.eclipse.xtext.xtext.generator;version="2.35.0", org.eclipse.xtext.xtext.generator.resourceFactory;version="2.35.0", org.eclipse.xtext.xtext.generator.scoping;version="2.35.0", org.eclipse.xtext.xtext.generator.serializer;version="2.35.0", + org.eclipse.xtext.xtext.generator.textmate;version="2.35.0";x-internal:=true, org.eclipse.xtext.xtext.generator.types;version="2.35.0", org.eclipse.xtext.xtext.generator.ui.codemining;version="2.35.0", org.eclipse.xtext.xtext.generator.ui.compare;version="2.35.0", diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/AutoRule.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/AutoRule.java new file mode 100644 index 00000000000..888f3727c26 --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/AutoRule.java @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import java.util.Optional; + +import org.eclipse.emf.mwe2.runtime.Mandatory; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Group; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.UntilToken; + +/** + * A TextMate rule that will parse the associated terminal rule and infer the TextMate equivalent automatically, if possible. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class AutoRule extends TextMateRule { + + @Mandatory + @Override + public void setTerminalRule(String terminalRule) { + super.setTerminalRule(terminalRule); + } + + public Optional init(Grammar grammar, boolean ignoreCase, TerminalRuleToTextMateRule generator) { + TerminalRule terminal = (TerminalRule) GrammarUtil.findRuleForName(grammar, getTerminalRule()); + if (terminal != null) { + Optional result = toBeginEndRule(terminal, generator).or(()->toMatchRule(terminal, generator)); + return result.map(r->{ + r.setName(Optional.ofNullable(getName()).orElseGet(()->toTextMateName(terminal, GrammarUtil.getSimpleName(grammar).toLowerCase()))); + return r; + }).filter(r->r.getName() != null); + } + return Optional.empty(); + } + + protected Optional toBeginEndRule(TerminalRule rule, TerminalRuleToTextMateRule generator) { + AbstractElement alternatives = rule.getAlternatives(); + if (alternatives instanceof Group) { + Group group = (Group) alternatives; + if (group.getElements().size() == 2 && group.getElements().get(1) instanceof UntilToken) { + String begin = generator.getMatchRegEx(group.getElements().get(0)); + String end = generator.getMatchRegEx(((UntilToken)group.getElements().get(1)).getTerminal()); + BeginEndRule result = new BeginEndRule(); + result.setBegin(begin); + result.setEnd(end); + return Optional.of(result); + } + } + return Optional.empty(); + } + + protected Optional toMatchRule(TerminalRule rule, TerminalRuleToTextMateRule generator) { + try { + String match = generator.getMatchRegEx(rule); + MatchRule result = new MatchRule(); + result.setMatch(match); + return Optional.of(result); + } catch(Exception e) { + return Optional.empty(); + } + } + + protected String toTextMateName(TerminalRule terminal, String langName) { + switch(terminal.getName()) { + case "SL_COMMENT": return "comment.line." + langName; + case "ML_COMMENT": return "comment.block." + langName; + case "STRING": return "string.quoted." + langName; + case "ID": return "variable." + langName; + case "INT": return "constant.numeric." + langName; + case "ANY_OTHER": return "invalid." + langName; + } + return null; + } + +} \ No newline at end of file diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/BeginEndRule.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/BeginEndRule.java new file mode 100644 index 00000000000..e932304023b --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/BeginEndRule.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.emf.mwe2.runtime.Mandatory; + +import com.google.gson.annotations.Expose; + +/** + * See the TextMate specification. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class BeginEndRule extends TextMateRule { + + @Expose private List patterns; + @Expose private String begin; + @Expose private String end; + @Expose private String contentName; + @Expose private Map captures; + @Expose private Map beginCaptures; + @Expose private Map endCaptures; + + @Mandatory + @Override + public void setName(String name) { + super.setName(name); + } + + public String getBegin() { + return begin; + } + @Mandatory + public void setBegin(String begin) { + this.begin = begin; + } + public String getEnd() { + return end; + } + @Mandatory + public void setEnd(String end) { + this.end = end; + } + + public void addMatchPattern(MatchRule rule) { + if (patterns == null) { + patterns = new ArrayList<>(); + } + this.patterns.add(rule); + } + + public void addBeginEndPattern(BeginEndRule rule) { + if (patterns == null) { + patterns = new ArrayList<>(); + } + this.patterns.add(rule); + } + + public String getContentName() { + return contentName; + } + + public void setContentName(String contentName) { + this.contentName = contentName; + } + + public void addInclude(String include) { + if (patterns == null) { + patterns = new ArrayList<>(); + } + this.patterns.add(new IncludeRule(include)); + } + + public void addBeginEndCapture(Capture c) { + if (beginCaptures == null) { + beginCaptures = new TreeMap<>(); + } + beginCaptures.put(c.getGroup(), c); + } + + public void addEndCapture(Capture c) { + if (endCaptures == null) { + endCaptures = new TreeMap<>(); + } + endCaptures.put(c.getGroup(), c); + } + + public void addCapture(Capture c) { + if (captures == null) { + captures = new TreeMap<>(); + } + captures.put(c.getGroup(), c); + } + + +} \ No newline at end of file diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/Capture.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/Capture.java new file mode 100644 index 00000000000..fb5b16e062c --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/Capture.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import com.google.gson.annotations.Expose; + +/** + * See the TextMate specification. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class Capture { + + private int group; + @Expose private String name; + + public int getGroup() { + return group; + } + public void setGroup(int group) { + this.group = group; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + +} diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/IncludeRule.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/IncludeRule.java new file mode 100644 index 00000000000..a36210a37b8 --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/IncludeRule.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import com.google.gson.annotations.Expose; + +/** + * See the TextMate specification. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class IncludeRule extends TextMateRule { + + @Expose private String include; + + public IncludeRule(String include) { + this.include = include; + } + + public String getInclude() { + return include; + } + + public void setInclude(String include) { + this.include = include; + } + + + +} diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/MatchRule.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/MatchRule.java new file mode 100644 index 00000000000..8a6b3980373 --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/MatchRule.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.emf.mwe2.runtime.Mandatory; + +import com.google.gson.annotations.Expose; + +/** + * See the TextMate specification. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class MatchRule extends TextMateRule { + + @Expose private String match; + @Expose private Map captures; + + @Mandatory + @Override + public void setName(String name) { + super.setName(name); + } + + public String getMatch() { + return match; + } + + @Mandatory + public void setMatch(String match) { + this.match = match; + } + + public void addCapture(Capture c) { + if (captures == null) { + captures = new TreeMap<>(); + } + captures.put(c.getGroup(), c); + } + + +} \ No newline at end of file diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/SkippedRule.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/SkippedRule.java new file mode 100644 index 00000000000..35da57326ea --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/SkippedRule.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +/** + * Explicitely skip a terminal rule from auto-processing. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class SkippedRule extends TextMateRule { + +} \ No newline at end of file diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TerminalRuleToTextMateRule.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TerminalRuleToTextMateRule.java new file mode 100644 index 00000000000..585155feeac --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TerminalRuleToTextMateRule.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.Alternatives; +import org.eclipse.xtext.CharacterRange; +import org.eclipse.xtext.EOF; +import org.eclipse.xtext.Group; +import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.NegatedToken; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.UntilToken; +import org.eclipse.xtext.Wildcard; +import org.eclipse.xtext.util.Strings; +import org.eclipse.xtext.util.XtextSwitch; + +/** + * Converter from Xtext {@link TerminalRule} to TextMate regular expressions. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class TerminalRuleToTextMateRule extends XtextSwitch { + + private final StringBuilder match; + private boolean negationMode = false; + + public TerminalRuleToTextMateRule() { + this.match = new StringBuilder(); + } + + public String getMatchRegEx(TerminalRule rule) { + doSwitch(rule.getAlternatives()); + String result = match.toString(); + match.setLength(0); + return result; + } + + public String getMatchRegEx(AbstractElement element) { + doSwitch(element); + String result = match.toString(); + match.setLength(0); + return result; + } + + @Override + public String caseAlternatives(Alternatives object) { + if (negationMode) { + for (var elem : object.getElements()) { + doSwitch(elem); + } + } else { + match.append("("); + boolean first = true; + for (var elem : object.getElements()) { + if (!first) match.append("|"); + first = false; + doSwitch(elem); + } + match.append(')'); + match.append(Strings.emptyIfNull(object.getCardinality())); + } + + return ""; + } + + @Override + public String caseWildcard(Wildcard object) { + match.append("."); + match.append(Strings.emptyIfNull(object.getCardinality())); + return ""; + } + + @Override + public String caseTerminalRule(TerminalRule object) { + doSwitch(object.getAlternatives()); + return ""; + } + + @Override + public String caseCharacterRange(CharacterRange object) { + if (!Strings.isEmpty(object.getCardinality())) { + match.append('('); + } + match.append("["); + doSwitch(object.getLeft()); + match.append("-"); + doSwitch(object.getRight()); + match.append("]"); + if (!Strings.isEmpty(object.getCardinality())) { + match.append(')'); + match.append(Strings.emptyIfNull(object.getCardinality())); + } + return ""; + } + + @Override + public String caseRuleCall(RuleCall object) { + if (!Strings.isEmpty(object.getCardinality())) { + match.append("("); + } + doSwitch(object.getRule()); + if (!Strings.isEmpty(object.getCardinality())) { + match.append(")"); + match.append(Strings.emptyIfNull(object.getCardinality())); + } + return ""; + } + + @Override + public String caseGroup(Group object) { + if (negationMode) { + throw new UnsupportedOperationException("Negation is not supported for group rules"); + } + if (!Strings.isEmpty(object.getCardinality())) { + match.append("("); + } + for (var elem : object.getElements()) { + doSwitch(elem); + } + if (!Strings.isEmpty(object.getCardinality())) { + match.append(")"); + } + match.append(Strings.emptyIfNull(object.getCardinality())); + return ""; + } + + @Override + public String caseNegatedToken(NegatedToken object) { + match.append("[^"); + negationMode = true; + doSwitch(object.getTerminal()); + negationMode = false; + match.append("]").append(Strings.emptyIfNull(object.getCardinality())); + return ""; + } + + @Override + public String caseUntilToken(UntilToken object) { + throw new UnsupportedOperationException("Until token not supported, use begin and end TextMate rules"); + } + + @Override + public String defaultCase(EObject object) { + throw new UnsupportedOperationException( + "Encountered a non terminal rule or a terminal rule that is not supported: " + object.toString()); + } + + @Override + public String caseKeyword(Keyword object) { + String value = object.getValue(); + match.append(toTextMateString(value)); + match.append(Strings.emptyIfNull(object.getCardinality())); + return ""; + } + + // Do nothing + @Override + public String caseEOF(EOF object) { + return ""; + } + + // see https://macromates.com/manual/en/regular_expressions + private String toTextMateString(String base) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < base.length(); i++) { + char c = base.charAt(i); + if (c == '*' || c == '+' || c == '.' || c == '?' || c == '\\' || c == '\r' || c == '\n' || c == '"' || c == '\t' + || c == '$' || c == '^' || c == '|' || c == '{' || c == '}'|| c == '[' || c == ']') { + builder.append("\\"); + } + if (c == ' ') { + builder.append("\\s"); + } else { + builder.append(c); + } + } + return builder.toString(); + } + +} diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateGrammar.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateGrammar.java new file mode 100644 index 00000000000..0027f3b4044 --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateGrammar.java @@ -0,0 +1,187 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.TerminalRule; + +import com.google.common.base.Joiner; +import com.google.gson.annotations.Expose; + +/** + * A TextMate grammar with some additional, optional properties that can be assigned from MWE2 to configure + * how rules are inferred. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class TextMateGrammar { + + @Expose private final List patterns; + @Expose private String scopeName; + @Expose private Map repository; + private boolean inferPatterns = true; + private boolean ignoreCase = false; + private TerminalRuleToTextMateRule generator = new TerminalRuleToTextMateRule(); + + public TextMateGrammar() { + this.patterns = new ArrayList<>(); + } + + public String getScopeName() { + return scopeName; + } + + public void setScopeName(String scopeName) { + this.scopeName = scopeName; + } + + public boolean isInferPatterns() { + return inferPatterns; + } + + public void setInferPatterns(boolean inferPatterns) { + this.inferPatterns = inferPatterns; + } + + public boolean isIgnoreCase() { + return ignoreCase; + } + + public void setIgnoreCase(boolean ignoreCase) { + this.ignoreCase = ignoreCase; + } + + public TerminalRuleToTextMateRule getGenerator() { + return generator; + } + + public void setGenerator(TerminalRuleToTextMateRule generator) { + this.generator = generator; + } + + public void addSkip(SkippedRule rule) { + this.patterns.add(rule); + } + + public void addAuto(AutoRule rule) { + this.patterns.add(rule); + } + + public void addMatch(MatchRule rule) { + this.patterns.add(rule); + } + + public void addInclude(String include) { + this.patterns.add(new IncludeRule(include)); + } + + public void addBeginEnd(BeginEndRule rule) { + this.patterns.add(rule); + } + + public void addRepositoryMatch(MatchRule rule) { + if (repository == null) { + repository = new TreeMap<>(); + } + this.repository.put(rule.getName(), rule); + rule.setName(null); + } + + public void addRepositoryBeginEnd(BeginEndRule rule) { + if (repository == null) { + repository = new TreeMap<>(); + } + this.repository.put(rule.getName(), rule); + rule.setName(null); + } + + public void addRule(TextMateRule rule) { + this.patterns.add(rule); + } + + protected TextMateGrammar init(Grammar grammar) { + String scopeName = this.scopeName; + if (scopeName == null) { + scopeName = "source." + getLanguageName(grammar); + } + TextMateGrammar result = new TextMateGrammar(); + result.setScopeName(scopeName); + TextMateRule keywords = getKeywordControlRule(grammar, ignoreCase); + result.addRule(keywords); + + Set seenTerminalRules = new HashSet<>(); + for(TextMateRule pattern: patterns) { + seenTerminalRules.add(pattern.getTerminalRule()); + if (pattern instanceof SkippedRule) { + continue; + } + if (pattern instanceof AutoRule) { + ((AutoRule)pattern).init(grammar, ignoreCase, generator).ifPresent(result::addRule); + } else { + result.addRule(pattern); + } + } + if (inferPatterns) { + List terminals = GrammarUtil.allTerminalRules(grammar) + .stream() + .filter(r -> !r.isFragment()) + .collect(Collectors.toList()); + for(TerminalRule terminal: terminals) { + if (!seenTerminalRules.add(terminal.getName())) { + continue; + } + AutoRule auto = newAutoRule(); + auto.setTerminalRule(terminal.getName()); + auto.init(grammar, ignoreCase, generator).ifPresent(result::addRule); + } + } + return result; + } + + protected AutoRule newAutoRule() { + return new AutoRule(); + } + + protected String getLanguageName(Grammar grammar) { + return GrammarUtil.getSimpleName(grammar).toLowerCase(Locale.ROOT); + } + + protected TextMateRule getKeywordControlRule(Grammar grammar, boolean ignoreCase) { + StringBuilder matchBuilder = new StringBuilder(); + if (ignoreCase) { + matchBuilder.append("(?i)"); + } + matchBuilder.append("\\b("); + List allKeywords = GrammarUtil.getAllKeywords(grammar) + .stream() + .filter(s->s.matches("\\w+")) + .sorted(Comparator.naturalOrder()) + .collect(Collectors.toList()); + matchBuilder.append(Joiner.on("|").join(allKeywords)); + matchBuilder.append(")\\b"); + MatchRule result = new MatchRule(); + result.setName("keyword.control." + getLanguageName(grammar)); + result.setMatch(matchBuilder.toString()); + return result; + } + +} diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateHighlightingFragment.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateHighlightingFragment.java new file mode 100644 index 00000000000..562337fecf0 --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateHighlightingFragment.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.xtext.generator.AbstractExternalFolderAwareFragment; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +/** + * A fragment to generate TextMate grammars. + * + * @see TextMateGrammar + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public class TextMateHighlightingFragment extends AbstractExternalFolderAwareFragment { + + private TextMateGrammar textMateGrammar; + + private String fileName; + + public TextMateGrammar getTextMateGrammar() { + if (textMateGrammar == null) { + textMateGrammar = new TextMateGrammar(); + } + return textMateGrammar; + } + + public void setTextMateGrammar(TextMateGrammar textMateGrammar) { + this.textMateGrammar = textMateGrammar; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + @Override + public void generate() { + String json = toJsonString(); + getOutputLocation().generateFile(fileName, json); + } + + protected String toJsonString() { + Grammar grammar = getGrammar(); + if (fileName == null) { + fileName = getLanguageName(grammar) + ".tmLanguage.json"; + } + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .setPrettyPrinting() + .create(); + String json = gson.toJson(getTextMateGrammar().init(getGrammar())); + return json; + } + + protected String getLanguageName(Grammar g) { + return GrammarUtil.getSimpleName(g).toLowerCase(); + } + +} diff --git a/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateRule.java b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateRule.java new file mode 100644 index 00000000000..4638dbc69e7 --- /dev/null +++ b/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/textmate/TextMateRule.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2024 Sigasi (http://www.sigasi.com) and others. + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.xtext.xtext.generator.textmate; + +import com.google.gson.annotations.Expose; + +/** + * Minimal TextMate rule. + * + * @author David Medina + * @author Sebastian Zarnekow + * @since 2.35 + */ +public abstract class TextMateRule { + @Expose private String name; + + private String terminalRule; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTerminalRule() { + return terminalRule; + } + + public void setTerminalRule(String terminalRule) { + this.terminalRule = terminalRule; + } + +} \ No newline at end of file diff --git a/org.eclipse.xtext/META-INF/MANIFEST.MF b/org.eclipse.xtext/META-INF/MANIFEST.MF index 18b5565e62f..b0b7da12f3e 100644 --- a/org.eclipse.xtext/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext/META-INF/MANIFEST.MF @@ -17,7 +17,8 @@ Export-Package: org.eclipse.xtext;version="2.35.0", org.eclipse.xtext.builder.standalone.tests, org.eclipse.xtend.standalone, org.eclipse.xtext.builder.standalone, - org.eclipse.xtend.performance.tests", + org.eclipse.xtend.performance.tests, + org.eclipse.xtext.xbase.ide", org.eclipse.xtext.common;version="2.35.0", org.eclipse.xtext.common.services;version="2.35.0", org.eclipse.xtext.conversion;version="2.35.0", @@ -181,20 +182,21 @@ Export-Package: org.eclipse.xtext;version="2.35.0", org.eclipse.xtext.validation;version="2.35.0", org.eclipse.xtext.validation.impl;version="2.35.0", org.eclipse.xtext.workspace;version="2.35.0"; - x-friends:="org.eclipse.xtext.xbase.ui, + x-friends:="org.eclipse.xtend.core, + org.eclipse.xtend.core.tests, + org.eclipse.xtend.ide.tests, org.eclipse.xtext.builder.tests, org.eclipse.xtext.ide, + org.eclipse.xtext.ide.tests, org.eclipse.xtext.tests, org.eclipse.xtext.ui, org.eclipse.xtext.ui.tests, org.eclipse.xtext.xbase, org.eclipse.xtext.xbase.junit, org.eclipse.xtext.xbase.testing, - org.eclipse.xtend.core, - org.eclipse.xtend.core.tests, - org.eclipse.xtend.ide.tests, - org.eclipse.xtext.ide.tests, - org.eclipse.xtext.xbase.tests", + org.eclipse.xtext.xbase.tests, + org.eclipse.xtext.xbase.ui, + org.eclipse.xtext.xbase.ide", org.eclipse.xtext.xtext;version="2.35.0"; x-friends:="org.eclipse.xtext.ide, org.eclipse.xtext.extras.tests,