/*
* 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.persistence;
import jetbrains.mps.extapi.persistence.ModelFactoryService;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryFromURL;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryRuleCoreService;
import jetbrains.mps.extapi.persistence.datasource.PreinstalledURLDataSourceFactories;
import jetbrains.mps.extapi.persistence.datasource.URLNotSupportedException;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.util.FileUtil;
import jetbrains.mps.util.JDOMUtil;
import jetbrains.mps.vfs.IFile;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.persistence.DataSource;
import org.jetbrains.mps.openapi.persistence.DataSourceListener;
import org.jetbrains.mps.openapi.persistence.ModelFactory;
import org.jetbrains.mps.openapi.persistence.ModelSaveException;
import org.jetbrains.mps.openapi.persistence.MultiStreamDataSource;
import org.jetbrains.mps.openapi.persistence.StreamDataSource;
import org.jetbrains.mps.openapi.persistence.datasource.DataSourceType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import static java.util.Collections.singletonMap;
/**
* evgeny, 3/6/13
*/
public final class PersistenceUtil {
private static final Logger LOG = LogManager.getLogger(PersistenceUtil.class);
private PersistenceUtil() {
}
/**
* Try to load a model using a default {@link org.jetbrains.mps.openapi.persistence.ModelFactory}
* identified by <code>extension</code> from supplied textual <code>content</code>.
*
* @return <code>null</code> if fails to load model from the content supplied (either model read error, no model factory for the extension, or factory
* doesn't support textual content)
*/
@Nullable
public static SModel loadModel(@NotNull String content) {
@SuppressWarnings("ConstantConditions")
@NotNull ModelFactory factory = ModelFactoryService.getInstance().getFactoryByType(PreinstalledModelFactoryTypes.PLAIN_XML);
byte[] bytes = content.getBytes(FileUtil.DEFAULT_CHARSET);
return loadModel(bytes, factory);
}
/**
* Try to load a model using {@link org.jetbrains.mps.openapi.persistence.ModelFactory}
* from supplied <code>content</code>.
*
* @return <code>null</code> if fails to load model from the content supplied (either model read error)
* or loaded model from the byte array content using the supplied model factory
*/
@Nullable
public static SModel loadModel(final byte[] content, @NotNull ModelFactory factory) {
try {
SModel model = factory.load(new ByteArrayInputSource(content), singletonMap(ModelFactory.OPTION_CONTENT_ONLY, Boolean.TRUE.toString()));
model.load();
return model;
} catch (IOException ex) {
return null;
}
}
@Nullable
public static SModel loadBinaryModel(final byte[] content) {
//noinspection ConstantConditions
return loadModel(content, ModelFactoryService.getInstance().getFactoryByType(PreinstalledModelFactoryTypes.BINARY));
}
public static SModel loadModel(@NotNull IFile file) {
try {
URL url = file.getUrl();
DataSourceFactoryFromURL dataSourceFactory = getDataSourceFactory(url);
if (dataSourceFactory == null) {
return null;
}
DataSource dataSource = dataSourceFactory.create(url, null);
if (dataSource.getType() == null) {
return null;
}
ModelFactory factory = ModelFactoryService.getInstance().getDefaultModelFactory(dataSource.getType());
if (factory == null) {
return null;
}
final Map<String, String> options = singletonMap(ModelFactory.OPTION_CONTENT_ONLY, Boolean.TRUE.toString());
SModel model = factory.load(dataSource, options);
model.load();
return model;
} catch (IOException | URLNotSupportedException e) {
LOG.error("", e);
return null;
}
}
@Nullable
private static DataSourceFactoryFromURL getDataSourceFactory(@NotNull URL url) {
DataSourceFactoryRuleCoreService service = DataSourceFactoryRuleCoreService.getInstance();
DataSourceFactoryFromURL dataSourceFactory = service.getFactory(url);
if (dataSourceFactory == null) {
LOG.error("Data Source Factory is not found for " + url);
return null;
}
return dataSourceFactory;
}
public static String saveModel(final SModel model, String extension) {
ModelFactory factory = PersistenceRegistry.getInstance().getModelFactory(extension);
if (factory == null || factory.isBinary()) {
return null;
}
try {
InMemoryStreamDataSource source = new InMemoryStreamDataSource();
factory.save(model, source);
return source.getContent(FileUtil.DEFAULT_CHARSET_NAME);
} catch (ModelSaveException | IOException e) {
LOG.error(e);
}
return null;
}
public static Element saveModelToXml(final SModel model) {
try {
SAXBuilder saxBuilder = new SAXBuilder();
Element rootElement = saxBuilder.build(modelContentAsStream(model, MPSExtentions.MODEL)).getRootElement();
rootElement.detach();
return rootElement;
} catch (IOException | JDOMException e) {
LOG.error(e);
}
return null;
}
public static SModel loadModelFromXml(final Element element) {
return loadModel(JDOMUtil.asString(new org.jdom.Document(element)));
}
public static byte[] saveBinaryModel(final SModel model) {
ModelFactory factory = PersistenceRegistry.getInstance().getModelFactory(MPSExtentions.MODEL_BINARY);
try {
InMemoryStreamDataSource source = new InMemoryStreamDataSource();
factory.save(model, source);
return source.myStream.toByteArray();
} catch (ModelSaveException | IOException e) {
LOG.error(e);
}
return null;
}
/**
* Serialize model with a persistence identified by extension and provide access to serialized content through InputStream.
* @return empty stream in case serialization failed. Caller is responsible to close the stream.
*/
public static InputStream modelContentAsStream(final SModel model, String extension) {
ModelFactory factory = PersistenceRegistry.getInstance().getModelFactory(extension);
if (factory != null) {
try {
InMemoryStreamDataSource source = new InMemoryStreamDataSource();
factory.save(model, source);
return source.getContentAsStream();
} catch (ModelSaveException | IOException e) {
LOG.error(e);
// fall-through
}
}
return new ByteArrayInputStream(new byte[0]);
}
public static String savePerRootModel(final SModel model, String name) {
ModelFactory factory = ModelFactoryService.getInstance().getFactoryByType(PreinstalledModelFactoryTypes.PER_ROOT_XML);
if (factory == null || factory.isBinary()) {
return null;
}
try {
InMemoryMultiStreamDataSource source = new InMemoryMultiStreamDataSource();
factory.save(model, source);
return source.getContent(name, FileUtil.DEFAULT_CHARSET_NAME);
} catch (ModelSaveException | IOException e) {
LOG.error(e);
}
return null;
}
public static String savePerRootModel(final SModel model, boolean isHeader) {
ModelFactory factory = ModelFactoryService.getInstance().getFactoryByType(PreinstalledModelFactoryTypes.PER_ROOT_XML);
if (factory == null || factory.isBinary()) {
return null;
}
try {
InMemoryMultiStreamDataSource source = new InMemoryMultiStreamDataSource();
factory.save(model, source);
if (isHeader) {
return source.getContent(MPSExtentions.DOT_MODEL_HEADER, FileUtil.DEFAULT_CHARSET_NAME);
} else {
for (String name : source.getAvailableStreams()) {
if (name.equals(MPSExtentions.DOT_MODEL_HEADER)) continue;
return source.getContent(name, FileUtil.DEFAULT_CHARSET_NAME);
}
}
} catch (ModelSaveException | IOException e) {
LOG.error(e);
}
return null;
}
public static abstract class StreamDataSourceBase implements StreamDataSource {
@NotNull
@Override
public String getLocation() {
return "in-memory";
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public InputStream openInputStream() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public OutputStream openOutputStream() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void addListener(@NotNull DataSourceListener listener) {
}
@Override
public void removeListener(@NotNull DataSourceListener listener) {
}
@Override
public long getTimestamp() {
return 0;
}
}
public static class InMemoryStreamDataSource extends StreamDataSourceBase {
private ByteArrayOutputStream myStream;
@Override
public OutputStream openOutputStream() throws IOException {
myStream = new ByteArrayOutputStream();
return myStream;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public DataSourceType getType() {
return null;
}
public InputStream getContentAsStream() {
return new ByteArrayInputStream(myStream.toByteArray());
}
public String getContent(String charsetName) {
try {
return myStream.toString(charsetName);
} catch (UnsupportedEncodingException e) {
LOG.error(e);
return null;
}
}
}
public abstract static class MultiStreamDataSourceBase implements MultiStreamDataSource {
@NotNull
@Override
public InputStream openInputStream(String name) throws IOException {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public OutputStream openOutputStream(String name) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public boolean delete(String name) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public String getLocation() {
return "in-memory";
}
@Override
public void addListener(@NotNull DataSourceListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void removeListener(@NotNull DataSourceListener listener) {
throw new UnsupportedOperationException();
}
@Override
public long getTimestamp() {
return 0;
}
@Override
public boolean isReadOnly() {
return true;
}
}
public static class InMemoryMultiStreamDataSource extends MultiStreamDataSourceBase {
private Map<String, ByteArrayOutputStream> myStreams = new LinkedHashMap<String, ByteArrayOutputStream>();
@NotNull
@Override
public Iterable<String> getAvailableStreams() {
return myStreams.keySet();
}
@NotNull
@Override
public OutputStream openOutputStream(String name) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
myStreams.put(name, stream);
return stream;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public DataSourceType getType() {
return null;
}
public String getContent(String name, String charsetName) {
try {
ByteArrayOutputStream stream = myStreams.get(name);
if (stream == null) {
return null;
}
return stream.toString(charsetName);
} catch (UnsupportedEncodingException e) {
LOG.error(e);
return null;
}
}
}
}