/*
* Copyright 2003-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.smodel;
import jetbrains.mps.extapi.model.SModelBase;
import jetbrains.mps.extapi.model.SModelData;
import jetbrains.mps.project.dependency.ModelDependenciesManager;
import jetbrains.mps.project.structure.modules.ModuleReference;
import jetbrains.mps.smodel.event.SModelChildEvent;
import jetbrains.mps.smodel.event.SModelDevKitEvent;
import jetbrains.mps.smodel.event.SModelImportEvent;
import jetbrains.mps.smodel.event.SModelLanguageEvent;
import jetbrains.mps.smodel.event.SModelListener;
import jetbrains.mps.smodel.event.SModelPropertyEvent;
import jetbrains.mps.smodel.event.SModelReferenceEvent;
import jetbrains.mps.smodel.event.SModelRootEvent;
import jetbrains.mps.smodel.loading.UpdateModeSupport;
import jetbrains.mps.smodel.nodeidmap.INodeIdToNodeMap;
import jetbrains.mps.smodel.nodeidmap.UniversalOptimizedNodeIdMap;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.model.EditableSModel;
import org.jetbrains.mps.openapi.model.SModelId;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SReference;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
/**
* An internal model implementation which (!) does not implement the {@link org.jetbrains.mps.openapi.model.SModel}.
* Instead the SModel implementations such as {@link}
*/
public class SModel implements SModelData, UpdateModeSupport {
private static final Logger LOG = LogManager.getLogger(SModel.class);
private static AtomicLong ourCounter = new AtomicLong();
static {
resetIdCounter();
}
protected SModelBase myModelDescriptor;
private Set<SNode> myRoots = new LinkedHashSet<SNode>();
private SModelReference myReference;
private boolean myDisposed;
private List<SLanguage> myLanguagesEngagedOnGeneration = new ArrayList<>();
private Map<SLanguage, Integer> myLanguagesIds = new LinkedHashMap<>();
// FIXME introduce UniqueList or ArraySet to avoid "if (!myList.contains && myList.add())" on change
// for both myDevKits and myImports. There's intellij's ArrayListSet, but it's dull and introduces unnecessary dependency
private List<SModuleReference> myDevKits = new ArrayList<>();
private List<ImportElement> myImports = new ArrayList<>();
private INodeIdToNodeMap myIdToNodeMap;
private StackTraceElement[] myDisposedStacktrace = null;
private ModelDependenciesManager myModelDependenciesManager;
private ImplicitImportsLegacyHolder myLegacyImplicitImports;
/**
* update mode, aka full load mode, is the state we are attaching newly loaded children to a model loaded partially
* since it could happen during model read, we can't rely on model read/write action mechanism.
* It used to be a mere boolean flag, but it doesn't help to deal with multiple getNode(SNodeId) calls, one of which triggered model load.
* Existing implementation used to hack around with synchronized access to model data (in overridden methods SModelBase#geSModelInternal(),
* e.g. LazyEditableSModelBase#getSModelInternal synchronize on myModel:UpdatableModel field, and UpdatableModel does synchronize on this when
* replacing model nodes in update mode). I could have used the same approach for my model, however, don't want to
* (a) build my model on top of LazyEditableSModelBase
* (b) keep this synchronized code (future work, though)
*
* Lock is not the best primitive to use here. With given API (setUpdateMode(boolean) I need smth like a semaphore I could check for being locked,
* and wait for release (i.e. without further acquire) if necessary. Neither java's Semaphore, nor Lock are good for this, picked latter just
* by tossing a coin (well, it respects the thread, its API is more streamlined with the activity, lock() sounds better than acquire())
*/
private final ReentrantLock myFullLoadMode = new ReentrantLock();
// nodes from this model communicate with it through this owner instance.
@NotNull
private final AttachedNodeOwner myNodeOwner;
public SModel(@NotNull SModelReference modelReference) {
this(modelReference, new UniversalOptimizedNodeIdMap());
}
public SModel(@NotNull SModelReference modelReference, INodeIdToNodeMap map) {
myReference = modelReference;
myIdToNodeMap = map;
myNodeOwner = new AttachedNodeOwner(this);
}
static void resetIdCounter() {
ourCounter.set(Math.abs(new SecureRandom().nextLong()));
}
public static SNodeId generateUniqueId() {
long id = Math.abs(ourCounter.incrementAndGet());
return new jetbrains.mps.smodel.SNodeId.Regular(id);
}
public SModelId getModelId() {
return myReference.getModelId();
}
@NotNull
public SModelReference getReference() {
return myReference;
}
@Override
public Iterable<org.jetbrains.mps.openapi.model.SNode> getRootNodes() {
fireModelNodesReadAccess();
return new Iterable<org.jetbrains.mps.openapi.model.SNode>() {
@Override
public Iterator<org.jetbrains.mps.openapi.model.SNode> iterator() {
return new Iterator<org.jetbrains.mps.openapi.model.SNode>() {
private final Iterator<SNode> myIterator = myRoots.iterator();
@Override
public boolean hasNext() {
return myIterator.hasNext();
}
@Override
public org.jetbrains.mps.openapi.model.SNode next() {
SNode res = myIterator.next();
if (res != null) {
res.assertCanRead();
res.getNodeOwner().fireNodeRead(res, true);
}
return res;
}
@Override
public void remove() {
throw new UnsupportedOperationException("can't change model roots through roots iterator");
}
};
}
};
}
public boolean isRoot(@Nullable org.jetbrains.mps.openapi.model.SNode node) {
return myRoots.contains(node);
}
//--------------IMPLEMENTATION-------------------
@Override
public void addRootNode(final org.jetbrains.mps.openapi.model.SNode node) {
assert node instanceof SNode;
enforceFullLoad();
if (myRoots.contains(node)) {
// why not warn?
return;
}
org.jetbrains.mps.openapi.model.SModel model = node.getModel();
// FIXME why on earth we remove new root from original location, but don't do the same for insertChild?
if (model != null && model != myModelDescriptor && node.getParent() == null) {
model.removeRootNode(node);
} else {
org.jetbrains.mps.openapi.model.SNode parent = node.getParent();
if (parent != null) {
parent.removeChild(node);
}
}
SNode sn = (SNode) node;
myRoots.add(sn);
sn.attach(myNodeOwner);
myNodeOwner.performUndoableAction(node, new AddRootUndoableAction(node));
myNodeOwner.fireNodeAdd(null, null, sn, null);
}
@Override
public void removeRootNode(final org.jetbrains.mps.openapi.model.SNode node) {
assert node instanceof SNode;
enforceFullLoad();
if (myRoots.contains(node)) {
myNodeOwner.fireBeforeNodeRemove(null, null, (SNode) node, null);
myRoots.remove(node);
SNode sn = (SNode) node;
sn.detach(new DetachedNodeOwner(this));
myNodeOwner.performUndoableAction(node, new RemoveRootUndoableAction(node, myModelDescriptor));
myNodeOwner.fireNodeRemove(null, null, sn, null);
}
}
@Override
@Nullable
public SNode getNode(@NotNull org.jetbrains.mps.openapi.model.SNodeId nodeId) {
SNode res = getNode_(nodeId);
if (res != null) {
res.assertCanRead();
myNodeOwner.fireNodeRead(res, true);
}
return res;
}
protected final void waitUpdateModeIsOver() {
if (isUpdateMode()) {
// there's a thread that updates the model, and myIdToNodeMap might get updated
// thus we shall wait once update is over, and lock()/unlock() pair merely ensures update mode if over.
// This is not a decent solution, we'd rather get separate SModelData implementation that is update-aware, and use locks/guards
// when accessing internal fields.
// Note, if the current thread is the one that performs the update, lock()/unlock() is fine, as the lock is re-enterable
myFullLoadMode.lock();
myFullLoadMode.unlock();
}
}
private SNode getNode_(org.jetbrains.mps.openapi.model.SNodeId nodeId) {
checkNotDisposed();
if (myDisposed) {
return null;
}
waitUpdateModeIsOver();
org.jetbrains.mps.openapi.model.SNode node = myIdToNodeMap.get(nodeId);
if (node != null) {
return ((SNode) node);
}
enforceFullLoad();
return ((SNode) myIdToNodeMap.get(nodeId));
}
@NotNull
public String toString() {
return myReference.toString();
}
//todo get rid of, try to cast, show an error if not casted
public boolean isDisposed() {
return myDisposed;
}
//todo cast if can be
public StackTraceElement[] getDisposedStacktrace() {
return myDisposedStacktrace;
}
@Deprecated
public void addModelListener(@NotNull SModelListener listener) {
getModelDescriptor().addModelListener(listener);
}
@Deprecated
public void removeModelListener(@NotNull SModelListener listener) {
getModelDescriptor().removeModelListener(listener);
}
/**
* FIXME it looks we can use openapi.SModel as return value now (i.e. not to restrain to SModelBase use)
*/
public SModelBase getModelDescriptor() {
return myModelDescriptor;
}
// FIXME (1) synchronized, really? (2) do we really care to have SModelBase here? (3) if yes, why it's not argument type?
public synchronized void setModelDescriptor(org.jetbrains.mps.openapi.model.SModel modelDescriptor) {
if (myModelDescriptor == modelDescriptor) {
// just to guard against accidental second assignment
return;
}
myModelDescriptor = ((SModelBase) modelDescriptor);
myNodeOwner.setEventDispatch(modelDescriptor == null ? null : myModelDescriptor.getNodeEventDispatch());
}
protected void enforceFullLoad() {
org.jetbrains.mps.openapi.model.SModel md = myModelDescriptor;
if (md != null) {
md.load();
}
}
private void fireModelNodesReadAccess() {
if (!canFireReadEvent()) return;
if (myModelDescriptor != null) {
NodeReadEventsCaster.fireModelNodesReadAccess(myModelDescriptor);
}
}
//---------listeners--------
/**
* Name clash with {@link SNodeOwner#performUndoableAction(org.jetbrains.mps.openapi.model.SNode, SNodeUndoableAction)} is unfortunate.
* This one is rather 'registerActionWithUndo'.
*/
protected void performUndoableAction(@NotNull SNodeUndoableAction action) {
if (!canFireEvent()) {
return;
}
UndoHelper.getInstance().addUndoableAction(action);
}
//todo code in the following methods should be written w/o duplication
public boolean canFireEvent() {
return myModelDescriptor != null && jetbrains.mps.util.SNodeOperations.isRegistered(myModelDescriptor) && !isUpdateMode();
}
public boolean canFireReadEvent() {
return canFireEvent();
}
public void dispose() {
if (myDisposed) {
return;
}
myDisposed = true;
myDisposedStacktrace = new Throwable().getStackTrace();
myIdToNodeMap = null;
myRoots.clear();
if (myModelDependenciesManager != null) {
myModelDependenciesManager.dispose();
myModelDependenciesManager = null;
}
}
private void checkNotDisposed() {
if (myDisposed) {
LOG.error(new IllegalModelAccessError("accessing disposed model"));
}
}
private List<SModelListener> getModelListeners() {
if (myModelDescriptor == null) {
return Collections.emptyList();
}
return myModelDescriptor.getModelListeners();
}
private void fireDevKitAddedEvent(@NotNull SModuleReference ref) {
if (!canFireEvent()) {
return;
}
final SModelDevKitEvent event = new SModelDevKitEvent(getModelDescriptor(), ref, true);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.devkitAdded(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
private void fireDevKitRemovedEvent(@NotNull SModuleReference ref) {
if (!canFireEvent()) {
return;
}
final SModelDevKitEvent event = new SModelDevKitEvent(getModelDescriptor(), ref, false);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.devkitRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
private void fireLanguageAddedEvent(@NotNull SLanguage ref) {
if (!canFireEvent()) {
return;
}
final SModelLanguageEvent event = new SModelLanguageEvent(getModelDescriptor(), ref, true);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.languageAdded(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
private void fireLanguageRemovedEvent(@NotNull SLanguage ref) {
if (!canFireEvent()) {
return;
}
final SModelLanguageEvent event = new SModelLanguageEvent(getModelDescriptor(), ref, false);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.languageRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
private void fireImportAddedEvent(@NotNull SModelReference modelReference) {
if (!canFireEvent()) return;
final SModelImportEvent event = new SModelImportEvent(getModelDescriptor(), modelReference, true);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.importAdded(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
private void fireImportRemovedEvent(@NotNull SModelReference modelReference) {
if (!canFireEvent()) return;
final SModelImportEvent event = new SModelImportEvent(getModelDescriptor(), modelReference, false);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.importRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
/*package*/ void fireRootAddedEvent(@NotNull SNode root) {
if (!canFireEvent()) {
return;
}
final SModelRootEvent event = new SModelRootEvent(getModelDescriptor(), root, true);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.rootAdded(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
/*package*/ void fireRootRemovedEvent(@NotNull SNode root) {
if (!canFireEvent()) {
return;
}
final SModelRootEvent event = new SModelRootEvent(getModelDescriptor(), root, false);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.rootRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
/*package*/ void fireBeforeRootRemovedEvent(org.jetbrains.mps.openapi.model.SNode node) {
if (!canFireEvent()) {
return;
}
final SModelRootEvent event = new SModelRootEvent(getModelDescriptor(), node, false);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.beforeRootRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
void firePropertyChangedEvent(@NotNull SNode node, @NotNull SProperty property, @Nullable String oldValue, @Nullable String newValue) {
if (!canFireEvent()) {
return;
}
final SModelPropertyEvent event = new SModelPropertyEvent(getModelDescriptor(), property, node, oldValue, newValue);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.propertyChanged(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
void fireChildAddedEvent(@NotNull SNode parent, @NotNull SContainmentLink role, @NotNull org.jetbrains.mps.openapi.model.SNode child, org.jetbrains.mps.openapi.model.SNode anchor) {
if (!canFireEvent()) {
return;
}
int childIndex = anchor == null ? 0 : parent.getChildren().indexOf(anchor) + 1;
final SModelChildEvent event = new SModelChildEvent(getModelDescriptor(), true, parent, role, childIndex, child);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.childAdded(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
void fireChildRemovedEvent(@NotNull SNode parent, @NotNull SContainmentLink role, @NotNull org.jetbrains.mps.openapi.model.SNode child, org.jetbrains.mps.openapi.model.SNode anchor) {
if (!canFireEvent()) {
return;
}
int childIndex = anchor == null ? 0 : parent.getChildren().indexOf(anchor) + 1;
final SModelChildEvent event = new SModelChildEvent(getModelDescriptor(), false, parent, role, childIndex, child);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.childRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
void fireBeforeChildRemovedEvent(@NotNull SNode parent, @NotNull SContainmentLink role, @NotNull org.jetbrains.mps.openapi.model.SNode child, org.jetbrains.mps.openapi.model.SNode anchor) {
if (!canFireEvent()) {
return;
}
int childIndex = anchor == null ? 0 : parent.getChildren().indexOf(anchor) + 1;
final SModelChildEvent event = new SModelChildEvent(getModelDescriptor(), false, parent, role, childIndex, child);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.beforeChildRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
void fireReferenceAddedEvent(@NotNull SReference reference) {
if (!canFireEvent()) {
return;
}
final SModelReferenceEvent event = new SModelReferenceEvent(getModelDescriptor(), reference, true);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.referenceAdded(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
void fireReferenceRemovedEvent(@NotNull SReference reference) {
if (!canFireEvent()) {
return;
}
final SModelReferenceEvent event = new SModelReferenceEvent(getModelDescriptor(), reference, false);
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.referenceRemoved(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
public FastNodeFinder createFastNodeFinder() {
return new DefaultFastNodeFinder(getModelDescriptor());
}
//---------node registration--------
void registerNode(@NotNull SNode node) {
checkNotDisposed();
if (myDisposed) return;
enforceFullLoad(); // FIXME dubious need to perform full load if all we do is populating id map
org.jetbrains.mps.openapi.model.SNodeId id = node.getNodeId();
if (id == null) {
assignNewId(node);
return;
}
org.jetbrains.mps.openapi.model.SNode existingNode = myIdToNodeMap.get(id);
if (existingNode == null) {
myIdToNodeMap.put(node.getNodeId(), node);
}
if (existingNode != null && existingNode != node) {
assignNewId(node);
}
}
private void assignNewId(SNode node) {
SNodeId id;
id = generateUniqueId();
while (myIdToNodeMap.containsKey(id)) {
resetIdCounter();
id = generateUniqueId();
}
node.setId(id);
myIdToNodeMap.put(id, node);
}
//---------imports manipulation--------
void unregisterNode(@NotNull SNode node) {
checkNotDisposed();
enforceFullLoad(); // FIXME see registerNode. What's the purpose?
org.jetbrains.mps.openapi.model.SNodeId id = node.getNodeId();
if (myDisposed || id == null) return;
myIdToNodeMap.remove(id);
}
public ModelDependenciesManager getModelDepsManager() {
if (myModelDependenciesManager == null) {
myModelDependenciesManager = new ModelDependenciesManager(getModelDescriptor());
// we do not need to track model changes as we are invalidating dep manager right away on any change
SRepository repo = getRepository();
if (repo != null) {
myModelDependenciesManager.trackRepositoryChanges(repo);
}
}
return myModelDependenciesManager;
}
private void invalidateModelDepsManager() {
if (myModelDependenciesManager != null) {
myModelDependenciesManager.invalidate();
}
}
//language
public Collection<SLanguage> usedLanguages() {
return Collections.unmodifiableSet(myLanguagesIds.keySet());
}
public int getLanguageImportVersion(SLanguage lang) {
Integer res = myLanguagesIds.get(lang);
if (res == null) {
LOG.error("Model " + getModelDescriptor().getModelName() + ": version for language " + lang.getQualifiedName() + " not found. Using last version instead.");
return lang.getLanguageVersion();
}
return res;
}
public void deleteLanguage(@NotNull SLanguage id) {
if (myLanguagesIds.remove(id) != null) {
invalidateModelDepsManager();
fireLanguageRemovedEvent(id);
markChanged();
}
}
public void addLanguage(@NotNull SLanguage language) {
// FIXME where to take version value to put into myLanguagesIds if not from deprecated method???
final int version = language.getLanguageVersion();
Integer existingVersion = myLanguagesIds.get(language);
if (existingVersion != null) {
if (version == -1 || existingVersion == version) {
return;
}
if (existingVersion != -1) {
throw new IllegalStateException("Can't add language import with different version. Old version: " + existingVersion + "; new version: " + version + "; model: " + getModelDescriptor().getModelName() + "; language: " + language.getQualifiedName());
}
}
setLanguageVersionInternal(language, version);
}
public void setLanguageImportVersion(SLanguage language, int version) {
if (!myLanguagesIds.containsKey(language)) {
throw new IllegalStateException("Can't change version for non-existing language import. Model: " + getModelDescriptor() + "; language: " + language);
}
setLanguageVersionInternal(language, version);
}
private void setLanguageVersionInternal(SLanguage language, int version) {
myLanguagesIds.put(language, version);
invalidateModelDepsManager();
fireLanguageAddedEvent(language);
markChanged();
}
//devkit
public List<SModuleReference> importedDevkits() {
return Collections.unmodifiableList(myDevKits);
}
public void addDevKit(SModuleReference ref) {
if (!myDevKits.contains(ref) && myDevKits.add(ref)) {
invalidateModelDepsManager();
fireDevKitAddedEvent(ref);
markChanged();
}
}
public void deleteDevKit(@NotNull SModuleReference ref) {
if (myDevKits.remove(ref)) {
invalidateModelDepsManager();
fireDevKitRemovedEvent(ref);
markChanged();
}
}
//model
public List<ImportElement> importedModels() {
return Collections.unmodifiableList(myImports);
}
public void addModelImport(ImportElement importElement) {
// myImports is ArrayList, AL.add() doesn't check for presence.
if (!myImports.contains(importElement) && myImports.add(importElement)) {
fireImportAddedEvent(importElement.getModelReference());
markChanged();
}
}
public void deleteModelImport(ImportElement importElement) {
if (myImports.remove(importElement)) {
if (myLegacyImplicitImports != null) {
// shall keep only if we do track implicit imports
myLegacyImplicitImports.addAdditionalModelVersion(importElement); // to save version and ID if model was imported implicitly
}
fireImportRemovedEvent(importElement.getModelReference());
markChanged();
}
}
/**
* This is compatibility method with legacy persistence mechanism, unless used, no implicit imports are tracked.
* Drop once we no longer need to support serialization of old persistence formats (there's no reason to track
* implicit imports if we aren't going to serialize them afterwards)
*
* It looks that there's no longer consumer of implicit imports. There's code to update them, but no code to read values, except
* for clients of #getAllImportElements()
*/
@NotNull
@ToRemove(version = 3.4)
public ImplicitImportsLegacyHolder getImplicitImportsSupport() {
if (myLegacyImplicitImports == null) {
myLegacyImplicitImports = new ImplicitImportsLegacyHolder(this);
}
return myLegacyImplicitImports;
}
/**
* @deprecated though it's our internal API, there's 1 use in mbeddr of this exact method we need to fix first.
* Once mbeddr use and 2 uses in our model persistence gone, remove the method
*/
@Deprecated
@ToRemove(version = 0)
public List<SModuleReference> engagedOnGenerationLanguages() {
return new SModelLegacy(this).engagedOnGenerationLanguages();
}
private void markChanged() {
if (myModelDescriptor == null) return;
org.jetbrains.mps.openapi.model.SModel model = getModelDescriptor();
if (model instanceof EditableSModel) {
((EditableSModel) model).setChanged(true);
}
}
public Collection<SLanguage> getLanguagesEngagedOnGeneration() {
return myLanguagesEngagedOnGeneration;
}
public void addEngagedOnGenerationLanguage(SLanguage ref) {
if (!myLanguagesEngagedOnGeneration.contains(ref)) {
myLanguagesEngagedOnGeneration.add(ref);
// don't send event but mark model as changed
if (canFireEvent()) {
markChanged();
}
}
}
public void removeEngagedOnGenerationLanguage(SLanguage ref) {
if (myLanguagesEngagedOnGeneration.contains(ref)) {
myLanguagesEngagedOnGeneration.remove(ref);
// don't send event but mark model as changed
if (canFireEvent()) {
markChanged();
}
}
}
//aspects / additional
/**
* update mode means we are attaching newly loaded children
*/
public boolean isUpdateMode() {
return myFullLoadMode.isLocked();
}
@Override
public void enterUpdateMode() {
myFullLoadMode.lock();
}
@Override
public void leaveUpdateMode() {
myFullLoadMode.unlock();
}
//to use only from SNode
protected SRepository getRepository() {
return myModelDescriptor == null ? null : myModelDescriptor.getRepository();
}
//---------refactorings--------
// To allow update of models not yet attached to a repo (e.g. when we read a model and are going to attach it in with
// refreshed state, rather than attach first and then refresh), we pass repository from outside rather than using this.getRepository()
public boolean updateExternalReferences(@NotNull SRepository repository) {
enforceFullLoad();
boolean changed = false;
for (org.jetbrains.mps.openapi.model.SNode node : myIdToNodeMap.values()) {
for (SReference reference : node.getReferences()) {
SModelReference oldReference = reference.getTargetSModelReference();
if (oldReference == null || !(reference instanceof SReferenceBase)) {
continue;
}
// there's RefUpdateUtil.updateModelRef() that could have been used here, it it was in [smodel].
// But it's in [project] now, and needs refactoring to relocate.
final org.jetbrains.mps.openapi.model.SModel resolved = oldReference.resolve(repository);
if (resolved != null && jetbrains.mps.smodel.SModelReference.differs(resolved.getReference(), oldReference)) {
changed = true;
((SReferenceBase) reference).setTargetSModelReference(resolved.getReference());
}
}
}
for (ImportElement e : myImports) {
final org.jetbrains.mps.openapi.model.SModel resolved = e.getModelReference().resolve(repository);
if (resolved != null && jetbrains.mps.smodel.SModelReference.differs(resolved.getReference(), e.getModelReference())) {
changed = true;
e.myModelReference = resolved.getReference();
}
}
if (updateRefs(myDevKits)) {
changed = true;
}
return changed;
}
public void changeModelReference(SModelReference newModelReference) {
enforceFullLoad();
SModelReference oldReference = myReference;
myReference = newModelReference;
for (org.jetbrains.mps.openapi.model.SNode node : myIdToNodeMap.values()) {
for (SReference reference : node.getReferences()) {
if (oldReference.equals(reference.getTargetSModelReference())) {
((jetbrains.mps.smodel.SReference) reference).setTargetSModelReference(newModelReference);
}
}
}
}
private boolean updateRefs(List<SModuleReference> refs) {
boolean changed = false;
for (int i = 0; i < refs.size(); i++) {
SModuleReference ref = refs.get(i);
SModule module = ModuleRepositoryFacade.getInstance().getModule(ref);
if (module != null) {
SModuleReference newRef = module.getModuleReference();
refs.set(i, newRef);
changed = changed || ModuleReference.differs(ref, newRef);
}
}
return changed;
}
public SModel createEmptyCopy() {
return new jetbrains.mps.smodel.SModel(getReference());
}
public void copyPropertiesTo(SModel to) {
if (myLegacyImplicitImports != null) {
for (ImportElement ie : myLegacyImplicitImports.getAdditionalModelVersions()) {
to.getImplicitImportsSupport().addAdditionalModelVersion(ie.copy());
}
}
for (ImportElement ie : importedModels()) {
to.addModelImport(ie.copy());
}
for (SModuleReference mr : importedDevkits()) {
to.addDevKit(mr);
}
for (SLanguage lang : usedLanguages()) {
to.addLanguage(lang);
}
for (SLanguage mr : getLanguagesEngagedOnGeneration()) {
to.addEngagedOnGenerationLanguage(mr);
}
}
public static final class ImportElement {
private SModelReference myModelReference;
private int myReferenceID; // persistence related index
private int myUsedVersion;
@Deprecated
public ImportElement(SModelReference modelReference, int referenceID) {
this(modelReference, referenceID, -1);
}
@Deprecated
public ImportElement(SModelReference modelReference, int referenceID, int usedVersion) {
myModelReference = modelReference;
myReferenceID = referenceID;
myUsedVersion = usedVersion;
}
public ImportElement(SModelReference modelReference) {
myModelReference = modelReference;
myReferenceID = 0;
myUsedVersion = -1;
}
public SModelReference getModelReference() {
return myModelReference;
}
public void setModelReference(SModelReference modelReference) {
myModelReference = modelReference;
}
public int getReferenceID() {
return myReferenceID;
}
public void setReferenceID(int referenceID) {
myReferenceID = referenceID;
}
public int getUsedVersion() {
return myUsedVersion;
}
protected ImportElement copy() {
return new ImportElement(myModelReference, myReferenceID, myUsedVersion);
}
public String toString() {
return "ImportElement(" +
"uid=" + myModelReference + ", " +
"referenceId=" + myReferenceID + ", " +
"usedVersion=" + myUsedVersion + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImportElement that = (ImportElement) o;
if (myReferenceID != that.myReferenceID) return false;
if (myUsedVersion != that.myUsedVersion) return false;
if (myModelReference != null ? !myModelReference.equals(that.myModelReference) : that.myModelReference != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = myModelReference != null ? myModelReference.hashCode() : 0;
result = 31 * result + myReferenceID;
result = 31 * result + myUsedVersion;
return result;
}
}
}