/*
* 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.extapi.model;
import jetbrains.mps.project.dependency.ModelDependenciesManager;
import jetbrains.mps.smodel.FastNodeFinder;
import jetbrains.mps.smodel.Language;
import jetbrains.mps.smodel.SModel.ImportElement;
import jetbrains.mps.smodel.SModelInternal;
import jetbrains.mps.smodel.SModelLegacy;
import jetbrains.mps.smodel.event.SModelFileChangedEvent;
import jetbrains.mps.smodel.event.SModelListener;
import jetbrains.mps.smodel.event.SModelListener.SModelListenerPriority;
import jetbrains.mps.smodel.event.SModelRenamedEvent;
import jetbrains.mps.smodel.loading.ModelLoadingState;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* IMPORTANT - DO NOT SUBCLASS THIS ONE DIRECTLY, USE {@link SModelBase} INSTEAD.
*
* Stub for model implementations with data kept separately in a SModel/SModelData (as of now/planned).
* Unlike {@link SModelBase}, which is true root of model descriptor hierarchy, this class keeps
* transition stuff like legacy SModelListeners and SModelInternal methods, our explicit though untold dependencies from smodel.SModel.
* Perhaps, one day we can get rid of if altogether.
*
* TODO move listeners to openapi
*/
public abstract class SModelDescriptorStub implements SModelInternal, SModel, FastNodeFinder.Factory {
private static final Logger LOG = LogManager.getLogger(SModelDescriptorStub.class);
private final List<SModelListener> myModelListeners = new CopyOnWriteArrayList<SModelListener>();
/**
* Migration to 3.0. Loads and returns model data.
*
* FIXME Replace uses of this method with getSModel(), make it abstract and implement in SModelBase subclasses.
* The name getSModelInternal is misleading as it clashes with SModelInternal interface this class implements.
* Though getSModel is not much better, at least in the context of SModelDescriptor it makes more sense.
*
* @deprecated use {@link SModelBase#getModelData()} or {@link #getSModel()}
* FIXME there's implicit convention that smodel.SModel has this openapi.SModel (aka descriptor) assigned once
* this method returns
*/
@Deprecated
public abstract jetbrains.mps.smodel.SModel getSModelInternal();
@Override
public void addModelListener(@NotNull SModelListener listener) {
if (listener.getPriority() == SModelListenerPriority.PLATFORM) {
myModelListeners.add(0, listener);
} else {
myModelListeners.add(listener);
}
}
@Override
public void removeModelListener(@NotNull SModelListener listener) {
myModelListeners.remove(listener);
}
@NotNull
public List<SModelListener> getModelListeners() {
return myModelListeners;
}
protected void clearListeners() {
myModelListeners.clear();
}
// Not SModel-specific listener notifications
protected void fireBeforeModelFileChanged(SModelFileChangedEvent event) {
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.beforeModelFileChanged(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
protected void fireModelFileChanged(SModelFileChangedEvent event) {
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.modelFileChanged(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
@Deprecated
protected void fireBeforeModelRenamed(SModelRenamedEvent event) {
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.beforeModelRenamed(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
@Deprecated
protected void fireModelRenamed(SModelRenamedEvent event) {
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.modelRenamed(event);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
/**
* @deprecated (a) we are in process to get rid of SModelListener; (b) this method used to change loading state in addition
* to event dispatch, and if you used to invoke it, please re-consider that code.
*/
@Deprecated
protected void fireModelStateChanged(ModelLoadingState newState) {
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.modelLoadingStateChanged(this, newState);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
protected void fireBeforeModelDisposed(SModel model) {
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.beforeModelDisposed(model);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
protected void fireModelSaved() {
for (SModelListener sModelListener : getModelListeners()) {
try {
sModelListener.modelSaved(this);
} catch (Throwable t) {
LOG.error(null, t);
}
}
}
/**
* Use {@link SModelBase#getModelData()} wherever possible.
* Use this method when accessing implementation aspects of smodel.SModel that are not exposed
* through SModelInternal interface (for latter, use {@link #getSModelInternal()} until ceased).
*/
public jetbrains.mps.smodel.SModel getSModel() {
return getSModelInternal();
}
public final ModelDependenciesManager getModelDepsManager() {
return getSModel().getModelDepsManager();
}
@Override
public java.util.Collection<SLanguage> importedLanguageIds() {
assertCanRead();
return getSModel().usedLanguages();
}
@Override
public void deleteLanguageId(@NotNull SLanguage ref) {
assertCanChange();
getSModel().deleteLanguage(ref);
}
@Override
public void addLanguage(Language language) {
assertCanChange();
new SModelLegacy(getSModel()).addLanguage(language);
}
@Override
public void addLanguage(@NotNull SLanguage language) {
assertCanChange();
getSModel().addLanguage(language);
}
@Override
public void setLanguageImportVersion(@NotNull SLanguage language, int version) {
assertCanChange();
getSModel().setLanguageImportVersion(language, version);
}
@Override
public int getLanguageImportVersion(SLanguage lang) {
assertCanRead();
return getSModel().getLanguageImportVersion(lang);
}
@Override
public List<SModuleReference> importedDevkits() {
assertCanRead();
return getSModel().importedDevkits();
}
@Override
public final void addDevKit(SModuleReference ref) {
assertCanChange();
getSModel().addDevKit(ref);
}
@Override
public final void deleteDevKit(@NotNull SModuleReference ref) {
assertCanChange();
getSModel().deleteDevKit(ref);
}
@NotNull
@Override
public Collection<SModelReference> getModelImports() {
assertCanRead();
ArrayList<SModelReference> rv = new ArrayList<SModelReference>();
for (ImportElement ie : getSModel().importedModels()) {
rv.add(ie.getModelReference());
}
return rv;
}
@Override
@Deprecated
public final void addModelImport(SModelReference modelReference, boolean firstVersion) {
assertCanChange();
new SModelLegacy(getSModel()).addModelImport(modelReference, firstVersion);
}
@Override
public final void addModelImport(@NotNull SModelReference ref) {
assertCanChange();
getSModel().addModelImport(new ImportElement(ref));
}
@Override
public final void deleteModelImport(SModelReference modelReference) {
assertCanChange();
final jetbrains.mps.smodel.SModel modelData = getSModel();
for (ImportElement importElement : new ArrayList<>(modelData.importedModels())) {
if (importElement.getModelReference().equals(modelReference)) {
modelData.deleteModelImport(importElement);
}
}
}
@Override
@Deprecated
public final List<SModuleReference> engagedOnGenerationLanguages() {
assertCanRead();
return new SModelLegacy(getSModel()).engagedOnGenerationLanguages();
}
@NotNull
@Override
public Collection<SLanguage> getLanguagesEngagedOnGeneration() {
assertCanRead();
return getSModel().getLanguagesEngagedOnGeneration();
}
@Override
@Deprecated
public final void addEngagedOnGenerationLanguage(SModuleReference ref) {
assertCanChange();
new SModelLegacy(getSModel()).addEngagedOnGenerationLanguage(ref);
}
@Override
public final void addEngagedOnGenerationLanguage(SLanguage lang) {
assertCanChange();
getSModel().addEngagedOnGenerationLanguage(lang);
}
@Override
@Deprecated
public final void removeEngagedOnGenerationLanguage(SModuleReference ref) {
assertCanChange();
new SModelLegacy(getSModel()).removeEngagedOnGenerationLanguage(ref);
}
@Override
public final void removeEngagedOnGenerationLanguage(SLanguage lang) {
assertCanChange();
getSModel().removeEngagedOnGenerationLanguage(lang);
}
/**
* Invoked to check if it's legal to read from the model.
* By default, is no-op, subclasses shall override to enforce proper policy
*/
protected void assertCanRead() {
// intentionally no-op
}
/**
* Invoked to check if it's legal to modify the model.
* By default, is no-op, subclasses shall override to enforce proper policy
*/
protected void assertCanChange() {
// intentionally no-op
}
@Override
public boolean isDisposed() {
return getDisposedStacktrace() != null;
}
@Override
public final StackTraceElement[] getDisposedStacktrace() {
return getSModel().getDisposedStacktrace();
}
@Override
public FastNodeFinder createNodeFinder(@NotNull SModel model) {
assert model == this;
return getSModel().createFastNodeFinder();
}
@Override
public final boolean updateExternalReferences(@NotNull SRepository repo) {
assertCanChange();
return getSModel().updateExternalReferences(getRepository());
}
@Override
public void changeModelReference(SModelReference newModelReference) {
assertCanChange();
getSModel().changeModelReference(newModelReference);
}
}