/*
* 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.GeneratableSModel;
import jetbrains.mps.extapi.model.ModelWithAttributes;
import jetbrains.mps.extapi.model.SModelData;
import jetbrains.mps.logging.Logger;
import jetbrains.mps.persistence.LazyLoadFacility;
import jetbrains.mps.persistence.PersistenceVersionAware;
import jetbrains.mps.smodel.DefaultSModel.InvalidDefaultSModel;
import jetbrains.mps.smodel.loading.ModelLoadResult;
import jetbrains.mps.smodel.loading.ModelLoadingState;
import jetbrains.mps.smodel.persistence.def.ModelReadException;
import org.apache.log4j.LogManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.persistence.DataSource;
import org.jetbrains.mps.openapi.persistence.ModelFactory;
import java.io.IOException;
import java.util.Map;
import java.util.function.BiConsumer;
public class DefaultSModelDescriptor extends LazyEditableSModelBase implements GeneratableSModel, PersistenceVersionAware, ModelWithAttributes {
private static final String MODEL_FOLDER_FOR_GENERATION = "useModelFolderForGeneration";
private static final Logger LOG = Logger.wrap(LogManager.getLogger(DefaultSModelDescriptor.class));
private final LazyLoadFacility myPersistence;
private SModelHeader myHeader;
public DefaultSModelDescriptor(@NotNull LazyLoadFacility persistence, @NotNull SModelHeader header) {
super(header.getModelReference(), persistence.getSource());
myPersistence = persistence;
myHeader = header;
}
public void replace(SModelData modelData) {
assertCanChange();
if (!(modelData instanceof DefaultSModel)) {
throw new IllegalArgumentException();
}
replaceModel((DefaultSModel) modelData, ModelLoadingState.FULLY_LOADED);
// DefaultSModel might come with own SModelHeader, ensure this descriptor references the right one
myHeader = ((DefaultSModel) modelData).getSModelHeader();
}
@Override
protected ModelLoadResult loadSModel(ModelLoadingState state) {
DataSource source = getSource();
if (!source.isReadOnly() && source.getTimestamp() == -1) {
// no file on disk
DefaultSModel model = new DefaultSModel(getReference(), myHeader);
return new ModelLoadResult(model, ModelLoadingState.FULLY_LOADED);
}
ModelLoadResult result;
try {
result = myPersistence.readModel(myHeader, state);
} catch (ModelReadException e) {
LOG.warning(String.format("Failed to load model %s: %s", getSource().getLocation(), e.toString()));
SuspiciousModelHandler.getHandler().handleSuspiciousModel(this, false);
InvalidDefaultSModel newModel = new InvalidDefaultSModel(getReference(), e);
return new ModelLoadResult(newModel, ModelLoadingState.NOT_LOADED);
}
jetbrains.mps.smodel.SModel model = result.getModel();
if (result.getState() == ModelLoadingState.FULLY_LOADED && getRepository() != null) {
boolean needToSave = model.updateExternalReferences(getRepository());
if (needToSave && !source.isReadOnly()) {
setChanged(true);
}
}
LOG.assertLog(model.getReference().equals(getReference()),
"\nError loading model from: \"" + source.getLocation() + "\"\n" +
"expected model UID : \"" + getReference() + "\"\n" +
"but was UID : \"" + model.getReference() + "\"\n" +
"the model will not be available.\n" +
"Make sure that all project's roots and/or the model namespace is correct");
return result;
}
protected boolean shouldCorrectModelRef(){
return false;
}
/**
* Since we expose persistence aspects of a model from (openapi)SModel, it's reasonable to keep
* persistence attributes we are not yet ready to expose here in the implementation. These attributes shall not be part of
* SModelData (smodel.SModel), which is purely about nodes and model structure.
* To me, persistence shall be completely independent aspect, not exposed from SModel, however, at this moment the best we could do is
* to keep persistence within SModel descriptor and hope for the future changes (deprecate and remove methods like getDataSource, getProblems)
* @return actual persistence version number of loaded/created model, or -1 if persistence versioning is not supported
*/
@Override
public int getPersistenceVersion() {
return myHeader.getPersistenceVersion();
}
@Override
public void setPersistenceVersion(int persistenceVersion) {
myHeader.setPersistenceVersion(persistenceVersion);
}
@Nullable
@Override
public ModelFactory getModelFactory() {
return myPersistence.getModelFactory();
}
@Override
protected boolean saveModel() throws IOException {
SModel smodel = getSModel();
if (smodel instanceof InvalidSModel) {
// we do not save stub model to not overwrite the real model
return false;
}
if (getRepository() != null) {
// if we do save model, ensure we write proper references. It's not the best possible solution, see MPS-22440 for details
smodel.updateExternalReferences(getRepository());
}
boolean upgraded = myPersistence.doesSaveUpgradePersistence(myHeader);
myPersistence.saveModel(myHeader, smodel);
return upgraded;
}
@Override
public boolean isGeneratable() {
return !isDoNotGenerate();
}
@Override
public boolean isGenerateIntoModelFolder() {
return Boolean.parseBoolean(getModelHeader().getOptionalProperty(MODEL_FOLDER_FOR_GENERATION));
}
@Override
public void setGenerateIntoModelFolder(boolean value) {
if (value) {
getModelHeader().setOptionalProperty(MODEL_FOLDER_FOR_GENERATION, Boolean.toString(true));
} else {
getModelHeader().removeOptionalProperty(MODEL_FOLDER_FOR_GENERATION);
}
}
@Override
public String getModelHash() {
return myPersistence.getModelHash();
}
@Override
public Map<String, String> getGenerationHashes() {
return myPersistence.getGenerationHashes();
}
@Override
public void setDoNotGenerate(boolean value) {
assertCanChange();
getModelHeader().setDoNotGenerate(value);
setChanged(true);
}
@Override
public boolean isDoNotGenerate() {
return getModelHeader().isDoNotGenerate();
}
@Override
public void setAttribute(@NotNull String key, @Nullable String value) {
getModelHeader().setOptionalProperty(key, value);
}
@Nullable
@Override
public String getAttribute(@NotNull String key) {
return getModelHeader().getOptionalProperty(key);
}
@Override
public void forEach(@NotNull BiConsumer<String, String> action) {
getModelHeader().getOptionalProperties().forEach(action);
}
private SModelHeader getModelHeader() {
return myHeader;
}
@Override
protected void reloadContents() {
try {
myHeader = myPersistence.readHeader();
} catch (ModelReadException e) {
myTimestampTracker.updateTimestamp(getSource());
SuspiciousModelHandler.getHandler().handleSuspiciousModel(this, false);
return;
}
super.reloadContents();
}
public SModelHeader getHeaderCopy() {
return myHeader.createCopy();
}
}