package jetbrains.mps.vcspersistence;
/*Generated by MPS */
import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;
import org.jetbrains.annotations.Nullable;
import jetbrains.mps.smodel.persistence.def.IModelPersistence;
import jetbrains.mps.smodel.persistence.def.v4.ModelPersistence4;
import jetbrains.mps.smodel.persistence.def.v5.ModelPersistence5;
import jetbrains.mps.smodel.persistence.def.v6.ModelPersistence6;
import jetbrains.mps.smodel.persistence.def.v7.ModelPersistence7;
import jetbrains.mps.smodel.persistence.def.v8.ModelPersistence8;
import jetbrains.mps.smodel.persistence.def.ModelPersistence;
import jetbrains.mps.smodel.SModelHeader;
import org.jetbrains.mps.openapi.persistence.StreamDataSource;
import jetbrains.mps.smodel.persistence.def.ModelReadException;
import java.io.InputStream;
import org.xml.sax.InputSource;
import java.io.InputStreamReader;
import jetbrains.mps.util.FileUtil;
import java.io.IOException;
import org.jetbrains.annotations.NotNull;
import jetbrains.mps.baseLanguage.closures.runtime.Wrappers;
import jetbrains.mps.smodel.ModelAccess;
import jetbrains.mps.extapi.persistence.FileDataSource;
import java.util.Map;
import jetbrains.mps.smodel.loading.ModelLoadResult;
import jetbrains.mps.smodel.loading.ModelLoadingState;
import jetbrains.mps.smodel.persistence.def.PersistenceVersionNotFoundException;
import jetbrains.mps.util.xml.XMLSAXHandler;
import jetbrains.mps.smodel.persistence.def.v4.IPersistenceWithReader;
import jetbrains.mps.smodel.persistence.def.v4.IModelReader;
import org.jdom.Document;
import jetbrains.mps.smodel.SModel;
import java.util.List;
import jetbrains.mps.smodel.persistence.lines.LineContent;
import java.io.StringReader;
import jetbrains.mps.util.JDOMUtil;
import org.jdom.JDOMException;
import jetbrains.mps.smodel.DefaultSModel;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.vfs.FileSystem;
import jetbrains.mps.smodel.persistence.def.DefaultMetadataPersistence;
import org.xml.sax.helpers.DefaultHandler;
import jetbrains.mps.util.xml.BreakParseSAXException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import jetbrains.mps.smodel.persistence.def.v9.ModelPersistence9;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import jetbrains.mps.util.StringUtil;
/**
* This is old persistences support for version control purposes.
* It's desirable to be possible to see diff and merge with models in very old persistences, which MPS can't fully
* support because of changes to SModel.
* For VCS purposes, what is needed is to show the model to the user in a state, which is near to how this model
* looked in the interested revision. So, while we can't read all the information into new SModel, we can still try to
* create a new SModel from an old "model state".
*
* The persistences here
* 1. should not be fully-functional.
* 2. can use any hacks to "load" the model.
* 3. must "load" the SModel in "new format" (as if they were save by the last persistence, see below)
* 4. should have "read" access when executing (for now, all public methods start read-actions)
*
* E.g. if in some persistence we had only names of node's concepts, we are still able to remove SConceptByName in newer
* MPS versions. The persistences here can use in-repo or even in-structure-models search to obtain concept ids for
* names it has. It doesn't matter, how. It must not work under any circumstances. It must not produce the exact vision
* of the old model. It MUST produce a new SModel.
* (??? [Mihail Muhin] isn't it better to produce model with persistence version set to LAST_VERSION?)
*/
public class VCSPersistenceSupport {
private static final Logger LOG = LogManager.getLogger(VCSPersistenceSupport.class);
public static final String TARGET_NODE_ID = "targetNodeId";
public static final String LINK = "link";
public static final String ROLE = "role";
public static final String NAME = "name";
public static final String NAMESPACE = "namespace";
public static final String NODE = "node";
public static final String TYPE = "type";
public static final String ID = "id";
public static final String RESOLVE_INFO = "resolveInfo";
public static final String MODEL = "model";
public static final String PROPERTY = "property";
public static final String VALUE = "value";
public static final String IMPORT_ELEMENT = "import";
public static final String VISIBLE_ELEMENT = "visible";
public static final String MODEL_IMPORT_INDEX = "index";
public static final String LANGUAGE = "language";
public static final String LANGUAGE_ASPECT = "languageAspect";
public static final String LANGUAGE_ENGAGED_ON_GENERATION = "language-engaged-on-generation";
public static final String DEVKIT = "devkit";
public static final String MODEL_UID = "modelUID";
public static final String VERSION = "version";
public static final String IMPLICIT = "implicit";
public static final String ROOTS = "roots";
public static final String ROOT_CONTENT = "root";
public static final String PERSISTENCE = "persistence";
public static final String PERSISTENCE_VERSION = "version";
@Nullable
private static IModelPersistence getPersistence(int version) {
// Assert here was replaced with LOG.error before 3.3 as we've found a couple
// places where this incompatibility with older version introduced new bugs
// Actually, these places must be fixed (see e.g. MPS-22503). Still, we
// leave error here till 3.4 or later to minimize the number of real issues [MM]
if (version < 4) {
LOG.error("unsupported version requested " + version, new Throwable());
}
if (version == 4) {
return new ModelPersistence4();
}
if (version == 5) {
return new ModelPersistence5();
}
if (version == 6) {
return new ModelPersistence6();
}
if (version == 7) {
return new ModelPersistence7();
}
if (version == 8) {
return new ModelPersistence8();
}
// todo remove this after removing usages of VCSPersistenceSupport from everywhere except VCSPersistenceUtil
return ModelPersistence.getPersistence(version);
}
private static void loadDescriptor(SModelHeader result, StreamDataSource dataSource) throws ModelReadException {
InputStream in = null;
try {
in = dataSource.openInputStream();
InputSource source = new InputSource(new InputStreamReader(in, FileUtil.DEFAULT_CHARSET));
parseAndHandleExceptions(source, new VCSPersistenceSupport.MyDescriptorHandler(result), "model descriptor");
} catch (IOException e) {
throw new ModelReadException("Couldn't read descriptor from " + dataSource.getLocation() + ": " + e.getMessage(), e);
} finally {
FileUtil.closeFileSafe(in);
}
}
@NotNull
public static SModelHeader loadDescriptor(final InputSource source) throws IOException {
final SModelHeader result = new SModelHeader();
final Wrappers._T<IOException> ex = new Wrappers._T<IOException>(null);
ModelAccess.instance().runReadAction(new Runnable() {
public void run() {
try {
parseAndHandleExceptions(source, new VCSPersistenceSupport.MyDescriptorHandler(result), "model descriptor");
} catch (IOException e) {
ex.value = e;
}
}
});
if (ex.value != null) {
throw ex.value;
}
return result;
}
@NotNull
public static SModelHeader loadDescriptor(final StreamDataSource source) throws ModelReadException {
final SModelHeader result = new SModelHeader();
final Wrappers._T<ModelReadException> ex = new Wrappers._T<ModelReadException>(null);
ModelAccess.instance().runReadAction(new Runnable() {
public void run() {
try {
loadDescriptor(result, source);
// for old persistences try to load header from metadata
if (result.getPersistenceVersion() < 7 && source instanceof FileDataSource) {
Map<String, String> metadata = loadMetadata(((FileDataSource) source).getFile());
if (metadata != null) {
if (metadata.containsKey(SModelHeader.DO_NOT_GENERATE)) {
result.setDoNotGenerate(Boolean.parseBoolean(metadata.remove(SModelHeader.DO_NOT_GENERATE)));
}
}
}
} catch (ModelReadException e) {
ex.value = e;
}
}
});
if (ex.value != null) {
throw ex.value;
}
return result;
}
private static ModelLoadResult readModel(@NotNull SModelHeader header, @NotNull InputSource source, ModelLoadingState state) throws IOException, ModelReadException {
IModelPersistence mp = getPersistence(header.getPersistenceVersion());
if (header.getPersistenceVersion() < 0) {
throw new ModelReadException("Couldn't read model because of unknown persistence version", null);
}
String m = "Can not find appropriate persistence version for model " + header.getModelReference() + "\n Use newer version of JetBrains MPS to load this model.";
if (mp == null) {
throw new PersistenceVersionNotFoundException(m);
}
// first try to use SAX parser
XMLSAXHandler<ModelLoadResult> handler = mp.getModelReaderHandler(state, header);
if (handler != null) {
parseAndHandleExceptions(source, handler, "model");
final ModelLoadResult result = handler.getResult();
// in case persistence version could change during IModelPersistence activities, might need to update header:
// header.setPersistenceVersion(mp.getVersion());
return result;
}
// then try to use DOM reader
if (!(mp instanceof IPersistenceWithReader)) {
throw new PersistenceVersionNotFoundException(m);
}
IModelReader reader = ((IPersistenceWithReader) mp).getModelReader();
if (reader == null) {
throw new PersistenceVersionNotFoundException(m);
}
Document document = loadModelDocument(source);
return new ModelLoadResult((SModel) reader.readModel(document, header), ModelLoadingState.FULLY_LOADED);
}
@NotNull
public static ModelLoadResult readModel(@NotNull final SModelHeader header, @NotNull final StreamDataSource dataSource, final ModelLoadingState state) throws ModelReadException {
final Wrappers._T<ModelLoadResult> result = new Wrappers._T<ModelLoadResult>();
final Wrappers._T<ModelReadException> ex = new Wrappers._T<ModelReadException>(null);
ModelAccess.instance().runReadAction(new Runnable() {
public void run() {
InputStream in = null;
try {
in = dataSource.openInputStream();
InputSource source = new InputSource(new InputStreamReader(in, FileUtil.DEFAULT_CHARSET));
result.value = readModel(header, source, state);
} catch (IOException e) {
ex.value = new ModelReadException("Couldn't read model: " + e.getMessage(), e, header);
} catch (ModelReadException e) {
ex.value = e;
} finally {
FileUtil.closeFileSafe(in);
}
}
});
if (ex.value != null) {
throw ex.value;
}
return result.value;
}
@Nullable
public static List<LineContent> getLineToContentMap(final String content) throws ModelReadException {
final Wrappers._T<List<LineContent>> result = new Wrappers._T<List<LineContent>>(null);
final Wrappers._T<ModelReadException> ex = new Wrappers._T<ModelReadException>(null);
ModelAccess.instance().runReadAction(new Runnable() {
public void run() {
try {
SModelHeader header;
header = loadDescriptor(new InputSource(new StringReader(content)));
IModelPersistence mp = getPersistence(header.getPersistenceVersion());
if (mp == null) {
return;
}
XMLSAXHandler<List<LineContent>> handler = mp.getLineToContentMapReaderHandler();
if (handler == null) {
return;
}
parseAndHandleExceptions(new InputSource(new StringReader(content)), handler, "line to content map");
result.value = handler.getResult();
} catch (IOException e) {
ex.value = new ModelReadException(e.toString(), e);
}
}
});
if (ex.value != null) {
throw ex.value;
}
return result.value;
}
@NotNull
private static Document loadModelDocument(@NotNull InputSource source) throws IOException {
try {
return JDOMUtil.loadDocument(source);
} catch (JDOMException e) {
throw new IOException("Exception on loading model from " + source, e);
}
}
@NotNull
public static DefaultSModel readModel(@NotNull final StreamDataSource source, final boolean interfaceOnly) throws ModelReadException {
final Wrappers._T<DefaultSModel> result = new Wrappers._T<DefaultSModel>();
final Wrappers._T<ModelReadException> ex = new Wrappers._T<ModelReadException>(null);
ModelAccess.instance().runReadAction(new Runnable() {
public void run() {
try {
SModelHeader header = loadDescriptor(source);
ModelLoadingState state = (interfaceOnly ? ModelLoadingState.INTERFACE_LOADED : ModelLoadingState.FULLY_LOADED);
result.value = (DefaultSModel) readModel(header, source, state).getModel();
} catch (ModelReadException e) {
ex.value = e;
}
}
});
if (ex.value != null) {
throw ex.value;
}
return result.value;
}
@NotNull
public static DefaultSModel readModel(@NotNull final String content, final boolean interfaceOnly) throws ModelReadException {
final Wrappers._T<DefaultSModel> result = new Wrappers._T<DefaultSModel>();
final Wrappers._T<ModelReadException> ex = new Wrappers._T<ModelReadException>(null);
ModelAccess.instance().runReadAction(new Runnable() {
public void run() {
try {
SModelHeader header = loadDescriptor(new InputSource(new StringReader(content)));
ModelLoadingState state = (interfaceOnly ? ModelLoadingState.INTERFACE_LOADED : ModelLoadingState.FULLY_LOADED);
result.value = (DefaultSModel) readModel(header, new InputSource(new StringReader(content)), state).getModel();
} catch (IOException e) {
ex.value = new ModelReadException(e.toString(), e);
} catch (ModelReadException e) {
ex.value = e;
}
}
});
if (ex.value != null) {
throw ex.value;
}
return result.value;
}
@Nullable
private static Map<String, String> loadMetadata(IFile modelFile) {
String modelPath = modelFile.getPath();
String versionPath = modelPath.substring(0, modelPath.length() - MPSExtentions.DOT_MODEL.length()) + ".metadata";
IFile metadataFile = FileSystem.getInstance().getFileByPath(versionPath);
if (!(metadataFile.exists())) {
return null;
}
return DefaultMetadataPersistence.load(metadataFile);
}
private static void parseAndHandleExceptions(InputSource source, DefaultHandler handler, String what) throws IOException {
try {
JDOMUtil.createSAXParser().parse(source, handler);
} catch (BreakParseSAXException e) {
// used to break SAX parsing flow
} catch (ParserConfigurationException e) {
LOG.error(e.toString(), e);
throw new IOException(String.format("Couldn't read %s: %s", what, e.getMessage()), e);
} catch (SAXException e) {
throw new IOException(String.format("Couldn't read %s: %s", what, e.getMessage()), e);
}
}
private static class MyDescriptorHandler extends DefaultHandler {
private final SModelHeader myResult;
public MyDescriptorHandler(SModelHeader result) {
myResult = result;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (VCSPersistenceSupport.MODEL.equals(qName)) {
for (int idx = 0; idx < attributes.getLength(); idx++) {
String name = attributes.getQName(idx);
String value = attributes.getValue(idx);
if (VCSPersistenceSupport.MODEL_UID.equals(name) || ModelPersistence9.REF.equals(name)) {
final SModelReference mr = (value == null ? null : PersistenceFacade.getInstance().createModelReference(value));
myResult.setModelReference(mr);
} else
if (SModelHeader.DO_NOT_GENERATE.equals(name)) {
myResult.setDoNotGenerate(Boolean.parseBoolean(value));
} else
if ("version".equals(name)) {
// old model version
} else {
myResult.setOptionalProperty(name, StringUtil.unescapeXml(value));
}
}
} else
if (VCSPersistenceSupport.PERSISTENCE.equals(qName)) {
String s = attributes.getValue(VCSPersistenceSupport.PERSISTENCE_VERSION);
if (s != null) {
try {
myResult.setPersistenceVersion(Integer.parseInt(s));
} catch (NumberFormatException ignored) {
}
}
} else
if ("attribute".equals(qName)) {
myResult.setOptionalProperty(attributes.getValue(VCSPersistenceSupport.NAME), attributes.getValue(VCSPersistenceSupport.VALUE));
} else {
throw new BreakParseSAXException();
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
throw new BreakParseSAXException();
}
}
}