From 86ece9e580916dedcc1eb6a09416186d0d6f80f6 Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Fri, 5 Dec 2014 14:53:57 +0100 Subject: [PATCH] [resource storage] Introduce support for storing computed resources, so the computed state can be loaded quickly when needed. (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=453047) Change-Id: I575c46c7e3fe89cb178adb26b02df74c25308df1 --- .../junit4/ui/util/IResourcesSetupUtil.java | 9 + .../junit4/ui/util/JavaProjectSetupUtil.java | 51 ++++ .../org.eclipse.xtext/.settings/.api_filters | 10 + .../org.eclipse.xtext/META-INF/MANIFEST.MF | 1 + .../generator/AbstractFileSystemAccess2.java | 16 +- ...ContextualOutputConfigurationProvider.java | 23 ++ .../IOutputConfigurationProvider.java | 14 +- .../OutputConfigurationProvider.java | 12 +- .../resource/DerivedStateAwareResource.java | 9 +- ...dStateAwareResourceDescriptionManager.java | 4 +- .../IResourceServiceProviderExtension.java | 21 ++ .../impl/DefaultResourceServiceProvider.java | 11 +- .../persistence/IResourceStorageFacade.xtend | 58 ++++ .../resource/persistence/PortableURIs.xtend | 172 ++++++++++++ .../persistence/ResourceStorageFacade.xtend | 129 +++++++++ .../persistence/ResourceStorageLoadable.xtend | 74 ++++++ .../ResourceStorageProviderAdapter.xtend | 29 ++ .../persistence/ResourceStorageWritable.xtend | 82 ++++++ .../SerializableResourceDescription.xtend | 251 ++++++++++++++++++ .../persistence/SourceLevelURIsAdapter.xtend | 48 ++++ .../persistence/StorageAwareResource.xtend | 90 +++++++ ...orageAwareResourceDescriptionManager.xtend | 29 ++ .../LangATestLanguageRuntimeModule.java | 9 +- .../persistence/PortableURIsTest.xtend | 67 +++++ .../SerializableResourceDescriptionTest.xtend | 88 ++++++ 25 files changed, 1296 insertions(+), 11 deletions(-) create mode 100644 plugins/org.eclipse.xtext/.settings/.api_filters create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IContextualOutputConfigurationProvider.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/IResourceServiceProviderExtension.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/IResourceStorageFacade.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/PortableURIs.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageFacade.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageLoadable.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageProviderAdapter.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageWritable.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescription.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SourceLevelURIsAdapter.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResource.xtend create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResourceDescriptionManager.xtend create mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/PortableURIsTest.xtend create mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescriptionTest.xtend diff --git a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/IResourcesSetupUtil.java b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/IResourcesSetupUtil.java index 8c83a553f..751a0d911 100644 --- a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/IResourcesSetupUtil.java +++ b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/IResourcesSetupUtil.java @@ -30,7 +30,9 @@ import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.ui.actions.WorkspaceModifyOperation; +import org.eclipse.ui.texteditor.MarkerUtilities; import org.eclipse.xtext.util.StringInputStream; +import org.junit.Assert; import com.google.common.io.ByteStreams; @@ -53,6 +55,13 @@ public class IResourcesSetupUtil { project.open(monitor()); return project; } + + public static void assertNoErrorsInWorkspace() throws CoreException { + IMarker[] findMarkers = ResourcesPlugin.getWorkspace().getRoot().findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); + for (IMarker iMarker : findMarkers) { + Assert.assertFalse(MarkerUtilities.getMarkerType(iMarker)+"-"+MarkerUtilities.getMessage(iMarker), MarkerUtilities.getSeverity(iMarker) == IMarker.SEVERITY_ERROR); + } + } public static void addNature(IProject project, String nature) throws CoreException { diff --git a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/JavaProjectSetupUtil.java b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/JavaProjectSetupUtil.java index 22d253575..9d2b2281c 100644 --- a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/JavaProjectSetupUtil.java +++ b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/ui/util/JavaProjectSetupUtil.java @@ -42,11 +42,19 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.core.ClasspathEntry; import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.launching.IVMInstall; +import org.eclipse.jdt.launching.IVMInstall2; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jdt.launching.environments.IExecutionEnvironment; +import org.eclipse.jdt.launching.environments.IExecutionEnvironmentsManager; import org.eclipse.xtext.ui.util.JREContainerProvider; import org.eclipse.xtext.util.RuntimeIOException; +import org.eclipse.xtext.xbase.lib.Pair; import org.osgi.service.prefs.BackingStoreException; import com.google.common.collect.Lists; +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Bytes; /** * @author Jan Koehnlein - Initial contribution and API @@ -85,6 +93,22 @@ public class JavaProjectSetupUtil { throw new WrappedException(e); } } + + public static InputStream jarInputStream(Pair ...entries) { + try { + ByteArrayOutputStream out2 = new ByteArrayOutputStream(); + JarOutputStream jo = new JarOutputStream(new BufferedOutputStream(out2)); + for (Pair entry : entries) { + JarEntry je = new JarEntry(entry.getKey()); + jo.putNextEntry(je); + ByteStreams.copy(entry.getValue(), jo); + } + jo.close(); + return new ByteArrayInputStream(out2.toByteArray()); + } catch (IOException e) { + throw new WrappedException(e); + } + } public static IJavaProject createJavaProject(String projectName) throws CoreException { IProject project = createSimpleProject(projectName); @@ -274,11 +298,38 @@ public class JavaProjectSetupUtil { } public static void addJreClasspathEntry(IJavaProject javaProject) throws JavaModelException { + // init default mappings + makeJava7Default(); IClasspathEntry existingJreContainerClasspathEntry = getJreContainerClasspathEntry(javaProject); if (existingJreContainerClasspathEntry == null) { addToClasspath(javaProject, JREContainerProvider.getDefaultJREContainerEntry()); } } + + + private static boolean isJava7Default = false; + + public static void makeJava7Default() { + if (!isJava7Default) { + IExecutionEnvironmentsManager manager = JavaRuntime.getExecutionEnvironmentsManager(); + IExecutionEnvironment[] environments = manager.getExecutionEnvironments(); + for (int i = 0; i < environments.length; i++) { + IExecutionEnvironment environment = environments[i]; + if (environment.getId().equals("JavaSE-1.6") && environment.getDefaultVM() == null) { + IVMInstall[] compatibleVMs = environment.getCompatibleVMs(); + for (IVMInstall ivmInstall : compatibleVMs) { + if (ivmInstall instanceof IVMInstall2) { + IVMInstall2 install2 = (IVMInstall2) ivmInstall; + if (install2.getJavaVersion().startsWith("1.7")) { + environment.setDefaultVM(ivmInstall); + } + } + } + } + } + isJava7Default = true; + } + } public static void makeJava5Compliant(IJavaProject javaProject) { @SuppressWarnings("unchecked") diff --git a/plugins/org.eclipse.xtext/.settings/.api_filters b/plugins/org.eclipse.xtext/.settings/.api_filters new file mode 100644 index 000000000..790925b5c --- /dev/null +++ b/plugins/org.eclipse.xtext/.settings/.api_filters @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF b/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF index dcd04a1ad..658ba30ed 100644 --- a/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.xtext/META-INF/MANIFEST.MF @@ -61,6 +61,7 @@ Export-Package: org.eclipse.xtext, org.eclipse.xtext.resource.containers, org.eclipse.xtext.resource.generic, org.eclipse.xtext.resource.impl, + org.eclipse.xtext.resource.persistence;x-friends:="org.eclipse.xtext.tests,org.eclipse.xtend.core,org.eclipse.xtend.core.tests", org.eclipse.xtext.scoping, org.eclipse.xtext.scoping.impl, org.eclipse.xtext.serializer, diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/AbstractFileSystemAccess2.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/AbstractFileSystemAccess2.java index 7514b3e80..02aa9d23f 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/AbstractFileSystemAccess2.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/AbstractFileSystemAccess2.java @@ -9,6 +9,8 @@ package org.eclipse.xtext.generator; import java.io.InputStream; +import org.eclipse.emf.ecore.resource.Resource; + /** * * Abstract base class for file system access supporting {@link IFileSystemAccessExtension3}. @@ -16,7 +18,7 @@ import java.io.InputStream; * @author Sven Efftinge - Initial contribution and API * @since 2.4 */ -public abstract class AbstractFileSystemAccess2 extends AbstractFileSystemAccess implements IFileSystemAccessExtension3{ +public abstract class AbstractFileSystemAccess2 extends AbstractFileSystemAccess implements IFileSystemAccessExtension3 { /** * @since 2.4 @@ -43,4 +45,16 @@ public abstract class AbstractFileSystemAccess2 extends AbstractFileSystemAccess } + /** + * Sets the context to further configure this file system access instance. + * + * @param context - a context from which project configuration can be obtained. Supported context types + * depend on the concrete implementation, but {@link Resource} is usually a good fit. + * + * @since 2.8 + */ + public void setContext(Object context) { + // do nothing + } + } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IContextualOutputConfigurationProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IContextualOutputConfigurationProvider.java new file mode 100644 index 000000000..840e42daa --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IContextualOutputConfigurationProvider.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.generator; + +import java.util.Set; + +import org.eclipse.emf.ecore.resource.Resource; + +import com.google.inject.ImplementedBy; + +/** + * @since 2.8 + */ +@ImplementedBy(OutputConfigurationProvider.class) +public interface IContextualOutputConfigurationProvider { + + Set getOutputConfigurations(Resource context); +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IOutputConfigurationProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IOutputConfigurationProvider.java index 956f7a8cd..44c6eb04e 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IOutputConfigurationProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/IOutputConfigurationProvider.java @@ -9,6 +9,8 @@ package org.eclipse.xtext.generator; import java.util.Set; +import org.eclipse.emf.ecore.resource.Resource; + import com.google.inject.ImplementedBy; /** @@ -19,7 +21,7 @@ import com.google.inject.ImplementedBy; public interface IOutputConfigurationProvider { Set getOutputConfigurations(); - class Delegate implements IOutputConfigurationProvider { + class Delegate implements IOutputConfigurationProvider, IContextualOutputConfigurationProvider { private IOutputConfigurationProvider delegate; public IOutputConfigurationProvider getDelegate() { @@ -36,5 +38,15 @@ public interface IOutputConfigurationProvider { return delegate.getOutputConfigurations(); } + /** + * @since 2.8 + */ + public Set getOutputConfigurations(Resource context) { + if (delegate instanceof IContextualOutputConfigurationProvider) { + return ((IContextualOutputConfigurationProvider) delegate).getOutputConfigurations(context); + } + return delegate.getOutputConfigurations(); + } + } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/OutputConfigurationProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/OutputConfigurationProvider.java index 66ee7f0fd..49b4a19d6 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/OutputConfigurationProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/generator/OutputConfigurationProvider.java @@ -11,11 +11,13 @@ import static com.google.common.collect.Sets.*; import java.util.Set; +import org.eclipse.emf.ecore.resource.Resource; + /** * @author Sven Efftinge - Initial contribution and API * @since 2.1 */ -public class OutputConfigurationProvider implements IOutputConfigurationProvider { +public class OutputConfigurationProvider implements IOutputConfigurationProvider, IContextualOutputConfigurationProvider { /** * @return a set of {@link OutputConfiguration} available for the generator @@ -32,4 +34,12 @@ public class OutputConfigurationProvider implements IOutputConfigurationProvider defaultOutput.setKeepLocalHistory(true); return newHashSet(defaultOutput); } + + /** + * @since 2.8 + */ + @Override + public Set getOutputConfigurations(Resource context) { + return getOutputConfigurations(); + } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java index 6f2ae10cf..5883d67e8 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResource.java @@ -15,8 +15,8 @@ import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.resource.impl.ResourceImpl; -import org.eclipse.xtext.linking.lazy.LazyLinkingResource; import org.eclipse.xtext.parser.IParseResult; +import org.eclipse.xtext.resource.persistence.StorageAwareResource; import org.eclipse.xtext.util.IResourceScopeCache; import org.eclipse.xtext.util.OnChangeEvictingCache; @@ -28,7 +28,7 @@ import com.google.inject.Inject; * @author Sven Efftinge - Initial contribution and API * @since 2.1 */ -public class DerivedStateAwareResource extends LazyLinkingResource { +public class DerivedStateAwareResource extends StorageAwareResource { @Inject(optional=true) private IDerivedStateComputer derivedStateComputer; @@ -62,7 +62,7 @@ public class DerivedStateAwareResource extends LazyLinkingResource { */ @Override public synchronized EList getContents() { - if (isLoaded && !isLoading && !isInitializing && !isUpdating && !fullyInitialized) { + if (isLoaded && !isLoading && !isInitializing && !isUpdating && !fullyInitialized && !isLoadedFromStorage()) { try { eSetDeliver(false); installDerivedState(false); @@ -109,6 +109,7 @@ public class DerivedStateAwareResource extends LazyLinkingResource { unloaded((InternalEObject) allContents.next()); } setParseResult(null); + setIsLoadedFromStorage(false); } /** @@ -189,7 +190,7 @@ public class DerivedStateAwareResource extends LazyLinkingResource { public void installDerivedState(boolean preIndexingPhase) { if (!isLoaded) throw new IllegalStateException("The resource must be loaded, before installDerivedState can be called."); - if (!fullyInitialized && !isInitializing) { + if (!fullyInitialized && !isInitializing && !isLoadedFromStorage()) { try { isInitializing = true; if (derivedStateComputer != null) diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResourceDescriptionManager.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResourceDescriptionManager.java index 69740889f..ec1a5a7f8 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResourceDescriptionManager.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/DerivedStateAwareResourceDescriptionManager.java @@ -12,8 +12,8 @@ import java.io.IOException; import org.apache.log4j.Logger; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.resource.impl.DefaultResourceDescription; -import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionManager; import org.eclipse.xtext.resource.impl.EObjectDescriptionLookUp; +import org.eclipse.xtext.resource.persistence.StorageAwareResourceDescriptionManager; import org.eclipse.xtext.util.IResourceScopeCache; import org.eclipse.xtext.util.RuntimeIOException; @@ -28,7 +28,7 @@ import com.google.inject.Singleton; * @since 2.1 */ @Singleton -public class DerivedStateAwareResourceDescriptionManager extends DefaultResourceDescriptionManager { +public class DerivedStateAwareResourceDescriptionManager extends StorageAwareResourceDescriptionManager { private final static Logger log = Logger.getLogger(DerivedStateAwareResourceDescriptionManager.class); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/IResourceServiceProviderExtension.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/IResourceServiceProviderExtension.java new file mode 100644 index 000000000..dd54f1386 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/IResourceServiceProviderExtension.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource; + +import org.eclipse.emf.common.util.URI; + +/** + * @author Sven Efftinge - Initial contribution and API + * + * @noimplement This interface is not intended to be implemented by clients. + * @since 2.8 + */ +public interface IResourceServiceProviderExtension { + + public boolean isReadOnly(URI uri); +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/impl/DefaultResourceServiceProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/impl/DefaultResourceServiceProvider.java index 1196253a0..842b0f7a0 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/impl/DefaultResourceServiceProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/impl/DefaultResourceServiceProvider.java @@ -13,6 +13,7 @@ import org.eclipse.xtext.resource.FileExtensionProvider; import org.eclipse.xtext.resource.IContainer; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.resource.IResourceServiceProvider; +import org.eclipse.xtext.resource.IResourceServiceProviderExtension; import org.eclipse.xtext.validation.IResourceValidator; import com.google.inject.ConfigurationException; @@ -22,7 +23,7 @@ import com.google.inject.Injector; /** * @author Sven Efftinge - Initial contribution and API */ -public class DefaultResourceServiceProvider implements IResourceServiceProvider { +public class DefaultResourceServiceProvider implements IResourceServiceProvider, IResourceServiceProviderExtension { @Inject private IContainer.Manager containerManager; @@ -75,5 +76,13 @@ public class DefaultResourceServiceProvider implements IResourceServiceProvider return null; } } + + /** + * @since 2.8 + */ + @Override + public boolean isReadOnly(URI uri) { + return uri.isArchive(); + } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/IResourceStorageFacade.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/IResourceStorageFacade.xtend new file mode 100644 index 000000000..84b7c878d --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/IResourceStorageFacade.xtend @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import org.eclipse.xtext.generator.IFileSystemAccessExtension3 +import java.io.OutputStream +import java.io.InputStream +import org.eclipse.emf.common.util.URI + +/** + * @author Sven Efftinge - Initial contribution and API + * + * @noimplement + * @noextend + * @since 2.8 + */ +interface IResourceStorageFacade { + + /** + * @return whether the given resource should and can be loaded from stored resource state + */ + def boolean shouldLoadFromStorage(StorageAwareResource resource) + + /** + * @return whether storage data exists for the given URI + */ + def boolean hasStorageFor(URI uri) + + /** + * Finds or creates a ResourceStorageLoadable for the given resource. + * Clients should first call shouldLoadFromStorage to check whether there exists a storage version + * of the given resource. + * + * @return an IResourceStorageLoadable + */ + def ResourceStorageLoadable getOrCreateResourceStorageLoadable(StorageAwareResource resource) + + /** + * Saves the resource using the given file system access. + */ + def void saveResource(StorageAwareResource resource, IFileSystemAccessExtension3 fsa) + + /** + * Creates a fresh ResourceStorageWritable wrapping the given OutputStream + */ + def ResourceStorageWritable createResourceStorageWritable(OutputStream outputStream) + + /** + * Creates a fresh ResourceStorageLoadable wrapping the given InputStream + */ + def ResourceStorageLoadable createResourceStorageLoadable(InputStream inputStream) + +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/PortableURIs.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/PortableURIs.xtend new file mode 100644 index 000000000..f6040f6e5 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/PortableURIs.xtend @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import com.google.common.base.Predicates +import com.google.common.base.Splitter +import com.google.inject.Inject +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.EObject +import org.eclipse.emf.ecore.EcoreFactory +import org.eclipse.emf.ecore.EcorePackage +import org.eclipse.emf.ecore.InternalEObject +import org.eclipse.emf.ecore.util.EcoreUtil +import org.eclipse.xtend.lib.annotations.Data +import org.eclipse.xtext.naming.QualifiedName +import org.eclipse.xtext.resource.IEObjectDescription +import org.eclipse.xtext.resource.XtextResource +import org.eclipse.xtext.scoping.IGlobalScopeProvider +import org.eclipse.xtext.linking.lazy.LazyURIEncoder + +/** + * Portable URIs are based on names and therefore are independent of the concrete file pathes and fuile names the + * of resources. + * + * A portable URI is really a resource URI to the client URI and a fragment that contains the information to retrieve the + * referenced element using the global scoping. That is it contains + *
    + *
  • the qualified name of a container of the target element + *
  • the type of that container + *
  • the path from that container to the actual target element + *
+ * + * @author Sven Efftinge - Initial contribution and API + * + * @since 2.8 + */ +class PortableURIs { + + public static val PORTABLE_SCHEME = "portable" + + @Inject IGlobalScopeProvider globalScopeProvider + @Inject LazyURIEncoder lazyURIencoder + + def boolean isPortableURIFragment(String uriFragment) { + uriFragment.startsWith(PORTABLE_SCHEME) + } + + def EObject resolve(StorageAwareResource resource, String portableFragment) { + val desc = fromFragmentString(portableFragment) + val mock = EcoreFactory.eINSTANCE.createEReference + mock.EType = EcorePackage.Literals.EOBJECT + val scope = globalScopeProvider.getScope(resource, mock, Predicates.alwaysTrue) + val description = scope.getElements(desc.descriptionQualifiedName).findFirst[ + EClass.name == desc.descriptionEClassName + ] + if (description == null) { + return null + } + val container = EcoreUtil.resolve(description.EObjectOrProxy, resource) + return getEObject(container, desc.descriptionRelativeFragment) + } + + def URI toPortableURI(StorageAwareResource res, URI uri, String fragment) { + if (res.URI == uri && lazyURIencoder.isCrossLinkFragment(res, fragment)) { + // try resolve + val result = res.getEObject(fragment) + if (result==null || result.eIsProxy) { + //this means it is an unresolvable lazy link, we won't be able to resolve it later + return uri.appendFragment(StorageAwareResource.UNRESOLVABLE_FRAGMENT) + } else { + val portableFragment = getPortableURIFragment(result) + if (portableFragment != null) { + return res.URI.appendFragment(portableFragment) + } + } + } + val resource = res.resourceSet.getResource(uri, false) + if (resource != null) { + val obj = resource.getEObject(fragment) + if (obj != null) { + val portableFragment = getPortableURIFragment(obj) + if (portableFragment != null) { + return res.URI.appendFragment(portableFragment) + } + } + } + return uri.appendFragment(fragment) + } + + protected def String getPortableURIFragment(EObject obj) { + switch res : obj.eResource { + XtextResource : { + val desc = res.resourceServiceProvider.resourceDescriptionManager.getResourceDescription(obj.eResource) + val containerDesc = desc.exportedObjects.findFirst [ + val possibleContainer = EcoreUtil.resolve(EObjectOrProxy, res) + obj==possibleContainer || EcoreUtil.isAncestor(obj, possibleContainer) + ] + if (containerDesc != null) { + val fragment = createPortableURIFragment(containerDesc, obj) + return fragment + } + } + } + return null + } + + protected def String createPortableURIFragment(IEObjectDescription desc, EObject target) { + val possibleContainer = EcoreUtil.resolve(desc.EObjectOrProxy, target) + val fragmentToTarget = getFragment(target, possibleContainer) + val portableDescription = new PortableFragmentDescription(desc.EClass.name, desc.qualifiedName, fragmentToTarget) + return toFragmentString(portableDescription) + } + + protected def String toFragmentString(PortableFragmentDescription desc) { + val typeName = desc.descriptionEClassName + val segments = desc.descriptionQualifiedName.segments + //TODO use safe delimiter algorithm (library) + var uriFragment = PORTABLE_SCHEME + '#' + typeName + '#'+ segments.join(':') + if (desc.descriptionRelativeFragment != null) { + uriFragment += '#' + desc.descriptionRelativeFragment + } + return uriFragment + } + + protected def PortableFragmentDescription fromFragmentString(String fragmentString) { + val segments = Splitter.on('#').split(fragmentString).iterator + segments.next // skip first + val eClassName = segments.next + val qname = QualifiedName.create(Splitter.on(':').split(segments.next).toList) + val fragment = if (segments.hasNext) { + segments.next + } + return new PortableFragmentDescription(eClassName, qname, fragment) + } + + protected def String getFragment(EObject fromContainer, EObject toChild) { + if (fromContainer == toChild) + return null + var lastChild = toChild as InternalEObject + var lastContainer = lastChild.eInternalContainer + var result = lastContainer.eURIFragmentSegment(lastChild.eContainingFeature, lastChild) + while (lastContainer != null && fromContainer != lastContainer) { + lastChild = lastContainer + lastContainer = lastContainer.eInternalContainer + if (lastContainer == null) { + throw new IllegalStateException("No more containers for element "+lastChild) + } + result = lastContainer.eURIFragmentSegment(lastChild.eContainingFeature, lastChild)+'/'+result + } + return result + } + + protected def EObject getEObject(EObject from, String toFragment) { + if (toFragment == null) + return from + val splitted = Splitter.on("/").split(toFragment) + return splitted.fold(from) [ + ($0 as InternalEObject).eObjectForURIFragmentSegment($1) + ] + } + + @Data static class PortableFragmentDescription { + String descriptionEClassName + QualifiedName descriptionQualifiedName + String descriptionRelativeFragment + } +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageFacade.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageFacade.xtend new file mode 100644 index 000000000..0d9d8af41 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageFacade.xtend @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import com.google.inject.Inject +import com.google.inject.Provider +import org.eclipse.xtext.generator.AbstractFileSystemAccess2 +import org.eclipse.xtext.generator.IContextualOutputConfigurationProvider +import java.io.InputStream +import java.io.OutputStream +import org.eclipse.xtext.generator.IFileSystemAccessExtension3 +import java.io.ByteArrayOutputStream +import java.io.ByteArrayInputStream +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.resource.impl.ExtensibleURIConverterImpl + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class ResourceStorageFacade implements IResourceStorageFacade { + + @Inject IContextualOutputConfigurationProvider outputConfigurationProvider + @Inject Provider fileSystemAccessProvider + + /** + * @return whether the given resource should be loaded from stored resource state + */ + override boolean shouldLoadFromStorage(StorageAwareResource resource) { + val adapter = SourceLevelURIsAdapter.findInstalledAdapter(resource.resourceSet) + if (adapter == null) { + return false; + } else { + if (adapter.sourceLevelURIs.contains(resource.URI)) + return false; + } + return doesStorageExist(resource) + } + + /** + * Finds or creates a ResourceStorageLoadable for the given resource. + * Clients should first call shouldLoadFromStorage to check whether there exists a storage version + * of the given resource. + * + * @return an IResourceStorageLoadable + */ + override ResourceStorageLoadable getOrCreateResourceStorageLoadable(StorageAwareResource resource) { + val stateProvider = resource.resourceSet.eAdapters.filter(ResourceStorageProviderAdapter).head + if (stateProvider != null) { + val inputStream = stateProvider.getResourceStorageLoadable(resource) + if (inputStream != null) + return inputStream + } + val inputStream = if (resource.resourceSet.URIConverter.exists(resource.URI.getBinaryStrorageURI, emptyMap)) { + resource.resourceSet.URIConverter.createInputStream(resource.URI.getBinaryStrorageURI) + } else { + val fsa = getFileSystemAccess(resource); + val outputRelativePath = computeOutputPath(resource) + fsa.readBinaryFile(outputRelativePath) + } + return createResourceStorageLoadable(inputStream) + } + + override void saveResource(StorageAwareResource resource, IFileSystemAccessExtension3 fsa) { + val path = computeOutputPath(resource) + val bout = new MyByteArrayOutputStream() + val outStream = createResourceStorageWritable(bout) + outStream.writeResource(resource) + fsa.generateFile(path, new ByteArrayInputStream(bout.toByteArray, 0, bout.length)) + } + + override def ResourceStorageLoadable createResourceStorageLoadable(InputStream in) { + return new ResourceStorageLoadable(in) + } + + override def ResourceStorageWritable createResourceStorageWritable(OutputStream out) { + return new ResourceStorageWritable(out) + } + + /** + * @return whether a stored resource state exists for the given resource + */ + protected def doesStorageExist(StorageAwareResource resource) { + val stateProvider = resource.resourceSet.eAdapters.filter(ResourceStorageProviderAdapter).head + if (stateProvider!=null && stateProvider.getResourceStorageLoadable(resource) != null) + return true; + // check for next to original location, i.e. jars + if (resource.resourceSet.URIConverter.exists(resource.URI.getBinaryStrorageURI, emptyMap)) { + return true + } + + // check for source project locations, i.e. use generator config + val fsa = getFileSystemAccess(resource); + val outputRelativePath = computeOutputPath(resource) + val uri = fsa.getURI(outputRelativePath) + return resource.resourceSet.URIConverter.exists(uri, null) + } + + protected def getFileSystemAccess(StorageAwareResource resource) { + val fsa = fileSystemAccessProvider.get() + fsa.context = resource + fsa.outputConfigurations = outputConfigurationProvider.getOutputConfigurations(resource).toMap[name] + return fsa + } + + protected def computeOutputPath(StorageAwareResource resource) { + val uri = resource.URI.getBinaryStrorageURI + val srcFolderPath = uri.trimFileExtension.trimSegments(uri.segmentCount-3).toString + val outputRelativePath = uri.toString.substring(srcFolderPath.length+1) + return outputRelativePath + } + + override hasStorageFor(URI uri) { + new ExtensibleURIConverterImpl().exists(getBinaryStrorageURI(uri), emptyMap()) + } + + protected def getBinaryStrorageURI(URI sourceURI) { + return sourceURI.trimSegments(1).appendSegment("."+sourceURI.lastSegment+'bin') + } + + private static class MyByteArrayOutputStream extends ByteArrayOutputStream { + override toByteArray() { buf } + def int length() { count } + } +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageLoadable.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageLoadable.xtend new file mode 100644 index 000000000..2b0b7f869 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageLoadable.xtend @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import java.io.IOException +import java.io.InputStream +import java.util.zip.ZipInputStream +import org.apache.log4j.Logger +import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl +import org.eclipse.xtend.lib.annotations.Data +import java.io.ObjectInputStream + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Data class ResourceStorageLoadable { + + static val LOG = Logger.getLogger(ResourceStorageLoadable) + + InputStream in + + protected def void loadIntoResource(StorageAwareResource resource) { + try { + if (!resource.isLoadedFromStorage) { + throw new IllegalStateException("Please use StorageAwareResource#load(ResourceStorageLoadable)."); + } + val zin = new ZipInputStream(in) + try { + loadEntries(resource, zin) + } finally { + zin.close + } + } catch (IOException e) { + LOG.error("Problem loading storage for "+resource.URI+". Error was:"+e.message, e) + } + } + + /** + * Load entries from the storage. + * Overriding methods should first delegate to super before adding their own entries. + */ + protected def void loadEntries(StorageAwareResource resource, ZipInputStream zipIn) { + readContents(resource, zipIn) + readResourceDescription(resource, zipIn) + } + + protected def void readResourceDescription(StorageAwareResource resource, ZipInputStream zipIn) { + zipIn.nextEntry + val objectIn = new ObjectInputStream(zipIn) + val description = objectIn.readObject as SerializableResourceDescription + description.updateResourceURI(resource.URI) + resource.resourceDescription = description + } + + protected def void readContents(StorageAwareResource resource, ZipInputStream zipIn) { + zipIn.nextEntry + val in = new BinaryResourceImpl.EObjectInputStream(zipIn, emptyMap) { + + override readCompressedInt() throws IOException { + //HACK! null resource set, to avoid usage of resourceSet's package registry + resourceSet = null + super.readCompressedInt() + } + + } + in.loadResource(resource) + } + +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageProviderAdapter.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageProviderAdapter.xtend new file mode 100644 index 000000000..ff91725bf --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageProviderAdapter.xtend @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import org.eclipse.emf.common.notify.impl.AdapterImpl + +/** + * An adapter that can be installed into a SerializableResource, + * to provide resource state. It is used with dirty editors providing the dirty non persisted + * state to other editors. + * + * @author Sven Efftinge - Initial contribution and API + */ +class ResourceStorageProviderAdapter extends AdapterImpl { + + override isAdapterForType(Object type) { + type == ResourceStorageProviderAdapter + } + + def ResourceStorageLoadable getResourceStorageLoadable(StorageAwareResource resource) { + return null + } + +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageWritable.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageWritable.xtend new file mode 100644 index 000000000..b50896768 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/ResourceStorageWritable.xtend @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import java.io.IOException +import java.io.ObjectOutputStream +import java.io.OutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import org.apache.log4j.Logger +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl +import org.eclipse.xtend.lib.annotations.Data + +/** + * @author Sven Efftinge - Initial contribution and API + */ +@Data class ResourceStorageWritable { + + static val LOG = Logger.getLogger(ResourceStorageWritable) + + OutputStream out + + def void writeResource(StorageAwareResource resource) { + if (resource.isLoadedFromStorage) { + throw new IllegalStateException("cannot write resources loaded from storage. URI was "+resource.URI) + } + val zipOut = new ZipOutputStream(out) + try { + writeEntries(resource, zipOut) + } catch (IOException e) { + LOG.error(e.message, e) + } finally { + zipOut.close + } + } + + /** + * Write entries into the storage. + * Overriding methods should first delegate to super before adding their own entries. + */ + protected def void writeEntries(StorageAwareResource resource, ZipOutputStream zipOut) { + writeContents(resource, zipOut) + writeResourceDescription(resource, zipOut) + } + + protected def void writeContents(StorageAwareResource storageAwareResource, ZipOutputStream zipOut) { + zipOut.putNextEntry(new ZipEntry("emf-contents")) + val out = new BinaryResourceImpl.EObjectOutputStream(zipOut, emptyMap) { + override writeURI(URI uri, String fragment) throws IOException { + val portableURI = storageAwareResource.portableURIs.toPortableURI(storageAwareResource, uri, fragment) + super.writeURI(portableURI.trimFragment, portableURI.fragment) + } + } + try { + out.saveResource(storageAwareResource) + } finally { + out.flush + } + zipOut.closeEntry + } + + + + protected def void writeResourceDescription(StorageAwareResource resource, ZipOutputStream zipOut) { + zipOut.putNextEntry(new ZipEntry("resource-description")) + val description = resource.resourceServiceProvider.resourceDescriptionManager.getResourceDescription(resource); + val serializableDescription = SerializableResourceDescription.createCopy(description) + val out = new ObjectOutputStream(zipOut); + try { + out.writeObject(serializableDescription); + } finally { + out.flush + } + zipOut.closeEntry + } +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescription.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescription.xtend new file mode 100644 index 000000000..8101f4480 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescription.xtend @@ -0,0 +1,251 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import java.io.Externalizable +import java.io.IOException +import java.io.ObjectInput +import java.io.ObjectOutput +import java.util.ArrayList +import java.util.HashMap +import java.util.List +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.EClass +import org.eclipse.emf.ecore.ENamedElement +import org.eclipse.emf.ecore.EObject +import org.eclipse.emf.ecore.EPackage +import org.eclipse.emf.ecore.EReference +import org.eclipse.emf.ecore.InternalEObject +import org.eclipse.emf.ecore.util.EcoreUtil +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtext.naming.QualifiedName +import org.eclipse.xtext.resource.IEObjectDescription +import org.eclipse.xtext.resource.IReferenceDescription +import org.eclipse.xtext.resource.IResourceDescription +import org.eclipse.xtext.resource.impl.AbstractResourceDescription + +import static extension org.eclipse.xtext.resource.persistence.SerializationExtensions.* + +/** + * @author Sven Efftinge - Initial contribution and API + * + * @since 2.8 + */ +@Accessors class SerializableResourceDescription extends AbstractResourceDescription implements Externalizable { + + def static SerializableResourceDescription createCopy(IResourceDescription desc) { + new SerializableResourceDescription => [ + URI = desc.URI + descriptions = desc.exportedObjects.map[createCopy(it)].toList + references = desc.referenceDescriptions.map[createCopy(it)].toList + importedNames = newArrayList(desc.importedNames) + ] + } + + private def static SerializableEObjectDescription createCopy(IEObjectDescription desc) { + new SerializableEObjectDescription => [ + EClass = desc.EClass + EObjectURI = desc.EObjectURI + qualifiedName = desc.qualifiedName + userData = new HashMap(desc.userDataKeys.size) + for (key : desc.userDataKeys) { + userData.put(key, desc.getUserData(key)) + } + ] + } + + private def static SerializableReferenceDescription createCopy(IReferenceDescription desc) { + new SerializableReferenceDescription => [ + sourceEObjectUri = desc.sourceEObjectUri + targetEObjectUri = desc.targetEObjectUri + EReference = desc.EReference + indexInList = desc.indexInList + containerEObjectURI = desc.containerEObjectURI + ] + } + + List descriptions = emptyList + List references = emptyList + List importedNames = emptyList + URI uRI + + def void updateResourceURI(URI uri) { + this.uRI = uri + for (desc : descriptions) { + desc.updateResourceURI(uri) + } + } + + override protected computeExportedObjects() { + descriptions as List as List + } + + override getImportedNames() { + importedNames + } + + override getReferenceDescriptions() { + references as Iterable as Iterable + } + + override readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + URI = in.readURI + val descriptionsSize = in.readInt + descriptions = new ArrayList(descriptionsSize); + for (i : 0 ..< descriptionsSize) { + descriptions.add(in.readCastedObject) + } + //reference descriptions are not portable atm. +// val referencesSize = in.readInt +// references = new ArrayList(referencesSize); +// for (i : 0 ..< referencesSize) { +// references.add(in.readCastedObject) +// } + val importedNamesSize = in.readInt + importedNames = new ArrayList(importedNamesSize) + for (i : 0 ..< importedNamesSize) { + importedNames.add(in.readQualifiedName) + } + } + + override writeExternal(ObjectOutput out) throws IOException { + out.writeURI(uRI) + out.writeInt(descriptions.size) + for (desc : descriptions) { + out.writeObject(desc) + } + //reference descriptions are not portable atm. +// out.writeInt(references.size) +// for (ref : references) { +// out.writeObject(ref) +// } + out.writeInt(importedNames.size) + for (name : importedNames) { + out.writeQualifiedName(name) + } + } + +} + +/** + * @since 2.8 + */ +@Accessors class SerializableEObjectDescription implements IEObjectDescription, Externalizable { + + URI eObjectURI + EClass eClass + QualifiedName qualifiedName + HashMap userData + @Accessors(NONE) transient EObject eObjectOrProxy + + def void updateResourceURI(URI uri) { + eObjectURI = uri.appendFragment(eObjectURI.fragment) + } + + override getEObjectOrProxy() { + if (eObjectOrProxy == null) { + val proxy = EcoreUtil.create(eClass) + (proxy as InternalEObject).eSetProxyURI(eObjectURI) + eObjectOrProxy = proxy + } + return eObjectOrProxy + } + + override getName() { + qualifiedName + } + + override getUserData(String key) { + userData.get(key) + } + + override getUserDataKeys() { + userData.keySet + } + + override readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + eObjectURI = in.readURI + eClass = in.readEcoreElement + qualifiedName = in.readQualifiedName + userData = in.readCastedObject + } + + override writeExternal(ObjectOutput out) throws IOException { + out.writeURI(eObjectURI) + out.writeURI(EcoreUtil.getURI(eClass)) + out.writeQualifiedName(qualifiedName) + out.writeObject(userData) + } + +} + +/** + * @since 2.8 + */ +@Accessors class SerializableReferenceDescription implements IReferenceDescription, Externalizable { + URI sourceEObjectUri + URI targetEObjectUri + URI containerEObjectURI + EReference eReference + int indexInList + + override readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + sourceEObjectUri = in.readURI + targetEObjectUri = in.readURI + containerEObjectURI = in.readURI + eReference = in.readEcoreElement + indexInList = in.readInt + } + + override writeExternal(ObjectOutput out) throws IOException { + out.writeURI(sourceEObjectUri) + out.writeURI(targetEObjectUri) + out.writeURI(containerEObjectURI) + out.writeEcoreElement(eReference) + out.writeInt(indexInList) + } + +} + + +/** + * @since 2.8 + */ +package class SerializationExtensions { + + def static T readEcoreElement(ObjectInput in) { + val uri = in.readURI + val ePackage = EPackage.Registry.INSTANCE.getEPackage(uri.trimFragment.toString) + return ePackage.eResource.getEObject(uri.fragment) as T + } + + def static void writeEcoreElement(ObjectOutput out, ENamedElement namedElement) { + val uri = EcoreUtil.getURI(namedElement) + out.writeURI(uri) + } + + def static T readCastedObject(ObjectInput in) { + in.readObject as T + } + + def static URI readURI(ObjectInput in) { + return URI::createURI(in.readUTF) + } + + def static void writeURI(ObjectOutput out, URI uri) { + out.writeUTF(uri.toString) + } + + def static QualifiedName readQualifiedName(ObjectInput in) { + return QualifiedName.create(in.readObject as ArrayList) + } + + def static void writeQualifiedName(ObjectOutput out, QualifiedName name) { + out.writeObject(new ArrayList(name.segments)) + } +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SourceLevelURIsAdapter.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SourceLevelURIsAdapter.xtend new file mode 100644 index 000000000..cb0c48d88 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/SourceLevelURIsAdapter.xtend @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import com.google.common.collect.ImmutableSet +import java.util.Collection +import org.eclipse.emf.common.notify.impl.AdapterImpl +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.resource.ResourceSet +import org.eclipse.xtend.lib.annotations.Accessors + +/** + * An adapter to be installed into a ResourceSet. + * + * It's used as a protocol to tell whether a StorageAwareResource + * should load from source or could load from serialized data. + * + * @see ResourceStorageProviderAdapter + * + * @author Sven Efftinge - Initial contribution and API + * + * @since 2.8 + */ +class SourceLevelURIsAdapter extends AdapterImpl { + + @Accessors ImmutableSet sourceLevelURIs + + override isAdapterForType(Object type) { + return type == SourceLevelURIsAdapter + } + + def static void setSourceLevelUris(ResourceSet resourceSet, Collection uris) { + val adapter = findInstalledAdapter(resourceSet) + ?: (new SourceLevelURIsAdapter => [ + resourceSet.eAdapters += it + ]) + adapter.sourceLevelURIs = ImmutableSet.copyOf(uris) + } + + def static SourceLevelURIsAdapter findInstalledAdapter(ResourceSet resourceSet) { + resourceSet.eAdapters.filter(SourceLevelURIsAdapter).head + } +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResource.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResource.xtend new file mode 100644 index 000000000..2eb8a69d1 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResource.xtend @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import com.google.inject.Inject +import java.io.IOException +import java.util.Map +import org.apache.log4j.Logger +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtext.linking.lazy.LazyLinkingResource +import org.eclipse.xtext.resource.IResourceDescription +import org.eclipse.xtext.util.internal.Stopwatches + +/** + * A resource implementation that can load itself from ResourceStorage. + * + * @author Sven Efftinge - Initial contribution and API + */ +class StorageAwareResource extends LazyLinkingResource { + public static val UNRESOLVABLE_FRAGMENT = "UNRESOLVABLE" + static val Logger LOG = Logger.getLogger(StorageAwareResource) + + @Accessors(PUBLIC_GETTER) @Inject(optional=true) IResourceStorageFacade resourceStorageFacade + + @Accessors(PUBLIC_GETTER) @Inject PortableURIs portableURIs + + @Accessors boolean isLoadedFromStorage = false; + + @Accessors IResourceDescription resourceDescription = null; + + override load(Map options) throws IOException { + if (!isLoaded && !isLoading && resourceStorageFacade!=null && resourceStorageFacade.shouldLoadFromStorage(this)) { + if (LOG.isDebugEnabled) { + LOG.debug("Loading "+URI+" from storage.") + } + val in = resourceStorageFacade.getOrCreateResourceStorageLoadable(this); + load(in) + } else { + super.load(options) + } + } + + def void load(ResourceStorageLoadable storageInputStream) { + if (storageInputStream == null) { + throw new NullPointerException('storageInputStream') + } + val task = Stopwatches.forTask("Loading from storage") + task.start + isLoading = true; + isLoadedFromStorage = true; + try { + storageInputStream.loadIntoResource(this); + isLoaded = true; + } finally { + isLoading = false + task.stop + } + } + + override protected doUnload() { + super.doUnload + isLoadedFromStorage = false; + } + + override protected clearInternalState() { + isLoadedFromStorage = false; + super.clearInternalState(); + } + + override getEObject(String uriFragment) { + if (portableURIs.isPortableURIFragment(uriFragment)) { + return portableURIs.resolve(this, uriFragment) + } + super.getEObject(uriFragment) + } + + override protected getUnresolvableURIFragments() { + if (isLoadedFromStorage) { + return #{UNRESOLVABLE_FRAGMENT} + } else { + return super.getUnresolvableURIFragments() + } + } + +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResourceDescriptionManager.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResourceDescriptionManager.xtend new file mode 100644 index 000000000..89a3038bf --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/resource/persistence/StorageAwareResourceDescriptionManager.xtend @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import org.eclipse.xtext.resource.impl.DefaultResourceDescriptionManager +import org.eclipse.emf.ecore.resource.Resource +import org.eclipse.xtext.resource.persistence.StorageAwareResource + +/** + * @author Sven Efftinge - Initial contribution and API + * + * @since 2.8 + */ +class StorageAwareResourceDescriptionManager extends DefaultResourceDescriptionManager { + + override getResourceDescription(Resource resource) { + switch resource { + StorageAwareResource case resource.resourceDescription != null + : resource.resourceDescription + default : super.getResourceDescription(resource) + } + } + +} \ No newline at end of file diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/linking/LangATestLanguageRuntimeModule.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/linking/LangATestLanguageRuntimeModule.java index 735b0888b..3441a0417 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/linking/LangATestLanguageRuntimeModule.java +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/linking/LangATestLanguageRuntimeModule.java @@ -3,6 +3,9 @@ Generated with Xtext */ package org.eclipse.xtext.linking; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.persistence.StorageAwareResource; + import com.google.inject.Binder; /** @@ -16,5 +19,9 @@ public class LangATestLanguageRuntimeModule extends AbstractLangATestLanguageRun // extend configuration here } - + + @Override + public Class bindXtextResource() { + return StorageAwareResource.class; + } } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/PortableURIsTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/PortableURIsTest.xtend new file mode 100644 index 000000000..0be5023f0 --- /dev/null +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/PortableURIsTest.xtend @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import org.eclipse.emf.ecore.EObject +import org.eclipse.emf.ecore.EcorePackage +import org.junit.Assert +import org.junit.Test +import org.eclipse.xtext.junit4.AbstractXtextTests +import org.eclipse.xtext.linking.LangATestLanguageStandaloneSetup +import org.eclipse.xtext.resource.XtextResourceSet +import org.eclipse.emf.common.util.URI +import org.eclipse.xtext.linking.langATestLanguage.Main +import org.eclipse.emf.ecore.util.EcoreUtil + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class PortableURIsTest extends AbstractXtextTests { + + override setUp() throws Exception { + super.setUp(); + with(new LangATestLanguageStandaloneSetup()); + } + + @Test def void testPortableUris() { + val resourceSet = get(XtextResourceSet) + val resourceA = resourceSet.createResource(URI.createURI("hubba:/bubba.langatestlanguage")) as StorageAwareResource + val resourceB = resourceSet.createResource(URI.createURI("hubba:/bubba2.langatestlanguage")) as StorageAwareResource + resourceB.load(getAsStream(''' + type B + '''), null) + resourceA.load(getAsStream(''' + import 'hubba:/bubba2.langatestlanguage' + + type A extends B + '''), null) + val extended = resourceA.contents.filter(Main).head.types.head.extends + val uri = EcoreUtil.getURI(extended) + val portableURI = resourceA.portableURIs.toPortableURI(resourceA, uri.trimFragment, uri.fragment) + assertEquals(resourceA.URI, portableURI.trimFragment) + assertTrue(resourceA.portableURIs.isPortableURIFragment(portableURI.fragment)) + assertSame(extended, resourceA.getEObject(portableURI.fragment)) + } + + @Test def void testEObjectRelativeFragments() { + checkFragmentBothDirections(EcorePackage.eINSTANCE, EcorePackage.eINSTANCE.EAnnotation_Details) + checkFragmentBothDirections(EcorePackage.eINSTANCE.EAttribute_EAttributeType, EcorePackage.eINSTANCE.EAttribute_EAttributeType) + try { + checkFragmentBothDirections(EcorePackage.eINSTANCE.EAnnotation_EModelElement, EcorePackage.eINSTANCE.EAttribute_EAttributeType) + Assert.fail(); + } catch (IllegalStateException e) { + // expected + } + } + + def checkFragmentBothDirections(EObject container, EObject child) { + val fragments = new PortableURIs() + val fragment = fragments.getFragment(container, child) + Assert.assertSame(child, fragments.getEObject(container, fragment)) + } +} \ No newline at end of file diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescriptionTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescriptionTest.xtend new file mode 100644 index 000000000..b41b88da7 --- /dev/null +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/resource/persistence/SerializableResourceDescriptionTest.xtend @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2014 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.resource.persistence + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import org.eclipse.emf.common.util.URI +import org.eclipse.emf.ecore.EcorePackage +import org.eclipse.xtext.naming.QualifiedName +import org.eclipse.xtext.resource.persistence.SerializableEObjectDescription +import org.eclipse.xtext.resource.persistence.SerializableReferenceDescription +import org.eclipse.xtext.resource.persistence.SerializableResourceDescription +import org.junit.Test + +import static org.junit.Assert.* + +/** + * @author Sven Efftinge - Initial contribution and API + */ +class SerializableResourceDescriptionTest { + + @Test def void testSerialization() { + val uri = URI::createURI("file:/foo/bar.baz.foo") + val before = new SerializableResourceDescription => [ + URI = uri + references = #[ + new SerializableReferenceDescription => [ + sourceEObjectUri = uri.appendFragment('foo') + targetEObjectUri = uri.appendFragment('hubble') + containerEObjectURI = uri.appendFragment('baz') + EReference = EcorePackage.eINSTANCE.EAnnotation_Contents + indexInList = 1 + ], + new SerializableReferenceDescription => [ + sourceEObjectUri = uri.appendFragment('foo2') + targetEObjectUri = uri.appendFragment('hubble2') + containerEObjectURI = uri.appendFragment('baz2') + EReference = EcorePackage.eINSTANCE.EAnnotation_Contents + indexInList = 2 + ] + ] + descriptions = #[ + new SerializableEObjectDescription => [ + EObjectURI = uri.appendFragment('baz') + qualifiedName = QualifiedName.create('foo','baz') + EClass = EcorePackage.eINSTANCE.EAttribute + userData = newHashMap('myKey' -> 'myValue') + ] + ] + importedNames = #[QualifiedName.create('foo'), QualifiedName.create('foo','bar')] + ] + + val bout = new ByteArrayOutputStream() + val objectOut = new ObjectOutputStream(bout) + objectOut.writeObject(before) + val in = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray)) + val after = in.readObject as SerializableResourceDescription + assertEquals(before.URI, after.URI) + assertEquals(before.importedNames, after.importedNames) + assertEquals(before.references.size, after.references.size) + for (int i : 0..