/* * Copyright 2003-2015 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.FolderDataSource; import jetbrains.mps.extapi.persistence.datasource.PreinstalledDataSourceTypes; 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.FilePerRootFormatUtil; import jetbrains.mps.smodel.persistence.def.ModelPersistence; import jetbrains.mps.smodel.persistence.def.ModelReadException; import jetbrains.mps.util.FileUtil; import jetbrains.mps.vfs.IFile; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; 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.ModelSaveException; import org.jetbrains.mps.openapi.persistence.MultiStreamDataSource; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; import org.jetbrains.mps.openapi.persistence.UnsupportedDataSourceException; import org.jetbrains.mps.openapi.persistence.datasource.DataSourceType; import org.xml.sax.InputSource; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * evgeny, 6/3/13 */ public class FilePerRootModelFactory implements ModelFactory, IndexAwareModelFactory { private static final Logger LOG = LogManager.getLogger(FilePerRootModelFactory.class); @NotNull private static PersistenceFacade FACADE() { return PersistenceFacade.getInstance(); } @Internal public FilePerRootModelFactory() { // do not delete, it is a java service } @NotNull @Override public SModel load(@NotNull DataSource dataSource, @NotNull Map<String, String> options) throws IOException { try { 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 MultiStreamDataSource; } @Override public boolean supports(@NotNull DataSource dataSource) { return dataSource instanceof MultiStreamDataSource; } @NotNull @Override public SModel create(@NotNull DataSource dataSource, @NotNull SModelName modelName, @NotNull ModelLoadingOption... options) throws UnsupportedDataSourceException, ModelCreationException { if (!supports(dataSource)) { throw new UnsupportedDataSourceException(dataSource); } SModelReference ref = FACADE().createModelReference(null, SModelId.generate(), modelName.getValue()); final SModelHeader header = SModelHeader.create(ModelPersistence.LAST_VERSION); header.setModelReference(ref); return new DefaultSModelDescriptor(new PersistenceFacility(this, (MultiStreamDataSource) dataSource), header); } @NotNull @Override public SModel load(@NotNull DataSource dataSource, @NotNull ModelLoadingOption... options) throws UnsupportedDataSourceException, ModelLoadException { if (!supports(dataSource)) { throw new UnsupportedDataSourceException(dataSource); } MultiStreamDataSource source = (MultiStreamDataSource) dataSource; PersistenceFacility pf = new PersistenceFacility(this, source); SModelHeader header; try { header = pf.readHeader(); } catch (ModelReadException ignored) { LOG.error("Can't read model: ", ignored); throw new ModelLoadException("Can't read model: ", Collections.emptyList(), ignored); } if (header.getModelReference() == null) { throw new ModelLoadException("Could not find model reference in the model header while loading from the " + source); } LOG.debug("Getting model " + header.getModelReference() + " from " + source.getLocation()); return new DefaultSModelDescriptor(pf, header); } @Override public boolean needsUpgrade(@NotNull DataSource dataSource) throws IOException { if (!supports(dataSource)) { throw new UnsupportedDataSourceException(dataSource); } InputStream in = null; try { in = ((MultiStreamDataSource) dataSource).openInputStream(FilePerRootDataSource.HEADER_FILE); InputSource source = new InputSource(new InputStreamReader(in, FileUtil.DEFAULT_CHARSET)); // FIXME replace with SingleStreamSource SModelHeader header = ModelPersistence.loadDescriptor(source); return header.getPersistenceVersion() < ModelPersistence.LAST_VERSION; } catch (ModelReadException ex) { throw new IOException(ex); } finally { FileUtil.closeFileSafe(in); } } @Override public void upgrade(@NotNull DataSource dataSource) throws IOException { if (!supports(dataSource)) { throw new UnsupportedDataSourceException(dataSource); } MultiStreamDataSource source = (MultiStreamDataSource) dataSource; try { ModelLoadResult model = FilePerRootFormatUtil.readModel(null, source, ModelLoadingState.FULLY_LOADED); FilePerRootFormatUtil.saveModel(model.getModel(), source, ModelPersistence.LAST_VERSION); } catch (ModelReadException ex) { throw new IOException(ex.getMessage(), ex); } } @Override public void save(@NotNull SModel model, @NotNull DataSource dataSource) throws ModelSaveException, IOException { if (!supports(dataSource)) { throw new UnsupportedDataSourceException(dataSource); } FilePerRootFormatUtil.saveModel(((SModelBase) model).getSModel(), (MultiStreamDataSource) dataSource, ModelPersistence.LAST_VERSION); } @Override public boolean isBinary() { return false; } @Override public String getFileExtension() { return null; } @NotNull @Override public String getFormatTitle() { return "Universal XML-based file-per-root format"; } @NotNull @Override public ModelFactoryType getType() { return PreinstalledModelFactoryTypes.PER_ROOT_XML; } @NotNull @Override public List<DataSourceType> getPreferredDataSourceTypes() { return Arrays.asList(PreinstalledDataSourceTypes.MODEL, PreinstalledDataSourceTypes.FOLDER, PreinstalledDataSourceTypes.MODEL_ROOT); } public static Map<String, String> getModelHashes(@NotNull MultiStreamDataSource source) { BigInteger fileHash = BigInteger.ZERO; Map<String, String> result = new HashMap<String, String>(); for (String streamName : source.getAvailableStreams()) { Map<String, String> generationHashes = null; if (source instanceof FolderDataSource) { IFile file = ((FolderDataSource) source).getFile(streamName); generationHashes = file == null ? null : ModelDigestHelper.getInstance().getGenerationHashes(file); } if (generationHashes == null) { generationHashes = DefaultModelPersistence.getDigestMap(source, streamName); if (generationHashes == null) { // no hash for stream return null; } } String streamHash = generationHashes.get(GeneratableSModel.FILE); if (streamName.equals(FilePerRootDataSource.HEADER_FILE)) { result.put(GeneratableSModel.HEADER, streamHash); } else { // copy root keys for (Entry<String, String> entry : generationHashes.entrySet()) { String key = entry.getKey(); if (GeneratableSModel.FILE.equals(key) || GeneratableSModel.HEADER.equals(key)) continue; result.put(entry.getKey(), entry.getValue()); } } fileHash = fileHash.xor(new BigInteger(streamHash, Character.MAX_RADIX)); } result.put(GeneratableSModel.FILE, fileHash.toString(Character.MAX_RADIX)); return result; } @Override public void index(@NotNull InputStream input, @NotNull Callback callback) throws IOException { ModelPersistence.index(input, callback); } @Override public SModelData parseSingleStream(@NotNull String name, @NotNull InputStream input) throws IOException { return ModelPersistence.getModelData(input); } private static class PersistenceFacility extends LazyLoadFacility { public PersistenceFacility(@NotNull FilePerRootModelFactory modelFactory, @NotNull MultiStreamDataSource dataSource) { super(modelFactory, dataSource); } @NotNull @Override public MultiStreamDataSource getSource() { return (MultiStreamDataSource) super.getSource(); } @Override public String getModelHash() { Map<String, String> genHashes = getGenerationHashes(); if (genHashes == null) { // I/O problem, hash is not available return null; } return genHashes.get(GeneratableSModel.FILE); } @Override public Map<String, String> getGenerationHashes() { return FilePerRootModelFactory.getModelHashes(getSource()); } @NotNull @Override public SModelHeader readHeader() throws ModelReadException { return FilePerRootFormatUtil.loadDescriptor(getSource()); } @NotNull @Override public ModelLoadResult readModel(@NotNull SModelHeader header, @NotNull ModelLoadingState state) throws ModelReadException { return FilePerRootFormatUtil.readModel(header, getSource(), state); } @Override public boolean doesSaveUpgradePersistence(@NotNull SModelHeader header) { return FilePerRootFormatUtil.actualPersistenceVersion(header.getPersistenceVersion()) != header.getPersistenceVersion(); } @Override public void saveModel(@NotNull SModelHeader header, SModelData modelData) throws IOException { FilePerRootFormatUtil.saveModel((jetbrains.mps.smodel.SModel) modelData, getSource(), header.getPersistenceVersion()); } } }