/* * 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.progress.EmptyProgressMonitor; import jetbrains.mps.project.dependency.GeneratorModuleScanner; import jetbrains.mps.project.structure.modules.Dependency; import jetbrains.mps.smodel.Generator; import jetbrains.mps.smodel.Language; import jetbrains.mps.smodel.ModelDependencyScanner; import jetbrains.mps.smodel.SModelOperations; import jetbrains.mps.smodel.SModelStereotype; import jetbrains.mps.util.annotation.ToRemove; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.language.SLanguage; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.util.ProgressMonitor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; // FIXME (1) OrganizeImports (there's no optimization) // FIXME (2) anything but empty dialog when OI for a generator module // FIXME (3) sort dependencies by name, group by kind(SDependencyScope) to make dependencies look uniform and easy to grasp. // FIXME (4) Refactor the class, reporting and breathe some OOP in here public class OptimizeImportsHelper { private static final Logger LOG = LogManager.getLogger(OptimizeImportsHelper.class); private final SRepository myRepository; /** * @param repository -- is a context repository which contains the modules/models the client want to resolve */ public OptimizeImportsHelper(@NotNull SRepository repository) { myRepository = repository; } //----public optimizeX methods-------- @NotNull public String optimizeProjectImports(Project p, ProgressMonitor monitor) { return optimizeProjectImports_internal(p, monitor).myReport; } /** * Optimizes project imports. Might take some time. * @deprecated use {@link #optimizeProjectImports(Project, ProgressMonitor)} instead */ @Deprecated @ToRemove(version = 3.3) public String optimizeProjectImports(Project p) { return optimizeProjectImports(p, new EmptyProgressMonitor()); } @NotNull public String optimizeSolutionImports(Solution solution) { return optimizeSolutionImports_internal(solution).myReport; } @NotNull public String optimizeLanguageImports(Language language) { return optimizeLanguageImports_internal(language).myReport; } @NotNull public String optimizeModelsImports(List<SModel> modelsToOptimize, ProgressMonitor monitor) { return optimizeModelsImports_internal(modelsToOptimize, monitor).myReport; } /** * Optimizes imports for a list of models. Might take some time, so please pass the monitor parameter * @deprecated use {@link #optimizeModelsImports(List, ProgressMonitor)} */ @Deprecated @ToRemove(version = 3.3) @NotNull public String optimizeModelsImports(List<SModel> modelsToOptimize) { return optimizeModelsImports(modelsToOptimize, new EmptyProgressMonitor()); } @NotNull public String optimizeModelImports(SModel modelDescriptor) { return optimizeModelImports_internal(modelDescriptor).myReport; } //----internal optimizeX methods-------- private Result optimizeProjectImports_internal(Project p, ProgressMonitor monitor) { Result result = new Result(); List<Language> projectLangs = p.getProjectModules(Language.class); List<Solution> projectSolutions = p.getProjectModules(Solution.class); monitor.start("Optimizing project imports", projectLangs.size() + projectSolutions.size()); try { for (Language l : projectLangs) { monitor.step(l.toString()); result.add(optimizeLanguageImports_internal(l)); monitor.advance(1); if (monitor.isCanceled()) { return result; } } for (Solution s : p.getProjectModules(Solution.class)) { monitor.step(s.toString()); result.add(optimizeSolutionImports_internal(s)); monitor.advance(1); if (monitor.isCanceled()) { return result; } } } finally { monitor.done(); } return result; } private Result optimizeSolutionImports_internal(Solution solution) { List<SModel> modelsToOptimize = solution.getModels(); Result result = optimizeModelsImports_internal(modelsToOptimize, new EmptyProgressMonitor()); result.myReport = optimizeModuleImports(solution, result, Collections.<SModuleReference>emptySet()) + "\n\n" + result.myReport; return result; } private Result optimizeLanguageImports_internal(Language language) { List<SModel> modelsToOptimize = new ArrayList<SModel>(); for (SModel model : language.getModels()) { if (SModelStereotype.isDescriptorModelStereotype(SModelStereotype.getStereotype(model)) || SModelStereotype.isStubModel(model)) { // with @desciptor model hosting imports to activate generation of custom aspects, it's not wise to remove these. // stub models aren't best candidates for organize imports, too. continue; } modelsToOptimize.add(model); } for (Generator g : language.getGenerators()) { modelsToOptimize.addAll(g.getModels()); } Result result = optimizeModelsImports_internal(modelsToOptimize, new EmptyProgressMonitor()); myRepository.saveAll(); for (Generator g : language.getGenerators()) { GeneratorModuleScanner gms = new GeneratorModuleScanner(); gms.walkPriorityRules(g); result.myReport = optimizeModuleImports(g, result, gms.getReferencedGenerators()) + "\n\n" + result.myReport; } result.myReport = optimizeModuleImports(language, result, Collections.<SModuleReference>emptySet()) + "\n\n" + result.myReport; return result; } private Result optimizeModelsImports_internal(List<SModel> modelsToOptimize, ProgressMonitor monitor) { Result result = new Result(); monitor.start("working", modelsToOptimize.size()); try { for (SModel model : modelsToOptimize) { monitor.step(model.toString()); if (SModelStereotype.isStubModel(model)) { // todo: looks like WTF result.add(collectModelDependencies(model)); } else { result.add(optimizeModelImports_internal(model)); } monitor.advance(1); if (monitor.isCanceled()) { return result; } } } finally { monitor.done(); } return result; } private Result optimizeModelImports_internal(SModel modelDescriptor) { Result result = collectModelDependencies(modelDescriptor); Set<SModelReference> unusedModels = new HashSet<SModelReference>(); for (SModelReference model : SModelOperations.getImportedModelUIDs(modelDescriptor)) { if (result.myUsedModels.contains(model)) continue; // FIXME //this is a temp code to fix http://youtrack.jetbrains.com/issue/MPS-19621 //we should re-save models and make them findModules through modules, not just by ID //this code is supposed to be deleted after 3.1 release SModel md = model.resolve(myRepository); if (md == null) continue; if (result.myUsedModels.contains(md.getReference())) continue; //end of tmp code unusedModels.add(model); } Set<SLanguage> unusedLanguages = new HashSet<SLanguage>(); for (SLanguage languageRef : ((jetbrains.mps.smodel.SModelInternal) modelDescriptor).importedLanguageIds()) { if (isUnusedLanguageRef(result, languageRef)) { unusedLanguages.add(languageRef); } } Set<SModuleReference> unusedDevkits = new HashSet<SModuleReference>(); for (SModuleReference devkitRef : ((jetbrains.mps.smodel.SModelInternal) modelDescriptor).importedDevkits()) { if (ModelsAutoImportsManager.getDevKits(modelDescriptor.getModule(), modelDescriptor).contains(devkitRef)) { continue; } if (isUnusedDevkitRef(result, devkitRef)) { unusedDevkits.add(devkitRef); } } result.myReport = removeFromImports(modelDescriptor, unusedModels, unusedLanguages, unusedDevkits); return result; } private Result collectModelDependencies(SModel model) { Result result = new Result(); /* FIXME how come we take engaged languages into account as 'used'. I'd rather demand them explicitly added as 'used', rather than implicitly taken from 'engaged' result.myUsedLanguages.addAll(((jetbrains.mps.smodel.SModelInternal) model).engagedOnGenerationLanguages()); */ ModelDependencyScanner modelScanner = new ModelDependencyScanner().crossModelReferences(true).usedLanguages(true).walk(model); result.myUsedLanguages.addAll(modelScanner.getUsedLanguages()); result.myUsedModels.addAll(modelScanner.getCrossModelReferences()); // add auto imports as dependencies for (SLanguage l : ModelsAutoImportsManager.getLanguages(model.getModule(), model)) { result.myUsedLanguages.add(l); } for (SModel m : ModelsAutoImportsManager.getAutoImportedModels(model.getModule(), model)) { result.addUsedModel(m.getReference()); } return result; } //----additional methods-------- private String optimizeModuleImports(AbstractModule module, Result result, Collection<SModuleReference> toKeep) { List<Dependency> unusedDeps = new ArrayList<Dependency>(); final SModuleReference optimizedModuleReference = module.getModuleReference(); HashSet<SModuleReference> inUse = new HashSet<SModuleReference>(toKeep); SRepository repository = module.getRepository(); // from used models, find out modules we need for (SModelReference mr : result.myUsedModels) { SModuleReference moduleInUse = mr.getModuleReference(); if (moduleInUse == null) { if (repository != null) { SModel model = mr.resolve(repository); if (model != null && model.getModule() != null) { inUse.add(model.getModule().getModuleReference()); } } } else { inUse.add(moduleInUse); } } for (Dependency d : module.getModuleDescriptor().getDependencies()) { if (d.getModuleRef().equals(optimizedModuleReference)) { unusedDeps.add(d); continue; } if (inUse.contains(d.getModuleRef())) { continue; } unusedDeps.add(d); } return removeFromImports(module, unusedDeps); } private boolean isUnusedDevkitRef(Result result, SModuleReference devkitRef) { DevKit dk = ((DevKit) devkitRef.resolve(myRepository)); if (dk == null) { return false; } for (SLanguage lang : dk.getAllExportedLanguageIds()) { if (!isUnusedLanguageRef(result, lang)) { return false; } } for (Solution solution : dk.getAllExportedSolutions()) { for (SModel model : solution.getModels()) { if (result.myUsedModels.contains(model.getReference())) { return false; } } } return true; } private boolean isUnusedLanguageRef(Result result, SLanguage languageRef) { if (result.myUsedLanguages.contains(languageRef)) { return false; } final SModule sourceModule = languageRef.getSourceModule(); if (sourceModule instanceof Language) { for (SModel md : ((Language) sourceModule).getAccessoryModels()) { if (result.myUsedModels.contains(md.getReference())) return false; } } return true; } private String removeFromImports(SModel modelDescriptor, Set<SModelReference> unusedModels, Set<SLanguage> unusedLanguages, Set<SModuleReference> unusedDevkits) { StringBuilder report = new StringBuilder("Import for model " + modelDescriptor.getReference() + " were optimized \n"); for (SLanguage langRef : unusedLanguages) { ((jetbrains.mps.smodel.SModelInternal) modelDescriptor).deleteLanguageId(langRef); report.append("Language ").append(langRef.getQualifiedName()).append(" was removed from imports\n"); } for (SModuleReference dkRef : unusedDevkits) { ((jetbrains.mps.smodel.SModelInternal) modelDescriptor).deleteDevKit(dkRef); report.append("Devkit ").append(dkRef.getModuleName()).append(" was removed from imports\n"); } for (SModelReference model : unusedModels) { ((jetbrains.mps.smodel.SModelInternal) modelDescriptor).deleteModelImport(model); report.append("Model ").append(model.getModelName()).append(" was removed from imports\n"); } return report.toString(); } private String removeFromImports(AbstractModule module, List<Dependency> unusedDeps) { StringBuilder report = new StringBuilder("Import for module " + module.getModuleName() + " were optimized \n"); for (Dependency dep : unusedDeps) { module.removeDependency(dep); report.append("Dependency on ").append(dep.getModuleRef().getModuleName()).append(" was removed\n"); } return report.toString(); } private static class Result { public String myReport = ""; public final Set<SLanguage> myUsedLanguages = new HashSet<SLanguage>(); public final Set<SModelReference> myUsedModels = new HashSet<SModelReference>(); public void add(Result addition) { myReport = myReport + addition.myReport + "\n"; myUsedLanguages.addAll(addition.myUsedLanguages); myUsedModels.addAll(addition.myUsedModels); } public void addUsedModel(SModelReference ref) { if (ref == null) { // todo: ? can be in case of DynamicReference in stubs return; } myUsedModels.add(ref); } } }