/*
* 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.loading;
import jetbrains.mps.extapi.model.SModelBase;
import jetbrains.mps.extapi.model.SModelData;
import jetbrains.mps.smodel.InvalidSModel;
import jetbrains.mps.smodel.ModelLoadResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/*
* Wrap SModelData with support code to facilitate gradual/partial loading of a model data.
* At the moment deals with two loading states only, INTERFACE and FULL.
*
* The aim of the class
* When we have model write-access, all model changes are made in single thread, so there will not be any threading problems
* The only problem appears when there are no write-actions and at least two concurring reads. In this case, the only thing
* that can change model is loading/replacing.
* This class has an aim to synchronize all loading processes
*/
public final class PartialModelDataSupport<T extends SModelData & UpdateModeSupport> {
private final SModelBase myModelDescriptor;
private final ModelLoader<T> myLoader;
private volatile T myModel = null;
private boolean myLoading = false;
public PartialModelDataSupport(@NotNull SModelBase modelDescriptor, @NotNull ModelLoader<T> loader) {
myModelDescriptor = modelDescriptor;
myLoader = loader;
}
/**
* getModelData is the right name here
* this method loads the model data up to the given state and return
*/
//null in parameter means "give me the current model, don't attempt to load"
//with null parameter, no synch should occur
public T getModel(@Nullable ModelLoadingState state) {
if (state == null) {
return myModel;
}
ModelLoadingState oldState = myModelDescriptor.getLoadingState();
if (state.ordinal() < oldState.ordinal()) {
return myModel;
}
if (myModel instanceof InvalidSModel) {
return myModel;
}
synchronized (this) {
oldState = myModelDescriptor.getLoadingState();
if (state.ordinal() > oldState.ordinal()) {
ensureLoadedTo(state);
}
return myModel;
}
}
private void ensureLoadedTo(final ModelLoadingState state) {
if (myLoading) {
return;
}
myLoading = true; //this is for elimination of infinite recursion
try {
ModelLoadResult<T> res;
switch (state) {
case NOT_LOADED: {
// XXX j.m.s.loading.ModelLoadResult that used to be here didn't tolerate null as an argument. If it never failed, the code is dead?
res = new ModelLoadResult<T>(null, ModelLoadingState.NOT_LOADED);
break;
}
case INTERFACE_LOADED: {
res = myLoader.doLoad(ModelLoadingState.INTERFACE_LOADED);
break;
}
case FULLY_LOADED: {
ModelLoadResult<T> fullModel = myLoader.doLoad(ModelLoadingState.FULLY_LOADED);
if (myModel == null || fullModel.getModelData() == null) {
res = fullModel;
} else {
myModel.enterUpdateMode(); //not to send events on changes
fullModel.getModelData().enterUpdateMode();
new PartialModelUpdateFacility(myModel, fullModel.getModelData(), myModelDescriptor).update();
fullModel.getModelData().leaveUpdateMode();
myModel.leaveUpdateMode(); //enable events
res = new ModelLoadResult<T>(myModel, fullModel.getState());
}
break;
}
default: throw new UnsupportedOperationException();
}
doReplace(res.getModelData(), res.getState());
} finally {
myLoading = false;
}
}
public void replaceWith(T newModel, ModelLoadingState state) {
synchronized (this) {
doReplace(newModel, state);
}
}
private void doReplace(T newModel, ModelLoadingState state) {
myModel = newModel;
myModelDescriptor.setLoadingState(state);
}
public interface ModelLoader<U extends SModelData & UpdateModeSupport> {
/**
* @param state one of {@link ModelLoadingState}, except for {@link ModelLoadingState#NOT_LOADED}
* @return model data loaded to the specified state at least.
*/
@NotNull
jetbrains.mps.smodel.ModelLoadResult<U> doLoad(ModelLoadingState state);
}
}