/* * 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.persistence.CopyNotSupportedException; import jetbrains.mps.extapi.persistence.FileBasedModelRoot; import jetbrains.mps.extapi.persistence.SourceRoot; import jetbrains.mps.extapi.persistence.SourceRootKinds; import jetbrains.mps.extapi.persistence.datasource.URLNotSupportedException; import jetbrains.mps.project.AbstractModule; import jetbrains.mps.smodel.CopyUtil; 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.openapi.model.EditableSModel; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelName; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.persistence.DataSource; import org.jetbrains.mps.openapi.persistence.ModelFactory; import java.io.IOException; import java.net.URISyntaxException; import java.util.List; import static jetbrains.mps.extapi.persistence.datasource.PreinstalledURLDataSourceFactories.FILE_OR_FOLDER; /** * Helps {@link DefaultModelRoot#copyTo(DefaultModelRoot)} * * Created by apyshkin on 12/19/16. */ final class CopyDefaultModelRootHelper { private final static Logger LOG = LogManager.getLogger(CopyDefaultModelRootHelper.class); private final DefaultModelRoot mySourceModelRoot; private final DefaultModelRoot myTargetModelRoot; private final AbstractModule mySourceModule; private final AbstractModule myTargetModule; CopyDefaultModelRootHelper(DefaultModelRoot sourceModelRoot, DefaultModelRoot targetModelRoot) { mySourceModelRoot = sourceModelRoot; myTargetModelRoot = targetModelRoot; mySourceModule = ((AbstractModule) mySourceModelRoot.getModule()); myTargetModule = ((AbstractModule) myTargetModelRoot.getModule()); } private boolean isInsideModuleDir() { final SModule module = mySourceModelRoot.getModule(); if (module instanceof AbstractModule) { IFile contentDirectory = mySourceModelRoot.getContentDirectory(); IFile moduleSourceDir = ((AbstractModule) module).getModuleSourceDir(); if (moduleSourceDir == null) { return false; } assert contentDirectory != null; return FileUtil.isAncestor(moduleSourceDir.getPath(), contentDirectory.getPath()); } return false; } /** * We are doing the same thing we do when collecting models but instead of creating models * we recalculate the paths (and other options) and create corresponding model copies under * the new (target) model root * * @throws CopyNotSupportedException if the content directory of the model root is not under module source directory * since <code>DefaultModelRoot</code> allows to change models under it * we forbid copying such model roots with the content directories located outside of the module * source directory */ public void copy() throws CopyNotSupportedException { if (mySourceModelRoot.getContentDirectory() == null) { return; } if (!isInsideModuleDir()) { throw new CopyNotSupportedException("The model root's content path must be inside module directory " + mySourceModelRoot + " : " + mySourceModelRoot.getModule()); } List<SourceRoot> sourceFiles = mySourceModelRoot.getSourceRoots(SourceRootKinds.SOURCES); List<SourceRoot> targetFiles = myTargetModelRoot.getSourceRoots(SourceRootKinds.SOURCES); assert sourceFiles.size() == targetFiles.size(); // #copyContentRootAndFiles guarantees for (int cnt = 0; cnt < sourceFiles.size(); ++cnt) { SourceRoot sourceRoot = sourceFiles.get(cnt); SourceRoot targetSourceRoot = targetFiles.get(cnt); targetSourceRoot.getAbsolutePath().mkdirs(); ModelSourceRootWalker modelSourceRootWalker = new ModelSourceRootWalker(mySourceModelRoot, (factory, dataSource, options, file) -> { try { IFile targetModelFile = calculateTargetModelFile(mySourceModule, myTargetModule, sourceRoot, targetSourceRoot, file); SModelBase modelData = (SModelBase) new ModelFactoryFacade(factory).load(dataSource, options); createModelCopy(factory, targetModelFile, modelData); } catch (URLNotSupportedException | URISyntaxException | IOException | ModelCannotBeCreatedException e) { LOG.error("", new CopyNotSupportedException("Could not copy because of unexpected error" , e)); } }); modelSourceRootWalker.traverse(sourceRoot); } } @NotNull private SModel createModelCopy(@NotNull ModelFactory factory, @NotNull IFile targetModelFile, @NotNull SModelBase modelDataToCopy) throws IOException, URISyntaxException, URLNotSupportedException, ModelCannotBeCreatedException { DataSource targetDataSource = FILE_OR_FOLDER.create(targetModelFile.getUrl(), myTargetModelRoot); ParametersCalculator prmCalculator = new ParametersCalculator(myTargetModelRoot); SModelName newModelName = new SModelName(convertNameConsideringModule(modelDataToCopy.getName().getValue(), mySourceModule, myTargetModule)); ModelCreationOptions options = prmCalculator.calculate(newModelName); SModel targetModel = myTargetModelRoot.createModel0(factory, targetDataSource, options); // TODO Since model factory can provide any model implementation // TODO model root doesn't know how to exactly copy the content of given model. // TODO So model content copying should be carried by model itself. // TODO This functionality should be extracted in separate interface (like CopyableSModel). CopyUtil.copyModelContentAndPreserveIds(modelDataToCopy, targetModel); CopyUtil.copyModelProperties(modelDataToCopy.getSModel(), ((SModelBase) targetModel).getSModel()); if (targetModel instanceof GeneratableSModel && modelDataToCopy instanceof GeneratableSModel) { ((GeneratableSModel) targetModel).setDoNotGenerate(((GeneratableSModel) modelDataToCopy).isDoNotGenerate()); } saveModel(targetModel); return targetModel; } @NotNull private IFile calculateTargetModelFile(AbstractModule sourceModule, AbstractModule targetModule, SourceRoot sourceRoot, SourceRoot targetSourceRoot, IFile sourceModelFile) { String relPath = FileBasedModelRoot.relativize(sourceModelFile.getPath(), sourceRoot.getAbsolutePath()); relPath = convertNameConsideringModule(relPath, sourceModule, targetModule); return targetSourceRoot.getAbsolutePath().getDescendant(relPath); } /** * A special hack * fixme move to workbench */ @NotNull private String convertNameConsideringModule(String name, AbstractModule sourceModule, AbstractModule targetModule) { if (name.startsWith(sourceModule.getModuleName())) { name = targetModule.getModuleName() + name.substring(sourceModule.getModuleName().length()); } return name; } // FIXME see MPS-18545 private static void saveModel(@NotNull SModel targetModel) { if (targetModel instanceof EditableSModel) { ((EditableSModel) targetModel).setChanged(true); ((EditableSModel) targetModel).save(); } } }