/* * 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(); } }