Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » EMF » BinaryResource lost unsettable empty values
BinaryResource lost unsettable empty values [message #929161] Mon, 01 October 2012 09:10 Go to next message
Guillaume P. is currently offline Guillaume P.Friend
Messages: 69
Registered: June 2010
Location: Toulouse, France
Member
Hi,

I recently changed my XML resources to Binary resources for some performances reasons. But I just realized that now the isSet state seems to be lost (if the attribute/reference has no value, it is no more persisted, even if it is set).
It seems that the saveEObjects method of the BinaryResourceImpl class has simply iterate through values without saving anything if there is no value.

My software use the set or unset state of an attribute/reference, and needs it to be persisted. Is there a known workaround (I don't really understand the way binary resource serialize data, and so I have no idea how I could customize it to add the set-but-empty information) ?

Regards.
Re: BinaryResource lost unsettable empty values [message #930190 is a reply to message #929161] Tue, 02 October 2012 05:04 Go to previous messageGo to next message
Ed Merks is currently offline Ed MerksFriend
Messages: 33140
Registered: July 2009
Senior Member
Guillaume,

Looking at the code, I'd not expect that to be the case. When we save,
if eIsSet is true, we definitely save a value

protected void saveFeatureValue(InternalEObject internalEObject,
int featureID, EStructuralFeatureData eStructuralFeatureData) throws
IOException
{
if (internalEObject.eIsSet(featureID))
{
writeCompressedInt(featureID + 1);
if (eStructuralFeatureData.name != null)
{
writeString(eStructuralFeatureData.name);
eStructuralFeatureData.name = null;
}
Object value = internalEObject.eGet(featureID, false, true);

and when we load we process that information. Maybe there is a case I
messed. I suggest you open a bugzilla with a simple test case;
preferably a zipped project with a failing unit test I can easily run
locally. I'll have a look and fix and problems that turns up; probably
I can fix it (assuming something is broken) in the maintenance stream...


On 01/10/2012 11:10 AM, Guillaume P. wrote:
> Hi,
>
> I recently changed my XML resources to Binary resources for some
> performances reasons. But I just realized that now the isSet state
> seems to be lost (if the attribute/reference has no value, it is no
> more persisted, even if it is set).
> It seems that the saveEObjects method of the BinaryResourceImpl class
> has simply iterate through values without saving anything if there is
> no value.
>
> My software use the set or unset state of an attribute/reference, and
> needs it to be persisted. Is there a known workaround (I don't really
> understand the way binary resource serialize data, and so I have no
> idea how I could customize it to add the set-but-empty information) ?
>
> Regards.


Ed Merks
Professional Support: https://www.macromodeling.com/
Re: BinaryResource lost unsettable empty values [message #938165 is a reply to message #930190] Tue, 09 October 2012 16:09 Go to previous messageGo to next message
Guillaume P. is currently offline Guillaume P.Friend
Messages: 69
Registered: June 2010
Location: Toulouse, France
Member
Ok, I found the problem/solution few hours after and I share it with you:

The problem is in the load process of binary resources: when reading a many feature, the EObjectInputStream reads the list of contained/referenced objects with method loadEObjects(...) and set the list content through the BasicEList.setData(...) method. But this method don't set the feature linked to the BasicEList as set if the content to set is empty... The result is that the empty many references are never loaded as set if they are empty.

Here is the workaround I put in place (which is working):

/**
 * A binary resource with the doLoad method is overridden for fixing a native bug with
 * set/no-set state.
 */
public class FixedBinaryResourceImpl extends XMIResourceImpl {
	/**
	 * Create a new RangeDB binary resource.
	 */
	public RangeDBBinaryResourceImpl() {
		super();
	}
	
	/**
	 * Create a new RangeDB binary resource with the given URI.
	 * @param uri resource URI
	 */
	public RangeDBBinaryResourceImpl(URI uri) {
		super(uri);
	}
	
	@Override
	protected void initResource() {
		super.initResource();
		
		getDefaultLoadOptions().put(XMLResource.OPTION_BINARY, true);
		getDefaultLoadOptions().put(XMLResource.OPTION_ZIP, true);
		
		getDefaultSaveOptions().put(XMLResource.OPTION_BINARY, true);
		getDefaultSaveOptions().put(XMLResource.OPTION_ZIP, true);
	}
	
	/**
	 * Code copied from {@link XMLResourceImpl} in order to support set/not-set
	 * state.
	 * @param inputStream stream to read
	 * @param options load options
	 * @throws IOException exception raised if a load problem occurs
	 */
	@Override
	public void doLoad(InputStream inputStream, Map<?, ?> options) throws IOException {
		if (inputStream instanceof URIConverter.Loadable) {
			((URIConverter.Loadable) inputStream).loadResource(this);
		} else if (options != null && Boolean.TRUE.equals(options.get(OPTION_BINARY))) {
			if (!(inputStream instanceof BufferedInputStream)) {
				int bufferCapacity = BinaryResourceImpl.getBufferCapacity(options);
				if (bufferCapacity > 0) {
					inputStream = new BufferedInputStream(inputStream, bufferCapacity);
				}
			}
			
			// FIX HERE: use the fixed EObjectInputStream class
			EObjectInputStream eObjectInputStream = new FixedEObjectInputStream(inputStream, options);
			ResourceHandler handler = (ResourceHandler) options.get(OPTION_RESOURCE_HANDLER);
			if (handler != null) {
				handler.preLoad(this, inputStream, options);
			}
			eObjectInputStream.loadResource(this);
			
			// Load the extrinsic ID map.
			//
			for (int i = 0, size = eObjectInputStream.readCompressedInt(); i < size; ++i) {
				setID(eObjectInputStream.loadEObject(), eObjectInputStream.readString());
			}
			
			if (handler != null) {
				handler.postLoad(this, inputStream, options);
			}
		} else {
			super.doLoad(inputStream, options);
		}
	}
	
	
	/**
	 * The default EObjectInputStream class has a problem which transform empty
	 * but set lists to empty not-set lists. To keep set or not-set state, this
	 * class has to be used. It has exactly the same behavior as its parent,
	 * excepted that this problem is fixed.
	 * 
	 * @author pelouas
	 */
	protected class FixedEObjectInputStream extends EObjectInputStream {
		/**
		 * Create a new EObjectInputStream.
		 * @param inputStream stream to read
		 * @param options load options
		 * @throws IOException exception if any problem occurs
		 */
		public FixedEObjectInputStream(InputStream inputStream, Map<?, ?> options) throws IOException {
			super(inputStream, options);
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl.EObjectInputStream
		 * #loadEObjects(org.eclipse.emf.ecore.util.InternalEList)
		 */
		@Override
		public void loadEObjects(InternalEList<InternalEObject> internalEObjects) throws IOException {
			super.loadEObjects(internalEObjects);
			
			// FIX HERE: ensure list is set even if empty
			if (internalEObjects.isEmpty()) {
				internalEObjects.clear();
			}
		}
	}
}


The concrete fix is in the method loadEObjects(...) where I ensure to set the list even if it is empty, but I have to start the code override from XMIResourceImpl to be able to override this method...
Re: BinaryResource lost unsettable empty values [message #938739 is a reply to message #938165] Wed, 10 October 2012 05:35 Go to previous message
Ed Merks is currently offline Ed MerksFriend
Messages: 33140
Registered: July 2009
Senior Member
Guillaume,

Comments below.

On 09/10/2012 6:09 PM, Guillaume P. wrote:
> Ok, I found the problem/solution few hours after and I share it with you:
>
> The problem is in the load process of binary resources: when reading a many feature, the EObjectInputStream reads the list of contained/referenced objects with method loadEObjects(...) and set the list content through the BasicEList.setData(...) method.
Well, the setData is called on the internal list used to pass to a call
to addAll on the feature's list:

InternalEList<InternalEObject> internalEObjects =
(InternalEList<InternalEObject>)(InternalEList<?>)resource.getContents();
internalEObjects.addAllUnique(internalEObjectList);

So the problem is indeed very close to how you've characterized it.
I.e., addAllUnique is a no-op when there's nothing to add so there's no
call-back to "didChange" and so the resulting multi-valued feature
doesn't not become set.

So the fix is to find all the places addAllUnique is called, check the
argument we're passing in is the empty list, and call clear in that
case, rather than addAllUnique; your fix doesn't address multi-valued
attributes.

Please open a bugzilla and I'll fix it in the maintenance stream (2.8.2)
and in 2.9.0.

Thanks for the careful investigation. It's much appreciated!!
> But this method don't set the feature linked to the BasicEList as set if the content to set is empty... The result is that the empty many references are never loaded as set if they are empty.
>
> Here is the workaround I put in place (which is working):
>
>
> /**
> * A binary resource with the doLoad method is overridden for fixing a native bug with
> * set/no-set state.
> */
> public class FixedBinaryResourceImpl extends XMIResourceImpl {
> /**
> * Create a new RangeDB binary resource.
> */
> public RangeDBBinaryResourceImpl() {
> super();
> }
>
> /**
> * Create a new RangeDB binary resource with the given URI.
> * @param uri resource URI
> */
> public RangeDBBinaryResourceImpl(URI uri) {
> super(uri);
> }
>
> @Override
> protected void initResource() {
> super.initResource();
>
> getDefaultLoadOptions().put(XMLResource.OPTION_BINARY, true);
> getDefaultLoadOptions().put(XMLResource.OPTION_ZIP, true);
>
> getDefaultSaveOptions().put(XMLResource.OPTION_BINARY, true);
> getDefaultSaveOptions().put(XMLResource.OPTION_ZIP, true);
> }
>
> /**
> * Code copied from {@link XMLResourceImpl} in order to support set/not-set
> * state.
> * @param inputStream stream to read
> * @param options load options
> * @throws IOException exception raised if a load problem occurs
> */
> @Override
> public void doLoad(InputStream inputStream, Map<?, ?> options) throws IOException {
> if (inputStream instanceof URIConverter.Loadable) {
> ((URIConverter.Loadable) inputStream).loadResource(this);
> } else if (options != null && Boolean.TRUE.equals(options.get(OPTION_BINARY))) {
> if (!(inputStream instanceof BufferedInputStream)) {
> int bufferCapacity = BinaryResourceImpl.getBufferCapacity(options);
> if (bufferCapacity > 0) {
> inputStream = new BufferedInputStream(inputStream, bufferCapacity);
> }
> }
>
> // FIX HERE: use the fixed EObjectInputStream class
> EObjectInputStream eObjectInputStream = new FixedEObjectInputStream(inputStream, options);
> ResourceHandler handler = (ResourceHandler) options.get(OPTION_RESOURCE_HANDLER);
> if (handler != null) {
> handler.preLoad(this, inputStream, options);
> }
> eObjectInputStream.loadResource(this);
>
> // Load the extrinsic ID map.
> //
> for (int i = 0, size = eObjectInputStream.readCompressedInt(); i < size; ++i) {
> setID(eObjectInputStream.loadEObject(), eObjectInputStream.readString());
> }
>
> if (handler != null) {
> handler.postLoad(this, inputStream, options);
> }
> } else {
> super.doLoad(inputStream, options);
> }
> }
>
>
> /**
> * The default EObjectInputStream class has a problem which transform empty
> * but set lists to empty not-set lists. To keep set or not-set state, this
> * class has to be used. It has exactly the same behavior as its parent,
> * excepted that this problem is fixed.
> *
> * @author pelouas
> */
> protected class FixedEObjectInputStream extends EObjectInputStream {
> /**
> * Create a new EObjectInputStream.
> * @param inputStream stream to read
> * @param options load options
> * @throws IOException exception if any problem occurs
> */
> public FixedEObjectInputStream(InputStream inputStream, Map<?, ?> options) throws IOException {
> super(inputStream, options);
> }
>
> /*
> * (non-Javadoc)
> *
> * @see
> * org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl.EObjectInputStream
> * #loadEObjects(org.eclipse.emf.ecore.util.InternalEList)
> */
> @Override
> public void loadEObjects(InternalEList<InternalEObject> internalEObjects) throws IOException {
> super.loadEObjects(internalEObjects);
>
> // FIX HERE: ensure list is set even if empty
> if (internalEObjects.isEmpty()) {
> internalEObjects.clear();
> }
> }
> }
> }
>
>
> The concrete fix is in the method loadEObjects(...) where I ensure to set the list even if it is empty, but I have to start the code override from XMIResourceImpl to be able to override this method...


Ed Merks
Professional Support: https://www.macromodeling.com/
Previous Topic:[net4j] Configuring the HTTP Acceptor
Next Topic:[Teneo] Adding EPackages Dynamically to DataStore
Goto Forum:
  


Current Time: Thu Apr 25 01:53:57 GMT 2024

Powered by FUDForum. Page generated in 0.03426 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top