/* * Copyright 2003-2016 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.extapi.persistence.FileDataSource; import jetbrains.mps.extapi.persistence.FileWithBackupDataSource; import jetbrains.mps.smodel.InvalidSModel; import jetbrains.mps.smodel.SModel; import jetbrains.mps.smodel.loading.ModelLoadingState; import jetbrains.mps.util.IterableUtil; import jetbrains.mps.vfs.IFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModel.Problem.Kind; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.persistence.DataSource; import org.jetbrains.mps.openapi.persistence.ModelSaveException; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; import org.jetbrains.mps.openapi.persistence.StreamDataSource; import java.io.IOException; import java.util.Collections; import java.util.Iterator; /** * */ public final class CustomPersistenceSModel extends EditableSModelBase implements SingleRootSModel { @NotNull private final SModelPersistence myPersistence; private volatile SModel myModel = null; private Iterable<Problem> myProblems = Collections.emptySet(); public CustomPersistenceSModel(@NotNull SModelReference modelReference, @NotNull StreamDataSource source, @NotNull SModelPersistence persistence) { super(modelReference, source instanceof FileDataSource ? FileWithBackupDataSource.create((FileDataSource) source) : source); myPersistence = persistence; } @NotNull @Override public StreamDataSource getSource() { return (StreamDataSource) super.getSource(); } @Override protected SModel getCurrentModelInternal() { return myModel; } @Override public SModel getSModelInternal() { if (myModel == null) { final ModelLoadingState oldState; synchronized (this) { oldState = getLoadingState(); if (myModel == null) { myModel = loadSModel(); myModel.setModelDescriptor(this); setLoadingState(ModelLoadingState.FULLY_LOADED); } } fireModelStateChanged(oldState, getLoadingState()); } return myModel; } private IFile getBackupFile(boolean ifExists) { StreamDataSource source = getSource(); if (source instanceof FileWithBackupDataSource) { IFile brokenFile = ((FileWithBackupDataSource) source).getBackupFile(); if (!ifExists || brokenFile.exists()) { return brokenFile; } } return null; } private SModel loadSModel() { DataSource source = getSource(); if (!source.isReadOnly() && source.getTimestamp() == -1) { // no file on disk return (SModel) myPersistence.createEmpty(getReference(), getSource()); } try { IFile brokenFile = getBackupFile(true); if (brokenFile != null) { long l = ((FileDataSource) getSource()).getFile().lastModified(); if (l > 0 && brokenFile.lastModified() > l) { SModelBase brokenModel = (SModelBase) PersistenceFacade.getInstance().getDefaultModelFactory().load( new FileDataSource(brokenFile, null), Collections.<String, String>emptyMap()); brokenModel.load(); // force save setChanged(true); return brokenModel.getSModel(); } } return (SModel) myPersistence.readModel(getReference(), getSource()); } catch (IOException e) { return new StubModel(getReference(), e); } } @Override protected void doUnload() { super.doUnload(); myModel = null; } @Override protected void reloadContents() { assertCanChange(); if (!isLoaded()) { return; } final SModel oldModel = myModel; myModel = loadSModel(); oldModel.setModelDescriptor(null); myModel.setModelDescriptor(this); oldModel.dispose(); setChanged(false); // XXX loadSModel() doesn't change loading state (though it's wrong, as reload might load empty model) // hence no fireModelStateChanged() call here fireModelReplaced(); } @Override protected boolean saveModel() throws ModelSaveException, IOException { SModel smodel = getSModel(); if (smodel instanceof InvalidSModel) { // we do not save stub model to not overwrite the real model return false; } try { myPersistence.writeModel(smodel, getSource()); IFile brokenFile = getBackupFile(true); if (brokenFile != null) { brokenFile.delete(); } myProblems = Collections.emptySet(); } catch (ModelSaveException e) { IFile brokenFile = getBackupFile(false); try { PersistenceFacade.getInstance().getDefaultModelFactory().save(this, new FileDataSource(brokenFile, null)); } catch (ModelSaveException ignore) { } catch (IOException ignore) { } myProblems = e.getProblems(); throw e; } return false; } @Override public SNode getRoot() { Iterator<SNode> iterator = getRootNodes().iterator(); return iterator.hasNext() ? iterator.next() : null; } @NotNull @Override public Iterable<Problem> getProblems() { return IterableUtil.merge(myProblems, super.getProblems()); } public static class StubModel extends jetbrains.mps.smodel.SModel implements InvalidSModel { private IOException myCause; public StubModel(@NotNull SModelReference modelReference, @Nullable IOException cause) { super(modelReference); myCause = cause; } @NotNull @Override public Iterable<Problem> getProblems() { return Collections.<Problem>singleton( new PersistenceProblem(Kind.Load, myCause == null ? "Couldn't read model." : "Cannot load. I/O problem: " + myCause.getMessage(), null, true)); } } }