eclipse-ocl / org.eclipse.ocl

Eclipse Public License 2.0
0 stars 0 forks source link

[pivot] Add support for the EMF java.util.Map$Entry idiom. #1376

Open eclipse-ocl-bot opened 1 month ago

eclipse-ocl-bot commented 1 month ago

| --- | --- | | Bugzilla Link | 443003 | | Status | NEW | | Importance | P3 normal | | Reported | Sep 01, 2014 06:28 EDT | | Modified | Jul 29, 2022 03:34 EDT | | Depends on | 443195 | | Blocks | 507628 | | See also | 540353, 540884, 471114, 577242 | | Reporter | Ed Willink |

Description

Bug 442744 suggests adding UML2Ecore support so that singly qualified associations exploit the java.util.Map$Entry idiom that uses an indexable EList rather than as a Collection reification.

The workaround for Bug 442744 identifies that the Ecore idiom is only useable in OCL if the specified navigation

aBank[12345678]

is expressed as

aBank.accountNo2persons->any(key = 12345678).value

The latter expression is verbose and inefficient.

Let the OCL support understand that an EClass with an

is the Map idiom allowing access as anEObject[aKey]

(Multi-dimensional keys could be realised either as a lookup cascade, or a Tuple key; the user model could choose the representation and OCL use whichever is defined.)

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Sep 07, 2014 05:06

A Java Map is supported by an Entry that supports independent key/value objects (and behind the scenes provides a linked list of similarly hashed Entry objects) - cost a 4 field object per actual entry.

A UML qualifier (or Ecore eKey) requires the 'key' to be a field in the value object, so the Java Entry objects are unnecessarily bloated.

Bug 443195 provides an enhancement to Ecore to support multi-dimensional eKey-based lookup using a hash table that only incurs support object overheads for hash-collisions.

The OCLinEcore support should therefore target an eKey-based approach.

[As noted in Bug 443195, eKeys are currently mandated to be EAttribute, where EReference would seem to be useful too.]

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Nov 20, 2018 09:52

The recent enhancements to Map support, Bug 540353, Bug 540884, may encourage a user to try to define a Map attribute in OCLinEcore.

Given the difficulties of OCL Collections/Tuples in OCLinEcore, even the naive external DataType implementation is unlikely to work without at least minor bug fixes.

Since Ecore already has its pseudo-Map EMap for XML persistence, it would be good to get the OCL Map and Ecore EMap to co-exist in some way.

A plausible approach is to exploit the current ecore/unboxed/boxed representation interchange to allow EMap as the ecore representation and MapValue as the boxed. Mutating tools such as QVTo, once converted to Pivot OCL, should see a mutable EMap.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Nov 21, 2018 06:29

(In reply to Ed Willink from comment #2)

A plausible approach is to exploit the current ecore/unboxed/boxed

Oops. This probably needs work to allow a Complete OCL / QVTd query to use Map parameters / returns.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 06, 2018 07:37

(In reply to Ed Willink from comment #3)

Oops. This probably needs work to allow a Complete OCL / QVTd query to use Map parameters / returns.

Currently e.g. EString2StringMapEntry is treated as a class so that the ES2AS conversions are blind to the underlying Map capability.

Adding an Ecore2AS.resolveMapType to convert Ecore Map classes to MapType(K,V) is fairly easy and allows compile-time type resolution. But the AS has changed and requires a further AS2ES support to convert MapType(String,String) to the 'correct' reified type. This could perhaps be achieved by a Class.behavioralClass except that we only have DataType.behavioralClass. Alternatively the ES2AS could be left unchanged by conformsTo needs to convert a pseudo-Map class into a true MapType, again requiring Class.behavioralClass.

In principle the Pivot should be a simplified normalized representation, i.e. strip EString2StringMapEntry and use Map(String,String). But ES2AS2ES round-tripping requires that the original usage is not lost. For e.g. EBoolean, we are forced into a typedef-like capability using DataType.behavioralClass. A TypedElement must reference the typedef to support round-tripping.

Promoting DataType.behavioralClass to Class.behavioralClass cannot be done at RC2.

Once there is a Class.behavioralClass, perhaps the sometimes magic conversions will be more obvious. Original (typedef) types always for declarations, resolved behavioral classes always for inter-type evaluations.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 07, 2018 06:09

(In reply to Ed Willink from comment #4)

Promoting DataType.behavioralClass to Class.behavioralClass cannot be done at RC2.

It's much nastier, prompting thinking on general API compatibility, see Bug 471114.

DataType.behavioralClass has an implicit 'DataType' opposite. Once promoted to Class this is a 'Class' opposite which conflicts with the superClass implicit opposite. An explicit opposite must be provided.

Oops, Bug 507628, we need a WFR to detect ambiguous implicit opposites.

Over 500 API filters are required and in some cases we are filtering removal of DataType.behavioralClass. Very dubious.

Since it is the behavioral Class that contributes to the CompleteClass, we could perhaps introduce a new Class.partialClass to redirect to the behavioral class and change DataType.behavioralClass to a deprecated derived name variant.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 07, 2018 06:22

(In reply to Ed Willink from comment #5)\ Since it is the behavioral Class that contributes to the CompleteClass, we

could perhaps introduce a new Class.partialClass

Yuk. Perhaps

Class::resolvedBy : Class[?] <=> Class::resolutionOf : Set(Class[*|1])

context Class:\ inv AyclicResolution: resolvedBy->closure(resolvedBy)->excludes(self)

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 19, 2018 08:01

Eventually after adding a baseline branch as per Bug 471114, a Class.resolvedClass can map the java.util.Map$Entry type to MapType. Tests pass and a new conformance test of a user Map init passes.

Oops. MapType is an IterableType - an aggregate. java.util.Map$Entry is an EMap entry type - not an aggregate. If we stick with the pragmatic equivalence, something is sure to bite downstream; as soon as some other Class has a resolvedClass.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 19, 2018 08:55

The potential ES2AS conversion for Ecore is:

class K2V 'java.util.Map$Entry' { key : K; value : V; }

Set(K2V) => Map(K,V)

But AS2ES will fail to recreate/reuse K2V unless there is a MapType.entryClass to support a permanent Set(EntryType) <=> Map(K,V). If we have an Entry(K,V) it can resolve to the Ecore entry type.

Is the missing Entry(K,V) a design inadequacy, or a kludge needed for Ecore?

If we have a full Set(Entry(K,V)) <=> Map(K,V), we risk an ambiguity wrt Map(K,V) => Set(K) for iteration.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 19, 2018 09:15

(In reply to Ed Willink from comment #8)

If we have a full Set(Entry(K,V)) <=> c

This would be wrong, since Set(Entry(K,V)) may have multiple same-K entries that would collide when converted from Set(Entry(K,V)) to Map(K,V). The EMap is sound, Set(Entry()) is not.

There seems no alternative to a MapType.resolvedEntryClass so that AS2ES to an ES with defective Map representation can fallback on Set('entry').


The promotion of DataType.behavioralClass to Class.resolvedClass was and is unnecessary.

Do we keep the Class.resolvedClass flexibility?

Do we roll back as DataType.resolvedClass to keep the cleanup?

Do we roll back as DataType.behavioralClass to avoid all API breakage?

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 29, 2018 04:36

(In reply to Ed Willink from comment #7)

Oops. MapType is an IterableType - an aggregate. java.util.Map$Entry is an EMap entry type - not an aggregate. If we stick with the pragmatic equivalence, something is sure to bite downstream ...

Solution, we just add MapType.entryClass so that AS2ES can reconstruct the idiomatic K2VMapEntry EClass. Simple addition => no significant API evolution.

But, if in OCLinEcore we use Map(K,V) there is no mention of the K2VMapEntry so the AS2CS conversion is lossy. Therefore OCLinEcore must use OrderedSet(K2VMapEntry) or K2VMapEntry[*|1]{unique, ordered}. (Yes the Ecore idiom is ordered.) If in OCLinEcore the usage is Map(K,V) then a synthetic K2VMapEntry must appear in Ecore, and this synthetic EClass will become non-synthetic after an ES2AS. It is tempting to make the synthetic EClass abstract/interface/serialisable to distinguish, but this may well interfere with XMI serialization where the entry class is at least semi-reified. Probably need to resort to an EAnnotation on the entry EClass.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 29, 2018 05:05

Ouch! Currently any OCL written against e.g. EAnnotation.details expects to iterate over the EString2StringMapEntry class. If in the pivot, EAnnotation.details changes from an OrderedSet(EString2StringMapEntry) to a Map(String,String) the iterator type changes. Incompatible.

Options:

a) abandon EMap => Map altogether - disappointing waste of effort\ b) accept breakage - unnacceptable\ c) rewrite old collection access to use entries\ e.g. eAnnotation.details->select(...)\ => eAnnotation.details->entries()->select(...)\ d) rewrite old iterator accesses as key iterator + value co-iterator\ e.g. eAnnotation.details->select(e | e.key...e.value...)\ => eAnnotation.details->select(k <- v | k...v...)\ e) rewrite new as old - hardly a step forwards\ e.g. eAnnotation.details->select(k <- v | k...v...)\ => eAnnotation.details->entries()->select(e | let k = e.value, v = e.value in k...v...)\ f) abandon EMap => Map for unannotated Ecore, allow for annotated Ecore

c) is perhaps easiest but inefficient; it requires a new Map::entries() method and a synthetic entry type.\ d) is unpleasant since it is a deep rewrite that conflicts with simple parsing

Either way we have to analyze aMap->... and if aMap has an entryClass, rewrite ... accordingly. A (deep) analysis of the iterator body to choose one of two iterator types may be challenging chicken/egg-wise. Worse an analysis of the body to choose Map::select/Collection::select.

f) is close to a waste of time since how many users will ever find it?

Bottom line, Ecore has an idiomatic way of declaring maps that made naive tooling interpret them as collections. We have to offer the new way cleanly and the old way as a compatibility.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 29, 2018 05:26

(In reply to Ed Willink from comment #11)

We have to offer the new way cleanly and the old way as a compatibility.

Potentially no old users, no new users. Certainly no use of EAnnotation.details in OCL within Eclipse OCL.

Ecore is idiomatic/broken. Statically an EMF collection of Map$Entry instances really is a collection not a map; it is just that EMF uses an EMap to give it a dynamic map functionality. It is an OCL bug that OCL evaluation as an OrderedSet behaves differently to EMap evaluation. Bug fixes are allowed to break compatibility. No need to rewrite old accesses.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Dec 30, 2018 10:50

In order to exploit Ecore EMap serialization we can only have exactly unit multiplicity Maps. We therefore neeed a WFR to alert users to the limitation.

eclipse-ocl-bot commented 1 month ago

By Ed Willink on Jan 14, 2019 13:27

Useful EMap functionality pushed to master for M1.

WFR still to do.