[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
This commit is contained in:
Sven Efftinge 2014-12-05 14:53:57 +01:00
parent fdfe435236
commit 86ece9e580
25 changed files with 1296 additions and 11 deletions

View file

@ -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 {

View file

@ -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<String, InputStream> ...entries) {
try {
ByteArrayOutputStream out2 = new ByteArrayOutputStream();
JarOutputStream jo = new JarOutputStream(new BufferedOutputStream(out2));
for (Pair<String, InputStream> 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")

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.xtext" version="2">
<resource path="src/org/eclipse/xtext/resource/DerivedStateAwareResourceDescriptionManager.java" type="org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager">
<filter id="338849923">
<message_arguments>
<message_argument value="org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager"/>
</message_arguments>
</filter>
</resource>
</component>

View file

@ -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,

View file

@ -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
}
}

View file

@ -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<OutputConfiguration> getOutputConfigurations(Resource context);
}

View file

@ -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<OutputConfiguration> 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<OutputConfiguration> getOutputConfigurations(Resource context) {
if (delegate instanceof IContextualOutputConfigurationProvider) {
return ((IContextualOutputConfigurationProvider) delegate).getOutputConfigurations(context);
}
return delegate.getOutputConfigurations();
}
}
}

View file

@ -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<OutputConfiguration> getOutputConfigurations(Resource context) {
return getOutputConfigurations();
}
}

View file

@ -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<EObject> 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)

View file

@ -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);

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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)
}

View file

@ -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
* <ul>
* <li>the qualified name of a container of the target element
* <li>the type of that container
* <li>the path from that container to the actual target element
* </ul>
*
* @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
}
}

View file

@ -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<AbstractFileSystemAccess2> 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 }
}
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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<SerializableEObjectDescription> descriptions = emptyList
List<SerializableReferenceDescription> references = emptyList
List<QualifiedName> 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<IEObjectDescription>
}
override getImportedNames() {
importedNames
}
override getReferenceDescriptions() {
references as Iterable<?> as Iterable<IReferenceDescription>
}
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<String,String> 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 extends ENamedElement> 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> 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<String>)
}
def static void writeQualifiedName(ObjectOutput out, QualifiedName name) {
out.writeObject(new ArrayList(name.segments))
}
}

View file

@ -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<URI> sourceLevelURIs
override isAdapterForType(Object type) {
return type == SourceLevelURIsAdapter
}
def static void setSourceLevelUris(ResourceSet resourceSet, Collection<URI> 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
}
}

View file

@ -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()
}
}
}

View file

@ -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)
}
}
}

View file

@ -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<? extends XtextResource> bindXtextResource() {
return StorageAwareResource.class;
}
}

View file

@ -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))
}
}

View file

@ -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..<before.references.size) {
val beforeRef = before.references.get(i)
val afterRef = after.references.get(i)
assertEquals(beforeRef.containerEObjectURI, afterRef.containerEObjectURI)
assertEquals(beforeRef.sourceEObjectUri, afterRef.sourceEObjectUri)
assertEquals(beforeRef.targetEObjectUri, afterRef.targetEObjectUri)
assertEquals(beforeRef.EReference, afterRef.EReference)
assertEquals(beforeRef.indexInList, afterRef.indexInList)
}
assertEquals(before.descriptions.size, after.descriptions.size)
for (int i : 0..<before.descriptions.size) {
val beforeDesc = before.descriptions.get(i)
val afterDesc = after.descriptions.get(i)
assertEquals(beforeDesc.EClass, afterDesc.EClass)
assertEquals(beforeDesc.name, afterDesc.name)
assertEquals(beforeDesc.qualifiedName, afterDesc.qualifiedName)
assertEquals(beforeDesc.userData, afterDesc.userData)
assertEquals(beforeDesc.EObjectURI, afterDesc.EObjectURI)
}
}
}