/* * 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.model.GeneratableSModel; import jetbrains.mps.extapi.model.SModelBase; import jetbrains.mps.extapi.model.SModelData; import jetbrains.mps.extapi.persistence.datasource.PreinstalledDataSourceTypes; import jetbrains.mps.generator.ModelDigestUtil; import jetbrains.mps.persistence.MetaModelInfoProvider.MetaInfoLoadingOption; import jetbrains.mps.persistence.MetaModelInfoProvider.RegularMetaModelInfo; import jetbrains.mps.persistence.MetaModelInfoProvider.StuffedMetaModelInfo; import jetbrains.mps.persistence.binary.BinaryPersistence; import jetbrains.mps.project.MPSExtentions; import jetbrains.mps.smodel.DefaultSModelDescriptor; import jetbrains.mps.smodel.SModelHeader; import jetbrains.mps.smodel.SModelId; import jetbrains.mps.smodel.loading.ModelLoadResult; import jetbrains.mps.smodel.loading.ModelLoadingState; import jetbrains.mps.smodel.persistence.def.ModelReadException; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.annotations.Internal; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelName; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.persistence.DataSource; import org.jetbrains.mps.openapi.persistence.ModelCreationException; import org.jetbrains.mps.openapi.persistence.ModelFactory; import org.jetbrains.mps.openapi.persistence.ModelFactoryType; import org.jetbrains.mps.openapi.persistence.ModelLoadException; import org.jetbrains.mps.openapi.persistence.ModelLoadingOption; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; import org.jetbrains.mps.openapi.persistence.StreamDataSource; import org.jetbrains.mps.openapi.persistence.UnsupportedDataSourceException; import org.jetbrains.mps.openapi.persistence.datasource.DataSourceType; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; /** * evgeny, 11/20/12 */ public class BinaryModelFactory implements ModelFactory, IndexAwareModelFactory { @NotNull private static PersistenceFacade FACADE() { return PersistenceFacade.getInstance(); } @Internal public BinaryModelFactory() { // do not delete, it is a java service } @NotNull @Override public SModel load(@NotNull DataSource dataSource, @NotNull Map<String, String> options) throws IOException { try { if (Boolean.parseBoolean(options.get(MetaModelInfoProvider.OPTION_KEEP_READ_METAINFO))) { return load(dataSource, MetaInfoLoadingOption.KEEP_READ); } return load(dataSource); } catch (ModelLoadException e) { throw new IOException(e); } } @NotNull @Override public SModel create(@NotNull DataSource dataSource, @NotNull Map<String, String> options) throws IOException { String modelName = options.get(OPTION_MODELNAME); if (modelName == null) { throw new IOException("Model name is not provided"); } try { return create(dataSource, new SModelName(modelName)); } catch (ModelCreationException e) { throw new IOException(e); } } @Override public boolean canCreate(@NotNull DataSource dataSource, @NotNull Map<String, String> options) { return dataSource instanceof StreamDataSource; } @Override public boolean supports(@NotNull DataSource dataSource) { return dataSource instanceof StreamDataSource; } @NotNull @Override public SModel create(@NotNull DataSource dataSource, @NotNull SModelName modelName, @NotNull ModelLoadingOption... options) throws UnsupportedDataSourceException, ModelCreationException { if (!supports(dataSource)) { throw new UnsupportedDataSourceException(dataSource); } StreamDataSource source = (StreamDataSource) dataSource; final SModelHeader header = new SModelHeader(); SModelReference newModelRef = FACADE().createModelReference(null, SModelId.generate(), modelName.getValue()); header.setModelReference(newModelRef); return new DefaultSModelDescriptor(new PersistenceFacility(this, source), header); } @NotNull @Override public SModel load(@NotNull DataSource dataSource, @NotNull ModelLoadingOption... options) throws UnsupportedDataSourceException, ModelLoadException { if (!supports(dataSource)) { throw new UnsupportedDataSourceException(dataSource); } StreamDataSource source = (StreamDataSource) dataSource; SModelHeader binaryModelHeader; try { binaryModelHeader = BinaryPersistence.readHeader(source); } catch (ModelReadException e) { throw new ModelLoadException("Could not read the model header while loading from the '" + dataSource + "'", Collections.emptyList(), getCause(e)); } if (Arrays.asList(options).contains(MetaInfoLoadingOption.KEEP_READ)) { binaryModelHeader.setMetaInfoProvider(new StuffedMetaModelInfo(new RegularMetaModelInfo(binaryModelHeader.getModelReference()))); } return new DefaultSModelDescriptor(new PersistenceFacility(this, source), binaryModelHeader); } private Throwable getCause(ModelReadException e) { Throwable cause; if (e.getCause() instanceof IOException) { cause = e.getCause(); } else { cause = e; } return cause; } @Override public boolean needsUpgrade(@NotNull DataSource dataSource) throws IOException { return false; } @Override public void upgrade(@NotNull DataSource dataSource) throws IOException { // no-op } @Override public void save(@NotNull SModel model, @NotNull DataSource dataSource) throws IOException { if (!(dataSource instanceof StreamDataSource)) { throw new UnsupportedDataSourceException(dataSource); } BinaryPersistence.writeModel(((SModelBase) model).getSModel(), (StreamDataSource) dataSource); } @Override public boolean isBinary() { return true; } @Override public String getFileExtension() { return MPSExtentions.MODEL_BINARY; } @NotNull @Override public String getFormatTitle() { return "Universal binary format"; } @NotNull @Override public ModelFactoryType getType() { return PreinstalledModelFactoryTypes.BINARY; } @NotNull @Override public List<DataSourceType> getPreferredDataSourceTypes() { return Collections.singletonList(PreinstalledDataSourceTypes.BINARY); } @Override public void index(@NotNull InputStream input, @NotNull Callback callback) throws IOException { BinaryPersistence.index(input, callback); } @Override public SModelData parseSingleStream(@NotNull String name, @NotNull InputStream input) throws IOException { return BinaryPersistence.getModelData(input); } public static Map<String, String> getDigestMap(@NotNull StreamDataSource source) { try { SModelHeader binaryModelHeader = BinaryPersistence.readHeader(source); binaryModelHeader.setMetaInfoProvider(new StuffedMetaModelInfo(new RegularMetaModelInfo(binaryModelHeader.getModelReference()))); final ModelLoadResult loadedModel = BinaryPersistence.readModel(binaryModelHeader, source, false); Map<String, String> result = BinaryPersistence.getDigestMap(loadedModel.getModel(), binaryModelHeader.getMetaInfoProvider()); result.put(GeneratableSModel.FILE, ModelDigestUtil.hashBytes(source.openInputStream())); return result; } catch (ModelReadException ignored) { /* ignore */ } catch (IOException ignored) { /* ignore */ } return null; } /** * This is provisional workaround to deal with performance tuning in jps/plugin (see CachedRepositoryData, CachedModelData) * where header is serialized to get passed to another process, where model is instantiated without need to read model file. * * If there's real benefit in this optimization (commit comment suggests it's 0.5 second in process startup time, which doesn't look too much, imo) * this serialization shall be addressed with an object supplied by descriptor itself, rather than by external means, so that full control over * serialize/restore is inside implementation, and all the internal stuff (like model header) doesn't get exposed. * FIXME revisit, reconsider approach */ public static SModel createFromHeader(@NotNull SModelHeader header, @NotNull StreamDataSource dataSource) { final ModelFactory modelFactory = FACADE().getModelFactory(MPSExtentions.MODEL_BINARY); assert modelFactory instanceof BinaryModelFactory; return new DefaultSModelDescriptor(new PersistenceFacility((BinaryModelFactory) modelFactory, dataSource), header.createCopy()); } private static class PersistenceFacility extends LazyLoadFacility { /*package*/ PersistenceFacility(BinaryModelFactory modelFactory, StreamDataSource dataSource) { super(modelFactory, dataSource); } @NotNull @Override public StreamDataSource getSource() { return (StreamDataSource) super.getSource(); } @Override public Map<String, String> getGenerationHashes() { Map<String, String> generationHashes = ModelDigestHelper.getInstance().getGenerationHashes(getSource()); if (generationHashes != null) return generationHashes; return BinaryModelFactory.getDigestMap(getSource()); } @NotNull @Override public SModelHeader readHeader() throws ModelReadException { return BinaryPersistence.readHeader(getSource()); } @NotNull @Override public ModelLoadResult readModel(@NotNull SModelHeader header, @NotNull ModelLoadingState state) throws ModelReadException { return BinaryPersistence.readModel(header, getSource(), state == ModelLoadingState.INTERFACE_LOADED); } @Override public boolean doesSaveUpgradePersistence(@NotNull SModelHeader header) { // binary persistence doesn't have versions yet return false; } @Override public void saveModel(@NotNull SModelHeader header, SModelData modelData) throws IOException { BinaryPersistence.writeModel((jetbrains.mps.smodel.SModel) modelData, getSource()); } } }