/* * 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.project; import jetbrains.mps.kernel.model.MissingDependenciesFixer; import jetbrains.mps.persistence.DefaultModelRoot; import jetbrains.mps.persistence.ModelCannotBeCreatedException; import jetbrains.mps.persistence.PersistenceRegistry; import jetbrains.mps.project.facets.GenerationTargetFacet; import jetbrains.mps.project.facets.JavaModuleFacet; import jetbrains.mps.project.facets.TestsFacet; import jetbrains.mps.project.persistence.ModuleReadException; import jetbrains.mps.project.structure.modules.ModuleDescriptor; import jetbrains.mps.smodel.Generator; import jetbrains.mps.smodel.Language; import jetbrains.mps.smodel.MPSModuleOwner; import jetbrains.mps.smodel.MPSModuleRepository; import jetbrains.mps.smodel.SModelOperations; import jetbrains.mps.util.annotation.ToRemove; import jetbrains.mps.vfs.IFile; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; 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.module.SRepository; import org.jetbrains.mps.openapi.persistence.ModelFactory; import org.jetbrains.mps.openapi.persistence.ModelFactoryType; import org.jetbrains.mps.openapi.persistence.ModelRoot; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; public class SModuleOperations { private static final Logger LOG = LogManager.getLogger(SModuleOperations.class); /** * @deprecated use {@link jetbrains.mps.project.facets.GenerationTargetFacet#getOutputLocation(SModel)} or {@link JavaModuleFacet#getOutputRoot()}. * Even {@link #getOutputRoot(SModel)} is much better as it (a) deals with IFile (b) hints it's root, not model-specific location */ @Deprecated @ToRemove(version = 3.5) public static String getOutputPathFor(SModel model) { IFile outputDir = SModelOperations.getOutputLocation(model); return outputDir != null ? outputDir.getPath() : null; } /** * Filesystem location for all output of the model's module. * Unlike {@link #getOutputPathFor(org.jetbrains.mps.openapi.model.SModel)} doesn't * translate IFile to String. * * @deprecated use {@link jetbrains.mps.smodel.SModelOperations#getOutputLocation(SModel)} instead. This operation is not {@code SModuleOperation}'s. * * @return module's output path, or null if unknown */ @ToRemove(version = 3.5) @Deprecated public static IFile getOutputRoot(@NotNull SModel model) { return SModelOperations.getOutputLocation(model); } /** * @deprecated This operation knows about output roots of {@link JavaModuleFacet} and {@link TestsFacet} only. Locations of any other * {@link GenerationTargetFacet} are ignored. There's only 1 use in MPS. Client code shall not assume there's single output root for a * module, there could be multiple outputs, specified per model. * @return all locations where generated files (including auxiliary model streams, files with hashes and dependencies) of the module could be found. */ @ToRemove(version = 3.5) @Deprecated public static Collection<IFile> getOutputRoots(@NotNull SModule module) { // XXX there's jetbrains.mps.tool.builder.paths.ModuleOutputPaths which looks quite similar, shall refactor. It's definitely not tooling-specific code. ArrayList<IFile> rv = new ArrayList<>(4); JavaModuleFacet jmf = module.getFacet(JavaModuleFacet.class); if (jmf != null) { IFile path = jmf.getOutputRoot(); if (path != null) { rv.add(path); } path = jmf.getOutputCacheRoot(); if (path != null) { rv.add(path); } } TestsFacet testFacet = module.getFacet(TestsFacet.class); if (testFacet != null) { IFile path = testFacet.getTestsOutputPath(); if (path != null) { rv.add(path); } path = testFacet.getOutputCacheRoot(); if (path != null) { rv.add(path); } } // XXX see DefaultStreamManager#getOverridenOutputDir(SModel) // we shall iterate over all models of the module, check instanceof GeneratableSModel && isGenerateIntoModelFolder(), and // then (md.getSource() as FileDataSource).getParent(), but GeneratedFilesExcludePolicy which I write the method for, used // to be satisfied with #getOutputRoot(), which didn't check for overridden output root either. return rv; } @NotNull public static JavaModuleFacet getJavaFacet(SModule module) { JavaModuleFacet facet = module.getFacet(JavaModuleFacet.class); if (facet != null) { return facet; } else { throw new IllegalArgumentException(); } } public static boolean isCompileInMps(SModule module) { JavaModuleFacet facet = module.getFacet(JavaModuleFacet.class); return facet != null && facet.isCompileInMps(); } public static boolean isCompileInIdea(SModule module) { JavaModuleFacet facet = module.getFacet(JavaModuleFacet.class); return facet != null && !facet.isCompileInMps(); } public static Set<String> getAllSourcePaths(SModule module) { // todo: get rid from source paths? ArrayList<String> paths = new ArrayList<>(4); JavaModuleFacet jmf = module.getFacet(JavaModuleFacet.class); if (jmf != null) { IFile path = jmf.getOutputRoot(); if (path != null) { paths.add(path.getPath()); } paths.addAll(jmf.getAdditionalSourcePaths()); } TestsFacet testsFacet = module.getFacet(TestsFacet.class); if (testsFacet != null) { IFile path = testsFacet.getTestsOutputPath(); if (path != null) { paths.add(path.getPath()); } } return new TreeSet<>(paths); } @Nullable public static EditableSModel createModelWithAdjustments(@NotNull String name, @NotNull ModelRoot root) { try { return createModelWithAdjustments(name, root, null); } catch (ModelCannotBeCreatedException ignore) { } return null; } @NotNull public static EditableSModel createModelWithAdjustments(@NotNull String name, @NotNull ModelRoot root, @Nullable ModelFactoryType modelFactoryType) throws ModelCannotBeCreatedException { EditableSModel model; if (modelFactoryType != null && root instanceof DefaultModelRoot) { DefaultModelRoot defaultModelRoot = (DefaultModelRoot) root; model = (EditableSModel) defaultModelRoot.createModel(new SModelName(name), null, null, modelFactoryType); } else { model = (EditableSModel) root.createModel(name); } // FIXME something bad: see MPS-18545 SModel api: createModel(), setChanged(), isLoaded(), save() // model.getSModel() ? model.setChanged(true); model.save(); ModelsAutoImportsManager.doAutoImport(root.getModule(), model); new MissingDependenciesFixer(model).fixModuleDependencies(); return model; } public static boolean needReloading(AbstractModule module) { // todo: ? SRepository repo = module.getRepository(); if (repo != null) { repo.getModelAccess().checkReadAccess(); } IFile descriptorFile = module.getDescriptorFile(); if ((descriptorFile == null) || !descriptorFile.exists()) { return false; } final ModuleDescriptor descriptor = module.getModuleDescriptor(); if (descriptor == null) return false; String timestampString = descriptor.getTimestamp(); if (timestampString == null) return true; long timestamp = Long.decode(timestampString); return timestamp != descriptorFile.lastModified(); } /** * Reads module from file and eventually reloads it (when CLManager triggers refresh) */ public static void reloadFromDisk(@NotNull AbstractModule module) { if (module.getRepository() == null) { throw new IllegalArgumentException("Module " + module + " is disposed"); } module.getRepository().getModelAccess().checkWriteAccess(); try { if (module instanceof Generator) { // loadDescriptor == null for Generator // FIXME shall support reload for generator modules (not necessarily with modile.loadDescriptor() thought) return; } ModuleDescriptor descriptor = module.loadDescriptor(); module.setModuleDescriptor(descriptor); } catch (ModuleReadException e) { AbstractModule.handleReadProblem(module, e, false); } } public static Project getProjectForModule(SModule module) { if (module == null) { return null; } Project project = null; SRepository repository = module.getRepository(); if (repository instanceof ProjectRepository) { project = ((ProjectRepository) repository).getProject(); } else if (repository instanceof MPSModuleRepository) { Language language = null; Set<MPSModuleOwner> owners = ((MPSModuleRepository) repository).getOwners(module); for (MPSModuleOwner owner : owners) { if (owner instanceof Project) { project = ((Project) owner); break; } else if (owner instanceof Language) { language = (Language) owner; } } if (project == null && language != null) { project = getProjectForModule(language); } } return project; } // helpers private static void checkContentPath(String path, SModule module, ModelRoot modelRoot) { if (PersistenceRegistry.JAVA_CLASSES_ROOT.equals(modelRoot.getType())) { return; } String sig = (containsFilesWithSuffix(new File(path), ".java") ? "j" : "") + (containsFilesWithSuffix(new File(path), ".class") ? "c" : ""); if (sig.length() == 2) { sig = "j&c"; } if (!sig.isEmpty()) { System.out.printf("!%s at %s type in %s%n", sig, modelRoot.getType(), module.getModuleName()); } } private static boolean containsFilesWithSuffix(File path, String suffix) { if (path.isFile()) { return path.getName().endsWith(suffix); } else if (path.isDirectory()) { for (File child : path.listFiles()) { if (containsFilesWithSuffix(child, suffix)) { return true; } } } return false; } }