/*
* 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.generator.impl.cache;
import jetbrains.mps.extapi.model.SModelBase;
import jetbrains.mps.extapi.model.SModelData;
import jetbrains.mps.generator.TransientModelsProvider.TransientSwapOwner;
import jetbrains.mps.generator.TransientModelsProvider.TransientSwapSpace;
import jetbrains.mps.persistence.binary.BareNodeReader;
import jetbrains.mps.persistence.binary.BareNodeWriter;
import jetbrains.mps.smodel.ModelDependencyUpdate;
import jetbrains.mps.smodel.TrivialModelDescriptor;
import jetbrains.mps.smodel.persistence.def.ModelReadException;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.util.io.ModelInputStream;
import jetbrains.mps.util.io.ModelOutputStream;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* fyodor, 1/10/11
*/
public abstract class FileSwapOwner implements TransientSwapOwner {
private static Logger LOG = LogManager.getLogger(FileSwapOwner.class);
abstract protected File getSwapDir();
@Override
public TransientSwapSpace initSwapSpace(String spaceId) {
return primSwapSpace(spaceId, true);
}
@Override
public TransientSwapSpace accessSwapSpace(String spaceId) {
return primSwapSpace(spaceId, false);
}
private TransientSwapSpace primSwapSpace(String spaceId, boolean init) {
File swapDir = getSwapDir();
if (swapDir == null) {
LOG.error("No swap directory");
return null;
}
File space = new File(swapDir, spaceId);
if (space.exists()) {
if (!space.isDirectory()) {
LOG.error("Swap space is not a directory");
return null;
}
if (init) {
new FileSwapSpace(space).clear();
}
} else {
if (!init) {
return null;
}
if (!space.mkdirs()) {
LOG.error("Couldn't create swap space directory");
return null;
}
}
return new FileSwapSpace(space);
}
public static class FileSwapSpace implements TransientSwapSpace {
private File mySpaceDir;
public FileSwapSpace(File dir) {
mySpaceDir = dir;
}
@Override
public boolean swapOut(SModelData model) {
if (mySpaceDir == null || !mySpaceDir.exists()) throw new IllegalStateException("no swap dir");
String modelId = model.getReference().getModelId().toString();
if (modelId == null || modelId.isEmpty()) {
LOG.error("Bad model id <" + modelId + ">");
return false;
}
modelId = modelId.replaceAll(":", "-");
File swapFile = new File(mySpaceDir, modelId);
if (swapFile.exists() && !swapFile.delete()) {
LOG.error("Couldn't delete swap file");
return false;
}
ArrayList<SNode> roots = new ArrayList<SNode>();
for (SNode next : model.getRootNodes()) {
roots.add(next);
}
ModelOutputStream mos = null;
IOException ioex = null;
try {
mos = new ModelOutputStream(new FileOutputStream(swapFile));
saveModel(model.getReference(), roots, mos);
} catch (IOException e) {
ioex = e;
LOG.error(null, e);
} finally {
if (mos != null) {
try {
mos.close();
} catch (IOException ignore) {
}
}
}
return ioex == null;
}
@Override
public <T extends SModelData> T restoreFromSwap(SModelReference mref, T modelData) {
if (mySpaceDir == null || !mySpaceDir.exists()) throw new IllegalStateException("no swap dir");
String modelId = mref.getModelId().toString();
if (modelId == null || modelId.isEmpty()) {
throw new IllegalStateException("bad modelId");
}
modelId = modelId.replaceAll(":", "-");
File swapFile = new File(mySpaceDir, modelId);
if (!swapFile.exists()) {
throw new IllegalStateException("no swap file");
}
ModelInputStream mis = null;
try {
mis = new ModelInputStream(new FileInputStream(swapFile));
return loadModel(mref, mis, modelData);
} catch (IOException e) {
LOG.error(null, e);
throw new RuntimeException(e);
} finally {
if (mis != null) {
try {
mis.close();
} catch (IOException ignore) {
}
}
if (!swapFile.delete()) {
LOG.error("Couldn't delete swap file");
}
}
}
@Override
public void clear() {
if (mySpaceDir == null || !mySpaceDir.exists()) throw new IllegalStateException("no swap dir");
for (File f : mySpaceDir.listFiles()) {
f.delete();
}
mySpaceDir.delete();
mySpaceDir = null;
}
private static final int VERSION = 49;
public <T extends SModelData> T loadModel(SModelReference modelReference, ModelInputStream is, T model) throws IOException {
int version = is.readInt();
if (version != VERSION) {
return null;
}
new BareNodeReader(modelReference, is).readNodesInto(model);
return model;
}
public void saveModel(SModelReference modelReference, List<SNode> roots, ModelOutputStream os) throws IOException {
os.writeInt(VERSION);
new BareNodeWriter(modelReference, os).writeNodes(roots);
}
}
// method created for testing
public static SNode writeAndReadNode(SNode node) throws IOException {
final SModelReference modelReference = node.getModel().getReference();
ByteArrayOutputStream os = new ByteArrayOutputStream();
ModelOutputStream mos = new ModelOutputStream(os);
BareNodeWriter writer = new BareNodeWriter(modelReference, mos);
writer.writeNode(node);
mos.close();
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
BareNodeReader reader = new BareNodeReader(modelReference, new ModelInputStream(is));
return reader.readNode(null);
}
// method created for testing
// FIXME can take openapi.SModel
public static SModel writeAndReadModel(SModel model) throws IOException, ModelReadException {
// write
final ByteArrayOutputStream os = new ByteArrayOutputStream(2048);
final ModelOutputStream mos = new ModelOutputStream(os);
mos.writeInt(44);
new BareNodeWriter(model.getReference(), mos).writeNodes(IterableUtil.asCollection(model.getRootNodes()));
mos.close();
final jetbrains.mps.smodel.SModel resultModel = new jetbrains.mps.smodel.SModel(
PersistenceFacade.getInstance().createModelReference("smodel.long.name.for.testing"));
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
ModelInputStream mis = new ModelInputStream(is);
// read
final int version = mis.readInt();
if (version != 44) {
return null;
}
new BareNodeReader(resultModel.getReference(), mis).readNodesInto(resultModel);
SModelBase result = new TrivialModelDescriptor(resultModel);
new ModelDependencyUpdate(result).updateUsedLanguages().updateImportedModels(model.getRepository());
return result;
}
}