blob: 75da83c648420cd38350b91a8777374718132502 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2013 Borland Software Corporation 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
*
* Contributors:
* Borland Software Corporation - initial API and implementation
* Alan McMorran - bug 316793
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.ast.env;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.UsageCrossReferencer;
import org.eclipse.m2m.internal.qvt.oml.cst.adapters.AbstractGenericAdapter;
import org.eclipse.m2m.internal.qvt.oml.expressions.DirectionKind;
import org.eclipse.m2m.internal.qvt.oml.expressions.ModelParameter;
/**
* @author sboyko
*
*/
public class ModelParameterExtent {
public ModelParameterExtent() {
this((ResourceSet) null);
}
public ModelParameterExtent(ResourceSet rs) {
this(Collections.<EObject>emptyList(), rs, null);
}
public ModelParameterExtent(ModelParameterExtent extentToCopy) {
this(new ArrayList<EObject>(EcoreUtil.copyAll(extentToCopy.getRootObjects())),
extentToCopy.getResourceSet(),
extentToCopy.getModelParameter());
}
public ModelParameterExtent(List<EObject> initialEObjs, ResourceSet rs, ModelParameter modelParameter) {
myResourceSet = rs;
myInitialEObjects = new ArrayList<EObject>(initialEObjs);
myAdditionalEObjects = new ArrayList<EObject>(INITIAL_EXTENT_SIZE);
myModelParameter = modelParameter;
if (isReadonly()) {
for (EObject eObj : myInitialEObjects) {
eObj.eAdapters().add(new ReadonlyExtentAdapter());
}
}
else {
for (EObject eObject : myInitialEObjects) {
getInMemoryResource(true).getContents().add(eObject);
}
}
// Remark:
// As initial objects may have non-null containers, so can be in the middle of an object tree,
// we need to track its original container as in [inout] extents the container my change during
// transformation execution. If the container of an initial object changes, it's not a root object
Map<EObject, EObject> containerMap = null;
for (EObject nextInitialRoot : myInitialEObjects) {
if(nextInitialRoot.eContainer() != null) {
if(containerMap == null) {
containerMap = new HashMap<EObject, EObject>();
}
containerMap.put(nextInitialRoot, nextInitialRoot.eContainer());
}
}
myInitialObj2ContainerMap = (containerMap != null) ? containerMap : Collections.<EObject, EObject>emptyMap();
}
private Resource getInMemoryResource(boolean createOnDemand) {
if (myInMemoryResource == null) {
if (!createOnDemand) {
return null;
}
myInMemoryResource = new ExtentResource();
if (myResourceSet == null) {
for (EObject obj : myInitialEObjects) {
if (obj.eResource() != null && obj.eResource().getResourceSet() != null) {
myResourceSet = obj.eResource().getResourceSet();
break;
}
}
}
if (myResourceSet != null) {
myResourceSet.getResources().add(myInMemoryResource);
}
}
return myInMemoryResource;
}
private ResourceSet getResourceSet() {
return myResourceSet;
}
private ModelParameter getModelParameter() {
return myModelParameter;
}
public static ModelParameter getReadonlyModelParameter(EObject eObj) {
EObject auxParent = eObj;
while (auxParent != null) {
Adapter adapter = EcoreUtil.getAdapter(auxParent.eAdapters(), ReadonlyExtentAdapter.class);
if (adapter != null) {
return ((ReadonlyExtentAdapter) adapter).getModelParameterExtent().myModelParameter;
}
auxParent = auxParent.eContainer();
}
return null;
}
public void addObject(EObject eObject) {
if (eObject != null) {
myAdditionalEObjects.add(eObject);
getInMemoryResource(true).getContents().add(eObject);
if(++myCountAddedAfterPurge >= myPurgeLimitSize) {
purgeContents(myAdditionalEObjects, true);
myCountAddedAfterPurge = 0;
myPurgeLimitSize = Math.max(myAdditionalEObjects.size(), INITIAL_EXTENT_SIZE);
}
}
}
public List<EObject> getInitialObjects() {
return myInitialEObjects;
}
public List<EObject> getRootObjects() {
List<EObject> objects = new ArrayList<EObject>();
for (EObject eObj : myInitialEObjects) {
// Remark: we allow the initial object to have eContainer != null
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=287711
EObject eContainer = eObj.eContainer();
if(eContainer == null || eContainer == myInitialObj2ContainerMap.get(eObj)) {
// if the initial objects retains its original container, IOW it still
// represents a root object in the extent
objects.add(eObj);
}
}
for (EObject eObj : myAdditionalEObjects) {
if (eObj.eContainer() == null) {
objects.add(eObj);
}
}
return objects;
}
public List<Object> getAllObjects() {
purgeContents();
List<Object> objects = new ArrayList<Object>();
getAllObjects(myInitialEObjects, objects);
getAllObjects(myAdditionalEObjects, objects);
return objects;
}
private static void getAllObjects(Collection<EObject> rootObjs, Collection<Object> result) {
for (EObject nextRoot : rootObjs) {
result.add(nextRoot);
TreeIterator<EObject> iterContents = EcoreUtil.getAllProperContents(nextRoot, false);
while (iterContents.hasNext()) {
result.add(iterContents.next());
}
}
}
public ModelExtentContents getContents() {
purgeContents();
Set<EObject> initialObjects = new LinkedHashSet<EObject>(myInitialEObjects);
Set<EObject> allRootObjects = new LinkedHashSet<EObject>(myInitialEObjects);
allRootObjects.addAll(myAdditionalEObjects);
Resource inMemoryResource = getInMemoryResource(false);
if (inMemoryResource != null) {
for (EObject obj : inMemoryResource.getContents()) {
// don't forget about UML stereotype applications which are the root objects
if (obj.eContainer() == null) {
allRootObjects.add(obj);
}
}
}
for (EObject obj : allRootObjects) {
if (((InternalEObject) obj).eDirectResource() instanceof ExtentResource) {
((InternalEObject) obj).eSetResource(null, null);
}
}
if (myInMemoryResource != null && myInMemoryResource.getResourceSet() != null) {
myInMemoryResource.getResourceSet().getResources().remove(myInMemoryResource);
myInMemoryResource = null;
}
return new ExtentContents(new ArrayList<EObject>(initialObjects), new ArrayList<EObject>(allRootObjects));
}
public boolean removeElement(EObject element) {
purgeContents();
delete(getRootObjects(), element);
if(!myInitialEObjects.remove(element)) {
myAdditionalEObjects.remove(element);
myPurgeLimitSize = Math.max(myAdditionalEObjects.size(), INITIAL_EXTENT_SIZE);
}
return true;
}
@Override
public String toString() {
return myInitialEObjects.isEmpty() ? super.toString() : myInitialEObjects.toString();
}
private void dispose() {
if (isReadonly()) {
for (EObject eObj : myInitialEObjects) {
Adapter adapter = EcoreUtil.getAdapter(eObj.eAdapters(), ReadonlyExtentAdapter.class);
if (adapter != null) {
eObj.eAdapters().remove(adapter);
}
}
}
}
public void cleanup() {
dispose();
if (myInMemoryResource != null && myInMemoryResource.getResourceSet() != null) {
myInMemoryResource.getResourceSet().getResources().remove(myInMemoryResource);
myInMemoryResource = null;
}
}
public boolean isReadonly() {
return myModelParameter != null && myModelParameter.getKind() == DirectionKind.IN;
}
private void purgeContents() {
purgeContents(myInitialEObjects, false);
purgeContents(myAdditionalEObjects, true);
}
private void purgeContents(List<EObject> elements, boolean isResetResource) {
ArrayList<EObject> result = null;
for (EObject nextElement : elements) {
EObject eContainer = nextElement.eContainer();
if(eContainer == null ||
(elements == myInitialEObjects && eContainer == myInitialObj2ContainerMap.get(nextElement))) {
if(result == null) {
result = new ArrayList<EObject>(elements.size());
}
result.add(nextElement);
} else {
InternalEObject internElement = (InternalEObject) nextElement;
if (isResetResource && internElement.eDirectResource() instanceof ExtentResource) {
internElement.eSetResource(null, null);
}
}
}
int resultSize = result != null ? result.size() : 0;
if (elements.size() != resultSize) {
elements.clear();
if(result != null) {
elements.addAll(result);
}
}
}
private static void delete(List<EObject> rootEObjects, EObject eObject) {
Set<EObject> eObjects = new HashSet<EObject>();
Set<EObject> crossResourceEObjects = new HashSet<EObject>();
eObjects.add(eObject);
for (@SuppressWarnings("unchecked")
TreeIterator<InternalEObject> j = (TreeIterator<InternalEObject>) (TreeIterator<?>) eObject
.eAllContents(); j.hasNext();) {
InternalEObject childEObject = j.next();
if (childEObject.eDirectResource() != null) {
crossResourceEObjects.add(childEObject);
} else {
eObjects.add(childEObject);
}
}
Map<EObject, Collection<EStructuralFeature.Setting>> usages;
usages = UsageCrossReferencer.findAll(eObjects, rootEObjects);
for (Map.Entry<EObject, Collection<EStructuralFeature.Setting>> entry : usages
.entrySet()) {
EObject deletedEObject = entry.getKey();
Collection<EStructuralFeature.Setting> settings = entry.getValue();
for (EStructuralFeature.Setting setting : settings) {
if (!eObjects.contains(setting.getEObject())
&& setting.getEStructuralFeature().isChangeable()) {
EcoreUtil.remove(setting, deletedEObject);
}
}
}
EcoreUtil.remove(eObject);
for (EObject crossResourceEObject : crossResourceEObjects) {
EcoreUtil.remove(crossResourceEObject.eContainer(),
crossResourceEObject.eContainmentFeature(),
crossResourceEObject);
}
}
private static final int INITIAL_EXTENT_SIZE = 150;
private int myCountAddedAfterPurge = 0;
private int myPurgeLimitSize = INITIAL_EXTENT_SIZE;
private final List<EObject> myInitialEObjects;
private final List<EObject> myAdditionalEObjects;
private final ModelParameter myModelParameter;
private final Map<EObject, EObject> myInitialObj2ContainerMap;
private static class ExtentContents implements ModelExtentContents {
private final List<EObject> myInitialObjects;
private final List<EObject> myRootObjects;
private ExtentContents(List<EObject> initialObjects, List<EObject> allRootObjects) {
this.myInitialObjects = Collections.unmodifiableList(initialObjects);
this.myRootObjects = Collections.unmodifiableList(allRootObjects);
}
public List<EObject> getInitialElements() {
return myInitialObjects;
}
public List<EObject> getAllRootElements() {
return myRootObjects;
}
}
private Resource myInMemoryResource;// = new ExtentResource();
private ResourceSet myResourceSet;
// /*
// * Ensures consistency of associated model extents of the given element
// * and its container.
// * <p>
// * Remark: Intended to be used after assignment to containment reference which
// * may result in move of the element between extents
// *
// * @param element the element to check for model extent consistency with its container
// *
// * @return <code>true</code> if extent move occurred, <code>false</code> otherwise
// */
// static boolean handlePossibleExtentSwitch(EObject element) {
// EObject container = element.eContainer();
// if(container == null) {
// return false;
// }
//
// if(element.eResource() != container.eResource()) {
// InternalEObject internalEObject = (InternalEObject)element;
// if(internalEObject.eDirectResource() != null) {
// internalEObject.eSetResource(null, null);
// } else {
//
// }
// return true;
// }
// return false;
// }
// private void _purgeContents() {
// for (Iterator<EObject> it = myAdditionalEObjects.iterator(); it.hasNext();) {
// EObject nextElement = (EObject) it.next();
// if(nextElement.eResource() != myInMemoryResource) {
// // already unbound from this extent by assignment to a container from another extent
// it.remove();
// }
// }
// }
private static class ExtentResource extends ResourceImpl {
private static int ourExtentId = 0;
ExtentResource() {
setURI(URI.createURI("extent:/" + (++ourExtentId))); //$NON-NLS-1$
setTrackingModification(false);
}
@Override
public boolean eNotificationRequired() {
return false;
}
@Override
public EList<EObject> getContents() {
if (contents == null) {
contents = new ContentsImpl();
}
return contents;
}
private class ContentsImpl extends ContentsEList<EObject> {
private static final long serialVersionUID = 2958909849409879855L;
@Override
protected boolean isUnique() {
// avoiding the check before elements being added is already contained
// we have it under our control, so make sure adding it just once
// when instantiated
return false;
}
}
}
private class ReadonlyExtentAdapter extends AbstractGenericAdapter<ReadonlyExtentAdapter> {
public ReadonlyExtentAdapter() {
}
public ModelParameterExtent getModelParameterExtent() {
return ModelParameterExtent.this;
}
public boolean isAdapterForType(Object type) {
return ReadonlyExtentAdapter.class == type;
}
@Override
public boolean equals(Object obj) {
return obj instanceof ReadonlyExtentAdapter;
}
@Override
public int hashCode() {
return ReadonlyExtentAdapter.class.hashCode();
}
}
}