blob: fa3c04c08437a576db306549922555418be10391 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2018 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.xtext.base.utilities;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.Element;
import org.eclipse.ocl.pivot.ExpressionInOCL;
import org.eclipse.ocl.pivot.internal.resource.ICSI2ASMapping;
import org.eclipse.ocl.pivot.internal.utilities.EnvironmentFactoryInternal;
import org.eclipse.ocl.pivot.resource.ASResource;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.Nameable;
import org.eclipse.ocl.xtext.base.cs2as.CS2AS;
import org.eclipse.ocl.xtext.basecs.ConstraintCS;
import org.eclipse.ocl.xtext.basecs.ElementCS;
import org.eclipse.ocl.xtext.basecs.ModelElementCS;
/**
* The CSI2ASMapping maintains the mapping between CS elements or rather their CSIs
* that remain stable after recreation and the AS elements. This mapping may be used
* repeatedly while editing (CS2AS conversions) to associate changing CS elements with
* stable Pivot elements.
* The mapping is also created during a AS2CS conversion to allow subsequent CS2AS
* conversions to reuse the original AS elements.
*/
public class CSI2ASMapping implements ICSI2ASMapping
{
/**
* Get the CSI2ASMapping owned by the environmentFactory on behalf of CS-aware consumers, or null if none in use.
*/
public static @Nullable CSI2ASMapping basicGetCSI2ASMapping(@NonNull EnvironmentFactoryInternal environmentFactory) {
return (CSI2ASMapping) environmentFactory.getCSI2ASMapping();
}
/**
* Create/reuse the CSI2ASMapping owned by the environmentFactory on behalf of CS-aware consumers.
*/
public static @NonNull CSI2ASMapping getCSI2ASMapping(@NonNull EnvironmentFactoryInternal environmentFactory) {
ICSI2ASMapping csi2asMapping = environmentFactory.getCSI2ASMapping();
if (csi2asMapping == null) {
csi2asMapping = new CSI2ASMapping(environmentFactory);
environmentFactory.setCSI2ASMapping(csi2asMapping);
}
return (CSI2ASMapping) csi2asMapping;
}
/**
* An AbstractCSI defines the private interface for a CSI and the shared support for a hashCode and toString.
*/
private abstract static class AbstractCSI implements CSI
{
protected final int hashCode;
protected AbstractCSI(int hashCode) {
this.hashCode = hashCode;
}
@Override
public int hashCode() {
return hashCode;
}
public @Nullable AbstractCSI isCSIfor(@Nullable CSI thatParent, @Nullable EReference thatChild, @Nullable String thatName, int thatIndex) {
return null;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
toString(s);
return s.toString();
}
protected abstract void toString(@NonNull StringBuilder s);
}
/**
* A RootCSI defines the CSI for the root element in a CSI hierrachy for which the parent is a Resource.
*/
private static class RootCSI extends AbstractCSI
{
private final @NonNull String name; //. Resource.uri
private final int index; // index in Resource.contents
private RootCSI(int hashCode, @NonNull String name, int index) {
super(hashCode);
this.name = name;
this.index = index;
}
@Override
public @Nullable RootCSI isCSIfor(@Nullable CSI thatParent, @Nullable EReference thatChild, @Nullable String thatName, int thatIndex) {
if (thatParent != null) {
return null;
}
if (thatChild != null) {
return null;
}
if (this.index != thatIndex){
return null;
}
return ClassUtil.safeEquals(this.name, thatName) ? this : null;
}
@Override
protected void toString(@NonNull StringBuilder s) {
s.append(name);
s.append("@");
s.append(index);
}
}
/**
* A MultipleChildCSI defines the CSI for the non-root element in a CSI hierarchy. The element may not have siblings.
*/
private static class SingleChildCSI extends AbstractCSI
{
protected final @NonNull AbstractCSI parent;
protected final @NonNull EReference child; // null only at root
protected SingleChildCSI(int hashCode, @NonNull AbstractCSI parent, @NonNull EReference child) {
super(hashCode);
this.parent = parent;
this.child = child;
}
@Override
public @Nullable SingleChildCSI isCSIfor(@Nullable CSI thatParent, @Nullable EReference thatChild, @Nullable String thatName, int thatIndex) {
if (this.parent != thatParent){
return null;
}
if (this.child != thatChild){
return null;
}
return this;
}
@Override
protected void toString(@NonNull StringBuilder s) {
parent.toString(s);
s.append("/");
s.append(child.getName());
}
}
/**
* A MultipleChildCSI defines the CSI for the non-root element in a CSI hierarchy. The element may have siblings which are
* distinguished primarily by name, and then be sequential index of same-named elements.
*/
private static class MultipleChildCSI extends SingleChildCSI
{
protected final @Nullable String name; // null for nameless elements, URI at root
protected final int index; // index of same-named elements in parent container
private MultipleChildCSI(int hashCode, @NonNull AbstractCSI parent, @NonNull EReference child, @Nullable String name, int index) {
super(hashCode, parent, child);
this.name = name;
this.index = index;
}
@Override
public @Nullable MultipleChildCSI isCSIfor(@Nullable CSI thatParent, @Nullable EReference thatChild, @Nullable String thatName, int thatIndex) {
if (super.isCSIfor(thatParent, thatChild, thatName, thatIndex) == null) {
return null;
}
if (this.index != thatIndex){
return null;
}
return ClassUtil.safeEquals(this.name, thatName) ? this : null;
}
@Override
protected void toString(@NonNull StringBuilder s) {
super.toString(s);
if (name != null) {
s.append("@");
s.append(name);
}
s.append("@");
s.append(index);
}
}
/**
* HashedCSIs maintains the known CSIs in an array of entries that may comprise null for no content, a CSI for
* a single content or a List<CSI> for multiple content.
*/
private class HashedCSIs
{
/**
* And mask for the number of bins in hash2csis.
*/
private int hashMask;
/**
* The known CSIs binned into hashSize null/single-CSI/multiple-List<CSI> entries.
*/
private Object[] hash2csis;
public HashedCSIs(int hashSize) {
int mask = 1;
while ((mask < hashSize) && (mask < 0xFFFF)){
mask = mask + mask + 1;
}
this.hashMask = mask;
this.hash2csis = new Object[hashMask+1];
}
private void add(@NonNull AbstractCSI csi) {
WeakReference<@NonNull AbstractCSI> csiRef = new WeakReference<>(csi);
int hashCode = csi.hashCode();
int hashIndex = hashCode & hashMask;
Object entry = hash2csis[hashIndex];
if (entry == null) {
hash2csis[hashIndex] = csiRef;
}
else if (entry instanceof WeakReference<?>) {
List<@NonNull WeakReference<@NonNull AbstractCSI>> csiList = new ArrayList<>();
hash2csis[hashIndex] = csiList;
@SuppressWarnings("unchecked")WeakReference<@NonNull AbstractCSI> castEntry = (WeakReference<@NonNull AbstractCSI>)entry;
csiList.add(castEntry);
csiList.add(csiRef);
}
else {
@SuppressWarnings("unchecked") List<@NonNull WeakReference<@NonNull AbstractCSI>> csiList = (List<@NonNull WeakReference<@NonNull AbstractCSI>>) entry;
csiList.add(csiRef);
}
// FIXME grow() ??
}
private @Nullable AbstractCSI find(int hashCode, @Nullable AbstractCSI thisParent, @Nullable EReference thisChild, @Nullable String thisName, int thisIndex) {
int hashIndex = hashCode & hashMask;
Object entry = hash2csis[hashIndex];
if (entry instanceof List<?>) {
@SuppressWarnings("unchecked") List<@NonNull WeakReference<@NonNull AbstractCSI>> csiList = (List<@NonNull WeakReference<@NonNull AbstractCSI>>) entry;
for (@NonNull WeakReference<@NonNull AbstractCSI> thatRef : csiList) {
AbstractCSI thatCSI = thatRef.get();
if (thatCSI != null) {
AbstractCSI childCSI = thatCSI.isCSIfor(thisParent, thisChild, thisName, thisIndex);
if (childCSI != null) {
return childCSI;
}
}
}
}
else if (entry instanceof WeakReference<?>) {
@SuppressWarnings("unchecked") WeakReference<@NonNull AbstractCSI> thatRef = (WeakReference<@NonNull AbstractCSI>) entry;
AbstractCSI thatCSI = thatRef.get();
if (thatCSI != null) {
AbstractCSI childCSI = thatCSI.isCSIfor(thisParent, thisChild, thisName, thisIndex);
if (childCSI != null) {
return childCSI;
}
}
}
return null;
}
protected @NonNull RootCSI getRootCSI(@NonNull ElementCS csElement) {
assert csElement.getCsi() == null;
assert csElement.eContainer() == null;
Resource eResource = ClassUtil.nonNullState(csElement.eResource());
String uri = eResource.getURI().toString();
int index = eResource.getContents().indexOf(csElement);
int hashCode = 3 * uri.hashCode() + 53 * index;
AbstractCSI csi = find(hashCode, null, null, uri, index);
if (csi == null) {
csi = new RootCSI(hashCode, uri, index);
}
add(csi);
return (RootCSI)csi;
}
protected @NonNull CSI getChildCSI(@NonNull ElementCS csElement) {
assert csElement.getCsi() == null;
EObject eContainer = csElement.eContainer();
assert eContainer instanceof ElementCS;
AbstractCSI thisParent = getCSI((ElementCS)eContainer);
int parentHashCode = thisParent.hashCode();
EReference eContainmentFeature = ClassUtil.nonNullState(csElement.eContainmentFeature());
EReference thisChild = eContainmentFeature;
int childHashCode = eContainmentFeature.hashCode();
String thisName = null;
if (csElement instanceof Nameable) {
thisName = ((Nameable)csElement).getName();
}
AbstractCSI csi;
if (eContainmentFeature.isMany()) {
int thisIndex;
@SuppressWarnings("unchecked") List<Object> siblings = (List<Object>) eContainer.eGet(eContainmentFeature);
if (thisName == null) {
thisIndex = siblings.indexOf(csElement);
}
else {
int count = 0;
for (Object sibling : siblings) {
if (sibling == csElement) {
break;
}
if (sibling instanceof Nameable) {
String thatName = ((Nameable)sibling).getName();
if (ClassUtil.safeEquals(thisName, thatName)) {
count++;
}
}
}
thisIndex = count;
}
int nameHashCode = thisName != null ? thisName.hashCode() : 0;
int hashCode = 3 * parentHashCode + 7 * childHashCode + nameHashCode + thisIndex;
csi = find(hashCode, thisParent, thisChild, thisName, thisIndex);
if (csi == null) {
csi = new MultipleChildCSI(hashCode, thisParent, thisChild, thisName, thisIndex);
}
}
else {
int hashCode = 3 * parentHashCode + 7 * childHashCode;
csi = find(hashCode, thisParent, thisChild, null, 0);
if (csi == null) {
csi = new SingleChildCSI(hashCode, thisParent, thisChild);
}
}
add(csi);
return csi;
}
}
protected final @NonNull EnvironmentFactoryInternal environmentFactory;
/**
* Mapping of each CS resource to its corresponding pivot Resource.
*/
protected final @NonNull Map<@NonNull BaseCSResource, @NonNull ASResource> cs2asResourceMap = new HashMap<>();
protected final @NonNull Map<@NonNull BaseCSResource, @NonNull CS2AS> cs2as2as = new HashMap<>();
/**
* The map from CS element (identified by URI) to pivot element at the end of the last update. This map enables
* the next update from a potentially different CS Resource and elements but the same URIs to re-use the pivot elements
* and to kill off the obsolete elements.
*/
private @NonNull Map<@NonNull CSI, @Nullable Element> csi2as = new HashMap<>();
/**
* A lazily created inverse map that may be required for navigation from an outline.
*/
private @Nullable Map<@NonNull Element, @NonNull ModelElementCS> as2cs = null;
/**
* The known CSIs binned into hashSize sublists.
*/
private @Nullable HashedCSIs hashedCSIs = null;
/**
* Available CS2AS converters.
*/
// private @Nullable List<CS2AS> cs2ases = null;
private CSI2ASMapping(@NonNull EnvironmentFactoryInternal environmentFactory) {
this.environmentFactory = environmentFactory;
}
public void add(@NonNull Map<@NonNull ? extends BaseCSResource, @NonNull ? extends ASResource> cs2asResourceMap) {
as2cs = null;
this.cs2asResourceMap.putAll(cs2asResourceMap);
}
public void add(@NonNull BaseCSResource csResource, @NonNull CS2AS cs2as) {
as2cs = null;
this.cs2asResourceMap.put(csResource, cs2as.getASResource());
this.cs2as2as.put(csResource, cs2as);
// List<CS2AS> cs2ases2 = cs2ases;
// if (cs2ases2 == null) {
// cs2ases = cs2ases2 = new ArrayList<>();
// }
// cs2ases2.add(cs2as);
}
public @NonNull Set<@NonNull CSI> computeCSIs(@NonNull BaseCSResource csResource) {
Set<@NonNull CSI> map = new HashSet<>();
for (Iterator<EObject> it = csResource.getAllContents(); it.hasNext(); ) {
EObject eObject = it.next();
if (eObject instanceof ModelElementCS) {
ModelElementCS csElement = (ModelElementCS)eObject;
CSI csURI = getCSI(csElement);
map.add(csURI);
}
}
return map;
}
protected @NonNull Map<@NonNull Element, @NonNull ModelElementCS> computeAS2CSMap() {
Map<@NonNull Element, @NonNull ModelElementCS> map = new HashMap<>();
for (Resource csResource : cs2asResourceMap.keySet()) {
for (Iterator<EObject> it = csResource.getAllContents(); it.hasNext(); ) {
EObject eObject = it.next();
if (eObject instanceof ModelElementCS) {
ModelElementCS csElement = (ModelElementCS)eObject;
Element pivotElement = csElement.getPivot();
if (pivotElement != null) {
map.put(pivotElement, csElement);
}
// System.out.println(ClassUtil.debugSimpleName(pivotElement) + " => " + ClassUtil.debugSimpleName(csElement));
}
}
}
return map;
}
@Override
public void dispose() {
for (@NonNull BaseCSResource csResource : new ArrayList<>(cs2as2as.keySet())) {
csResource.dispose();
}
csi2as.clear();
as2cs = null;
// List<CS2AS> cs2ases2 = cs2ases;
// if (cs2ases2 != null) {
// cs2ases = null;
// for (CS2AS cs2as : cs2ases2) {
// cs2as.dispose();
// }
// }
}
/**
* Return the Pivot element corresponding to a given CS element.
*/
public @Nullable Element get(@NonNull ModelElementCS csElement) {
CSI csi = getCSI(csElement);
return csi2as.get(csi);
}
/**
* Return the AS Resource corresponding to a given CS Resource.
*/
public @Nullable ASResource getASResource(@Nullable BaseCSResource csResource) {
return cs2asResourceMap.get(csResource);
}
public @Nullable CS2AS getCS2AS(@NonNull BaseCSResource csResource) {
return cs2as2as.get(csResource);
}
/**
* Get the Concrete Syntax Identifier for a CS element. This is a form of URI. It is significantly compacted to
* save on memory.
* @param csResource2aliasMap
*/
private @NonNull AbstractCSI getCSI(@NonNull ElementCS csElement) {
CSI csi = csElement.getCsi();
if (csi == null) {
HashedCSIs hashedCSIs2 = hashedCSIs;
if (hashedCSIs2 == null) {
int size = 1024;
Resource eResource = csElement.eResource();
if (eResource != null) {
int count = 0;
for (TreeIterator<EObject> tit = eResource.getAllContents(); tit.hasNext(); tit.next()) {
count++;
}
size = 4 * count;
}
hashedCSIs = hashedCSIs2 = new HashedCSIs(size);
}
EObject eContainer = csElement.eContainer();
if (eContainer == null) {
csi = hashedCSIs2.getRootCSI(csElement);
}
else { //if (eContainer instanceof ElementCS) {
csi = hashedCSIs2.getChildCSI(csElement);
}
// else {
// csi = hashedCSIs2.getOtherCSI(csElement);
// }
csElement.setCsi(csi);
}
return (AbstractCSI) csi;
}
/**
* Return all mapped CS Resources.
*/
public @NonNull Set<@NonNull BaseCSResource> getCSResources() {
return cs2asResourceMap.keySet();
}
public @Nullable ModelElementCS getCSElement(@NonNull Element pivotElement) {
Map<@NonNull Element, @NonNull ModelElementCS> as2cs2 = as2cs;
if (as2cs2 == null) {
as2cs = as2cs2 = computeAS2CSMap();
}
ModelElementCS modelElementCS = as2cs2.get(pivotElement);
if ((modelElementCS == null) && (pivotElement instanceof ExpressionInOCL)) { // ExpressionInOCL may be created later
EObject eObject = ((ExpressionInOCL)pivotElement).eContainer();
if (eObject instanceof Element) {
modelElementCS = as2cs2.get(eObject);
if (modelElementCS instanceof ConstraintCS) {
modelElementCS = ((ConstraintCS)modelElementCS).getOwnedSpecification();
}
}
}
return modelElementCS;
}
public @NonNull EnvironmentFactoryInternal getEnvironmentFactory() {
return environmentFactory;
}
public @NonNull Map<@NonNull CSI, @Nullable Element> getMapping() {
return csi2as;
}
/**
* Install the Pivot element corresponding to a given CS element.
*/
public void put(@NonNull ModelElementCS csElement, @Nullable Element pivotElement) {
CSI csi = getCSI(csElement);
csi2as.put(csi, pivotElement);
}
/**
* Remove the Resource mappings for all csResources. The CSI mappings persist until update() is called.
*/
public void removeCSResource(@NonNull BaseCSResource csResource) {
as2cs = null;
cs2asResourceMap.remove(csResource);
cs2as2as.remove(csResource);
}
/**
* Update the mapping to cache the Pivot elements with respect to the CSIs for all CS elements in csResources.
*/
public void update() {
as2cs = null;
csi2as.clear();
List<@NonNull BaseCSResource> iterationDomain = new ArrayList<>(cs2asResourceMap.keySet());
int oldSize = cs2asResourceMap.size();
while (true) {
for (@NonNull BaseCSResource csResource : iterationDomain) {
for (Iterator<EObject> it = csResource.getAllContents(); it.hasNext(); ) {
EObject eObject = it.next();
if (eObject instanceof ModelElementCS) {
ModelElementCS csElement = (ModelElementCS)eObject;
Element pivotElement = csElement.getPivot();
put(csElement, pivotElement);
}
}
}
int newSize = cs2asResourceMap.size(); // Bug 538551 - this iterationDomain was observed to grow
if (newSize <= oldSize) {
break;
}
oldSize = newSize;
Set<@NonNull BaseCSResource> newIterationDomain = new HashSet<>(cs2asResourceMap.keySet());
newIterationDomain.removeAll(iterationDomain);
iterationDomain.clear();
iterationDomain.addAll(newIterationDomain);
assert iterationDomain.size() > 0;
assert iterationDomain.size() == (newSize - oldSize);
}
}
}