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