/* * Copyright 2003-2017 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.ide.ui.dialogs.properties; import com.intellij.icons.AllIcons; import com.intellij.ide.util.BrowseFilesListener; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.fileChooser.FileChooserFactory; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.EmptyRunnable; import com.intellij.ui.AnActionButton; import com.intellij.ui.AnActionButtonRunnable; import com.intellij.ui.CheckboxTree; import com.intellij.ui.CheckboxTreeBase.CheckPolicy; import com.intellij.ui.CheckedTreeNode; import com.intellij.ui.FieldPanel; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.InsertPathAction; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.SpeedSearchComparator; import com.intellij.ui.ToolbarDecorator; import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBLabel; import com.intellij.ui.table.JBTable; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; import com.intellij.util.ui.AbstractTableCellEditor; import com.intellij.util.ui.ItemRemovable; import com.intellij.util.ui.JBUI; import jetbrains.mps.extapi.module.ModuleFacetBase; import jetbrains.mps.findUsages.CompositeFinder; import jetbrains.mps.generator.impl.plan.ModelScanner; import jetbrains.mps.icons.MPSIcons.General; import jetbrains.mps.ide.findusages.model.IResultProvider; import jetbrains.mps.ide.findusages.model.SearchQuery; import jetbrains.mps.ide.findusages.model.holders.GenericHolder; import jetbrains.mps.ide.findusages.model.holders.ModelsHolder; import jetbrains.mps.ide.findusages.model.scopes.FindUsagesScope; import jetbrains.mps.ide.findusages.model.scopes.ModelsScope; import jetbrains.mps.ide.findusages.model.scopes.ModulesScope; import jetbrains.mps.ide.findusages.view.FindUtils; import jetbrains.mps.ide.generator.GenPlanPickPanel; import jetbrains.mps.ide.icons.IdeIcons; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.ide.ui.dialogs.properties.choosers.CommonChoosers; import jetbrains.mps.ide.ui.dialogs.properties.creators.ModelChooser; import jetbrains.mps.ide.ui.dialogs.properties.editors.RuleTypeEditor; import jetbrains.mps.ide.ui.dialogs.properties.genpriorities.GeneratorPrioritiesTree; import jetbrains.mps.ide.ui.dialogs.properties.input.ModuleCollector; import jetbrains.mps.ide.ui.dialogs.properties.renderers.RuleTypeRenderer; import jetbrains.mps.ide.ui.dialogs.properties.renders.DependencyCellState; import jetbrains.mps.ide.ui.dialogs.properties.renders.ModelTableCellRender; import jetbrains.mps.ide.ui.dialogs.properties.renders.ModuleTableCellRender; import jetbrains.mps.ide.ui.dialogs.properties.roots.editors.ModelRootContentEntriesEditor; import jetbrains.mps.ide.ui.dialogs.properties.tables.items.DependenciesTableItem; import jetbrains.mps.ide.ui.dialogs.properties.tables.items.DependenciesTableItem.ModuleType; import jetbrains.mps.ide.ui.dialogs.properties.tables.models.DependTableModel; import jetbrains.mps.ide.ui.dialogs.properties.tables.models.ModuleDependTableModel; import jetbrains.mps.ide.ui.dialogs.properties.tables.models.UsedLangsTableModel; import jetbrains.mps.ide.ui.dialogs.properties.tabs.BaseTab; import jetbrains.mps.ide.ui.finders.LanguageModelImportFinder; import jetbrains.mps.ide.ui.finders.LanguageUsagesFinder; import jetbrains.mps.ide.ui.finders.ModelUsagesFinder; import jetbrains.mps.ide.ui.finders.ModuleUsagesFinder; import jetbrains.mps.project.AbstractModule; import jetbrains.mps.project.DevKit; import jetbrains.mps.project.MPSProject; import jetbrains.mps.project.ModuleInstanceCondition; import jetbrains.mps.project.ProjectPathUtil; import jetbrains.mps.project.Solution; import jetbrains.mps.project.VisibleModuleCondition; import jetbrains.mps.project.dependency.GeneratorModuleScanner; import jetbrains.mps.project.structure.modules.Dependency; import jetbrains.mps.project.structure.modules.DevkitDescriptor; import jetbrains.mps.project.structure.modules.GeneratorDescriptor; import jetbrains.mps.project.structure.modules.LanguageDescriptor; import jetbrains.mps.project.structure.modules.ModuleDescriptor; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_AbstractRef; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_ExternalRef; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_RefSet; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingPriorityRule; import jetbrains.mps.project.structure.modules.mappingpriorities.RuleType; import jetbrains.mps.smodel.ConceptDeclarationScanner; import jetbrains.mps.smodel.DefaultScope; import jetbrains.mps.smodel.Generator; import jetbrains.mps.smodel.Language; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.smodel.ModelDependencyScanner; import jetbrains.mps.util.Computable; import jetbrains.mps.util.ComputeRunnable; import jetbrains.mps.util.ConditionalIterable; import jetbrains.mps.util.EqualUtil; import jetbrains.mps.util.FileUtil; import jetbrains.mps.util.ModelComputeRunnable; import jetbrains.mps.util.Pair; import jetbrains.mps.util.ToStringComparator; import jetbrains.mps.util.annotation.ToRemove; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; 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.FacetsFacade; import org.jetbrains.mps.openapi.module.SDependencyScope; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SModuleFacet; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.module.SearchScope; import org.jetbrains.mps.openapi.ui.Modifiable; import org.jetbrains.mps.openapi.ui.persistence.Tab; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.DefaultCellEditor; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SpinnerNumberModel; import javax.swing.event.ChangeEvent; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.text.DefaultFormatter; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EventObject; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; public class ModulePropertiesConfigurable extends MPSPropertiesConfigurable { private final ModuleDescriptor myModuleDescriptor; private AbstractModule myModule; /* * Generally module's repository would be the same as the project's. * However, one of possible repository story evolution scenario suggests deployed modules could * belong to a different repository than the project's, hence it's better to record actual one. * XXX is it possible that module comes here not attached to a repo? */ private final SRepository myModuleRepository; private final List<FacetCheckBox> myCheckBoxes = new ArrayList<FacetCheckBox>(); private final FacetTabsPersistence myFacetTabsPersistence; // We are tightly coupled with IDEA IDE here, no reason to be shy about project kind. public ModulePropertiesConfigurable(SModule module, MPSProject project) { super(project); // XXX for whatever reason, it looks like we are not inside read although passing SModule here (e.g. ModuleProperties_Action doesn't bother to get one). Why? // Same for ModelPropertiesConfigurable, btw. // For scenario when module comes not from the project's repo, use of GetModuleRepo looks odd as we lock project repo // to get data of a module from a different repository (although one can pretend that locking project repo locks all dependency repositories as well). myModuleRepository = new ModelComputeRunnable<>(new GetModuleRepo(module)).runRead(project.getModelAccess()); myModule = (AbstractModule) module; myModuleDescriptor = myModule.getModuleDescriptor(); myFacetTabsPersistence = new FacetTabsPersistence(project).initFromEP(); registerTabs(new ModuleCommonTab()); if (!(myModule instanceof DevKit)) { final ModuleDependenciesTab moduleDependenciesTab = new ModuleDependenciesTab(); registerTabs(moduleDependenciesTab, new ModuleUsedLanguagesTab()); if (myModule instanceof Language) { registerTabs(new RuntimeTab()); } if (myModule instanceof Generator) { registerTabs(new GeneratorAdvancesTab((Generator) myModule, new GeneratorDependencyProvider(moduleDependenciesTab))); } } for (SModuleFacet moduleFacet : myModule.getFacets()) { Tab facetTab = myFacetTabsPersistence.getFacetTab(moduleFacet); if (facetTab != null) { registerTabs(facetTab); } } registerTabs(new AddFacetsTab()); } @Override protected void save() { // let facet instances serialize their data into facet descriptors. Would be better to do that for // changed (Tab.isModified()) facets only, but there's no (easy?) way to figure out module facet from a tab, thus // we save all module facets with active descriptors (it's AddFacetTab#apply() responsibility to add facet descriptors // for newly added facets, and to remove descriptors for unchecked facets. This sharing is questionable, perhaps, could do both here). for (SModuleFacet moduleFacet : myModule.getFacets()) { myModuleDescriptor.updateFacetDescriptor(moduleFacet); } // todo: !!! myModule.setModuleDescriptor(myModuleDescriptor); //In case of Generator saving lead to reload of containing Language //As result Language unload old Generator module and creates new - so we need to update object // FIXME why on Earth do we set module descriptor to a module?! Is there better way to tell module to refresh its settings???? //TODO: remove when generator will be separated from language if (myModule instanceof Generator) { myModule = (AbstractModule) myModuleDescriptor.getModuleReference().resolve(myModuleRepository); } myModule.save(); } @Nls @Override public String getDisplayName() { return String.format(PropertiesBundle.message("module.title"), myModule.getClass().getSimpleName(), myModule.getModuleName()); } private FindUsagesScope getModuleAndOwnedModelsScope() { return new ModelAccessHelper(myProject.getModelAccess()).runReadAction(new Computable<FindUsagesScope>() { @Override public FindUsagesScope compute() { final ModulesScope rv = new ModulesScope(myModule); rv.resolveRespectsAllVisible(true); return rv; } }); } /*package*/ void findModuleUsages(List<SModuleReference> modules) { final SearchQuery query = new SearchQuery(new GenericHolder<Object>(modules), getModuleAndOwnedModelsScope()); final IResultProvider provider = FindUtils.makeProvider(new CompositeFinder(new ModuleUsagesFinder())); showUsageImpl(query, provider); forceCancelCloseDialog(); } /*package*/ void findModelUsages(List<SModelReference> models) { final SearchQuery query = new SearchQuery(new ModelsHolder(models), getModuleAndOwnedModelsScope()); final IResultProvider provider = FindUtils.makeProvider(new CompositeFinder(new ModelUsagesFinder())); showUsageImpl(query, provider); forceCancelCloseDialog(); } /*package*/ void findLanguageUsages(List<SLanguage> languages) { final SearchQuery query = new SearchQuery(new GenericHolder<Collection<SLanguage>>(languages, "Languages"), new ModelsScope(myModule.getModels())); final IResultProvider provider = FindUtils.makeProvider(new CompositeFinder(new LanguageModelImportFinder()), new CompositeFinder(new LanguageUsagesFinder())); showUsageImpl(query, provider); forceCancelCloseDialog(); } public class ModuleCommonTab extends CommonTab { private ModuleDependenciesTab myModuleDependenciesTab; private ModelRootContentEntriesEditor myEntriesEditor; private JTextField myGenOut; private JSpinner myLanguageVersion; private JSpinner myModuleVersion; private DefaultScope myPlanPickScope; private GenPlanPickPanel myPlanPanel; @Override protected String getConfigItemName() { return myModuleDescriptor.getNamespace(); } @Override protected String getConfigItemPath() { if (myModule.getDescriptorFile() == null) { return ""; } else { return FileUtil.getCanonicalPath(myModule.getDescriptorFile().getPath()); } } @Override protected JComponent getBottomComponent() { if (myModule instanceof DevKit) { myModuleDependenciesTab = new ModuleDependenciesTab(); myModuleDependenciesTab.init(); // init to avoid myModuleDependenciesTab.getTabComponent() == null return myModuleDependenciesTab.getTabComponent(); } else { myEntriesEditor = new ModelRootContentEntriesEditor(myModuleDescriptor, myProject.getRepository()); Disposer.register(ModulePropertiesConfigurable.this, myEntriesEditor); return myEntriesEditor.getComponent(); } } //null=not supported @Nullable protected Integer getLanguageVersion() { if (!(myModule instanceof Language)) return null; return ((Language) myModule).getLanguageVersion(); } @Nullable protected Integer getModuleVersion() { if (myModule instanceof DevKit) return null; return myModule.getModuleVersion(); } @Override protected JComponent getTopComponent() { if (myModule instanceof Language || myModule instanceof Solution) { boolean hasLanguageVersion = getLanguageVersion() != null; boolean hasModuleVersion = getModuleVersion() != null; JPanel panel = new JPanel(); panel.setLayout(new GridLayoutManager(1 + (hasLanguageVersion ? 1 : 0) + (hasModuleVersion ? 1 : 0), 2, JBUI.emptyInsets(), -1, -1)); int row = 0; JBLabel label = new JBLabel(PropertiesBundle.message("module.genoutput.title")); panel.add(label, new GridConstraints(row, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); myGenOut = new JTextField(); final FileChooserDescriptor outputPathsChooserDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor(); InsertPathAction.addTo(myGenOut, outputPathsChooserDescriptor); outputPathsChooserDescriptor.setHideIgnored(false); BrowseFilesListener listener = new BrowseFilesListener(myGenOut, "", "", outputPathsChooserDescriptor); FieldPanel genOutPath = new FieldPanel(myGenOut, null, null, listener, EmptyRunnable.getInstance()); FileChooserFactory.getInstance().installFileCompletion(genOutPath.getTextField(), outputPathsChooserDescriptor, true, null); genOutPath.setText(getGenOutPath()); panel.add(genOutPath, new GridConstraints(row++, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); if (hasLanguageVersion) { JLabel verLabel = new JBLabel(PropertiesBundle.message("mps.properties.configurable.language.version")); panel.add(verLabel, new GridConstraints(row, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); myLanguageVersion = new JSpinner(new SpinnerNumberModel((int) getLanguageVersion(), 0, getLanguageVersion() + 10000, 1)); JSpinner.NumberEditor jsEditor = (JSpinner.NumberEditor) myLanguageVersion.getEditor(); DefaultFormatter formatter = (DefaultFormatter) jsEditor.getTextField().getFormatter(); formatter.setAllowsInvalid(false); panel.add(myLanguageVersion, new GridConstraints(row++, 1, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(30, -1), null, 0, false)); } if (hasModuleVersion) { JLabel verLabel = new JBLabel(PropertiesBundle.message("mps.properties.configurable.module.version")); panel.add(verLabel, new GridConstraints(row, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); myModuleVersion = new JSpinner(new SpinnerNumberModel((int) getModuleVersion(), 0, getModuleVersion() + 10000, 1)); JSpinner.NumberEditor jsEditor = (JSpinner.NumberEditor) myModuleVersion.getEditor(); DefaultFormatter formatter = (DefaultFormatter) jsEditor.getTextField().getFormatter(); formatter.setAllowsInvalid(false); panel.add(myModuleVersion, new GridConstraints(row++, 1, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(30, -1), null, 0, false)); } return panel; } else if (myModule instanceof DevKit) { myPlanPickScope = new DefaultScope() { @Override protected Set<SModule> getInitialModules() { return new HashSet<SModule>(((DevKit) myModule).getExportedSolutions()); } @Override protected Collection<Language> getInitialUsedLanguages() { return ((DevKit) myModule).getExportedLanguages(); } }; myPlanPanel = new GenPlanPickPanel(myProject, myPlanPickScope, "Generation plan for models using this devkit"); myPlanPanel.setPlanModel(((DevkitDescriptor) myModuleDescriptor).getAssociatedGenPlan()); return myPlanPanel; } return null; } private String getGenOutPath() { String outputDir = ProjectPathUtil.getGeneratorOutputPath(myModuleDescriptor); return outputDir != null ? FileUtil.getCanonicalPath(outputDir) : ""; } @Override public boolean isModified() { if (super.isModified()) { return true; } if (myModule instanceof DevKit) { if (myModuleDependenciesTab.isModified()) { return true; } SModelReference currentPlan = ((DevkitDescriptor) myModuleDescriptor).getAssociatedGenPlan(); final SModelReference uiPlanValue = myPlanPanel.getPlanModel(); if (currentPlan == null ? uiPlanValue != null : !currentPlan.equals(uiPlanValue)) { return true; } // fall-through } if (!(myModule instanceof DevKit) && myEntriesEditor.isModified()) { return true; } if (myGenOut != null && !(myGenOut.getText().equals(getGenOutPath()))) { return true; } if (myLanguageVersion != null) { try { int newLanguageVersion = ((Integer) myLanguageVersion.getValue()); if (!EqualUtil.equals(newLanguageVersion, getLanguageVersion())) return true; } catch (NumberFormatException e) { //just continue omitting this field } } if (myModuleVersion != null) { try { int newModuleVersion = ((Integer) myModuleVersion.getValue()); if (!EqualUtil.equals(newModuleVersion, getModuleVersion())) return true; } catch (NumberFormatException e) { //just continue omitting this field } } return false; } @Override public void apply() { if (super.isModified()) { myModuleDescriptor.setNamespace(myTextFieldName.getText()); } if (myModuleDescriptor instanceof DevkitDescriptor) { myModuleDependenciesTab.apply(); ((DevkitDescriptor) myModuleDescriptor).setAssociatedPlan(myPlanPanel.getPlanModel()); myPlanPickScope.invalidateCaches(); } else { if (myGenOut != null && !(myGenOut.getText().equals(getGenOutPath()))) { // here we imply getGenOutPath uses ProjectPathUtil.getGeneratorOutputPath ProjectPathUtil.setGeneratorOutputPath(myModuleDescriptor, myGenOut.getText()); } if (myLanguageVersion != null) { try { int newLanguageVersion = ((Integer) myLanguageVersion.getValue()); ((Language) myModule).setLanguageVersion(newLanguageVersion); } catch (NumberFormatException e) { //just continue omitting this field } } if (myModuleVersion != null) { try { int newModuleVersion = ((Integer) myModuleVersion.getValue()); myModule.setModuleVersion(newModuleVersion); } catch (NumberFormatException e) { //just continue omitting this field } } myEntriesEditor.apply(); } } } public class ModuleDependenciesTab extends DependenciesTab { /*package*/ List<DependenciesTableItem> getActualDependencies() { int x = myDependTableModel.getRowCount(); ArrayList<DependenciesTableItem> rv = new ArrayList<DependenciesTableItem>(x); for (int i = 0; i < x; i++) { rv.add(myDependTableModel.getValueAt(i)); } return rv; } @Override protected DependTableModel getDependTableModel() { final ModuleDependTableModel rv = new ModuleDependTableModel(myProject.getRepository(), myModuleDescriptor); rv.init(); return rv; } @Override protected TableCellEditor getTableCellEditor() { return new DependenciesTableCellEditor(); } @Override protected AnActionButtonRunnable getAnActionButtonRunnable() { return new AnActionButtonRunnable() { @Override public void run(AnActionButton anActionButton) { final boolean isDevkit = myModule instanceof DevKit; Iterable<SModule> selectionSet = getProjectModules(); if (isDevkit) { selectionSet = new ConditionalIterable<SModule>(selectionSet, new VisibleModuleCondition()); } ComputeRunnable<List<SModuleReference>> c = new ComputeRunnable<List<SModuleReference>>(new ModuleCollector(selectionSet)); myProject.getModelAccess().runReadAction(c); final String dialogTitle = isDevkit ? "Choose DevKit contents" : "Choose modules"; final List<SModuleReference> list = CommonChoosers.showModuleSetChooser(myProject, dialogTitle, c.getResult()); if (list.isEmpty()) { return; } myProject.getModelAccess().runReadAction(new Runnable() { @Override public void run() { for (SModuleReference moduleReference : list) { final SModule module = moduleReference.resolve(myProject.getRepository()); final Dependency dep; if (isDevkit) { dep = new Dependency(moduleReference, SDependencyScope.EXTENDS); } else { dep = new Dependency(moduleReference, SDependencyScope.DEFAULT, false); } if (module instanceof Language) { myDependTableModel.addLanguageItem(dep); } else if (module instanceof Generator) { myDependTableModel.addGeneratorItem(dep); } else if (module instanceof Solution) { myDependTableModel.addSolutionItem(dep); } else if (module instanceof DevKit) { myDependTableModel.addDevkitItem(dep); } else { myDependTableModel.addUnspecifiedItem(dep); } } // foreach moduleReference } }); } }; } @Override protected TableCellRenderer getTableCellRender() { final ModuleTableCellRender mtcr = new ModuleTableCellRender(myModuleRepository); final HashSet<SModuleReference> extendsSet = new HashSet<>(); final HashSet<SModuleReference> generationTargets = new HashSet<>(); final HashSet<SModuleReference> xModuleSet = new HashSet<>(); myModuleRepository.getModelAccess().runReadAction(new Runnable() { @Override public void run() { // XXX perhaps, worth adding ModuleProperties data collection (much like ModelProperties) if (myModule instanceof Language) { SModel structureAspect = ((Language) myModule).getStructureModelDescriptor(); if (structureAspect != null) { // we keep lang.core.structure reference, if any, just not to warn about superfluous lang.core import ConceptDeclarationScanner cds = new ConceptDeclarationScanner(); cds.scan(structureAspect); cds.getDependencyModules().forEach(m -> extendsSet.add(m.getModuleReference())); } ModelScanner tms = new ModelScanner(); for (Generator g : ((Language) myModule).getGenerators()) { g.getOwnTemplateModels().forEach(tms::scan); } tms.getTargetLanguages().forEach(l -> generationTargets.add(l.getSourceModuleReference())); } // collect target modules of cross-model references ModelDependencyScanner mds = new ModelDependencyScanner().usedLanguages(false).crossModelReferences(true).usedConcepts(false); myModule.getModels().forEach(mds::walk); SearchScope moduleScope = myModule.getScope(); for (SModelReference xRef : mds.getCrossModelReferences()) { SModel xModel = moduleScope.resolve(xRef); if (xModel != null) { xModuleSet.add(xModel.getModule().getModuleReference()); } else if (xRef.getModuleReference() != null) { xModuleSet.add(xRef.getModuleReference()); } // bad luck, reference to a model from unknown module, no idea what to do } if (myModule instanceof Generator) { GeneratorModuleScanner gms = new GeneratorModuleScanner(); gms.walkPriorityRules((Generator) myModule); xModuleSet.addAll(gms.getReferencedGenerators()); } } }); mtcr.addCellState(Objects::isNull, DependencyCellState.NOT_AVAILABLE); if (myModule instanceof Language) { // XXX would be great to report superfluous extends for generators as well (populate extendSet from template model scanner // that knows what constitutes 'extends' between generators. The main problem is nobody knows how to tell generators are truly // in 'extends' relation. mtcr.addCellState(moduleImport -> { SModuleReference importRef = moduleImport.getModuleReference(); // XXX not quite nice as the same module may be imported twice, as a regular dependency as well as 'extends', // here we don't tell one from another and warn both. Would be better to refactor StateTableCellRenderer to give more // info about table row (access to underlying table value?) boolean isExtendsDep = ((ModuleDependTableModel) myDependTableModel).getExtendedModules().contains(importRef); return isExtendsDep && !extendsSet.contains(moduleImport.getModuleReference()); }, DependencyCellState.SUPERFLUOUS_EXTENDS); } mtcr.addCellState(moduleImport -> !generationTargets.contains(moduleImport.getModuleReference()) && !xModuleSet.contains(moduleImport.getModuleReference()), DependencyCellState.UNUSED); return mtcr; } @Nullable @Override protected FindActionButton getFindAnAction(JBTable table) { return new FindActionButton(table) { @Override public void actionPerformed(AnActionEvent e) { List<SModuleReference> modules = new ArrayList<SModuleReference>(); for (int i : myTable.getSelectedRows()) { final DependenciesTableItem valueAt = myDependTableModel.getValueAt(i); modules.add(valueAt.getItem().getModuleRef()); } findModuleUsages(modules); } }; } private class DependenciesTableCellEditor extends DefaultCellEditor { public DependenciesTableCellEditor() { super(new JComboBox()); } @Override public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, int row, int column) { final JComboBox combo = (JComboBox) super.getTableCellEditorComponent(table, value, isSelected, row, column); combo.removeAllItems(); if (row < 0 || row >= table.getModel().getRowCount()) { return combo; } DependenciesTableItem rowItem = myDependTableModel.getValueAt(row); List items = getItemsForCell(rowItem); for (Object o : items) { combo.addItem(o); } combo.setSelectedItem(rowItem.getItem().getScope()); // MPS-22987 As we don't know height of editor before creation, we need to update row height and return it back after final int tableRowHeight = table.getRowHeight(); table.setRowHeight(row, (int)combo.getPreferredSize().getHeight()); combo.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) {} @Override public void focusLost(FocusEvent e) { table.setRowHeight(tableRowHeight); combo.removeFocusListener(this); } }); return combo; } private List<SDependencyScope> getItemsForCell(DependenciesTableItem item) { List<SDependencyScope> scopes = new ArrayList<SDependencyScope>(5); scopes.add(SDependencyScope.DEFAULT); if (isLangToLang(item)) { scopes.add(SDependencyScope.EXTENDS); scopes.add(SDependencyScope.GENERATES_INTO); } if (isGenToGen(item)) { scopes.add(SDependencyScope.EXTENDS); // DESIGN dependencies between generators allows use of referenced generators in priority rules without // imposing any run-time dependency between generators. scopes.add(SDependencyScope.DESIGN); } return scopes; } private boolean isLangToLang(DependenciesTableItem item) { return myDependTableModel.getSource() instanceof LanguageDescriptor && item.getModuleType().equals(ModuleType.LANGUAGE); } private boolean isGenToGen(DependenciesTableItem item) { return myDependTableModel.getSource() instanceof GeneratorDescriptor && item.getModuleType().equals(ModuleType.GENERATOR); } } } public class RuntimeTab extends BaseTab { private RuntimeTableModel myRuntimeTableModel; private AccessoriesModelsTableModel myAccessoriesModelsTableModel; public RuntimeTab() { super(PropertiesBundle.message("mps.properties.runtime.title"), General.Runtime, PropertiesBundle.message("mps.properties.runtime.tip")); } @Override public void apply() { myRuntimeTableModel.apply(); myAccessoriesModelsTableModel.apply(); } @Override public void init() { JPanel usedLangsTab = new JPanel(); usedLangsTab.setLayout(new GridLayoutManager(2, 1, INSETS, -1, -1)); final JBTable runtimeTable = new JBTable(); runtimeTable.setShowHorizontalLines(false); runtimeTable.setShowVerticalLines(false); runtimeTable.setAutoCreateRowSorter(false); runtimeTable.setAutoscrolls(true); runtimeTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); myRuntimeTableModel = new RuntimeTableModel(); myRuntimeTableModel.init(); runtimeTable.setModel(myRuntimeTableModel); runtimeTable.setDefaultRenderer(SModuleReference.class, new ModuleTableCellRender(myModuleRepository)); ToolbarDecorator decorator = ToolbarDecorator.createDecorator(runtimeTable); decorator.setAddAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton anActionButton) { Iterable<SModule> modules = new ConditionalIterable<SModule>(getProjectModules(), new ModuleInstanceCondition(Solution.class)); modules = new ConditionalIterable<SModule>(modules, new VisibleModuleCondition()); ComputeRunnable<List<SModuleReference>> c = new ComputeRunnable<List<SModuleReference>>(new ModuleCollector(modules)); myProject.getModelAccess().runReadAction(c); List<SModuleReference> list = CommonChoosers.showModuleSetChooser(myProject, "Choose solutions", c.getResult()); for (SModuleReference reference : list) { myRuntimeTableModel.addItem(reference); } } }).setRemoveAction(new RemoveEntryAction(runtimeTable)).addExtraAction(new FindActionButton(runtimeTable) { @Override public void actionPerformed(AnActionEvent e) { List<SModuleReference> modules = new ArrayList<SModuleReference>(); for (int row : runtimeTable.getSelectedRows()) { modules.add(myRuntimeTableModel.getValueAt(row)); } findModuleUsages(modules); } }); decorator.setPreferredSize(new Dimension(500, 150)); JPanel table = decorator.createPanel(); table.setBorder(IdeBorderFactory.createBorder()); usedLangsTab.add(table, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); new TableColumnSearch(runtimeTable, 0).setComparator(new SpeedSearchComparator(false, true)); //--------------------------------------------- final JBTable accessoriesTable = new JBTable(); accessoriesTable.setShowHorizontalLines(false); accessoriesTable.setShowVerticalLines(false); accessoriesTable.setAutoCreateRowSorter(false); accessoriesTable.setAutoscrolls(true); accessoriesTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); myAccessoriesModelsTableModel = new AccessoriesModelsTableModel(); myAccessoriesModelsTableModel.init(); accessoriesTable.setModel(myAccessoriesModelsTableModel); accessoriesTable.setDefaultRenderer(SModelReference.class, new ModelTableCellRender(myProject.getRepository())); ToolbarDecorator decoratorForAccessories = ToolbarDecorator.createDecorator(accessoriesTable); decoratorForAccessories.setAddAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton anActionButton) { List<SModelReference> list = new ModelChooser(myProject).compute(); for (SModelReference reference : list) { myAccessoriesModelsTableModel.addItem(reference); } } }).setRemoveAction(new RemoveEntryAction(accessoriesTable)).addExtraAction(new FindActionButton(accessoriesTable) { @Override public void actionPerformed(AnActionEvent e) { List<SModelReference> models = new ArrayList<SModelReference>(); for (int row : accessoriesTable.getSelectedRows()) { models.add(myAccessoriesModelsTableModel.getValueAt(row)); } findModelUsages(models); } }); decoratorForAccessories.setPreferredSize(new Dimension(500, 150)); table = decoratorForAccessories.createPanel(); table.setBorder(IdeBorderFactory.createBorder()); usedLangsTab.add(table, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); new TableColumnSearch(accessoriesTable, 0).setComparator(new SpeedSearchComparator(false, true)); setTabComponent(usedLangsTab); } @Override public boolean isModified() { return myRuntimeTableModel.isModified(); } private class RuntimeTableModel extends AbstractTableModel implements ItemRemovable, Modifiable { private List<SModuleReference> myTableItems = new LinkedList<SModuleReference>(); public RuntimeTableModel() { } @Override public void init() { myTableItems.addAll(((LanguageDescriptor) myModuleDescriptor).getRuntimeModules()); } @Override public int getColumnCount() { return 1; } @Override public int getRowCount() { return myTableItems.size(); } public void addItem(SModuleReference moduleReference) { if (moduleReference == null || myTableItems.contains(moduleReference)) return; myTableItems.add(moduleReference); fireTableDataChanged(); } public SModuleReference getValueAt(int rowIndex) { return myTableItems.get(rowIndex); } @Override public Object getValueAt(int rowIndex, int columnIndex) { return this.getValueAt(rowIndex); } @Override public void removeRow(int idx) { myTableItems.remove(idx); } @Override public Class<?> getColumnClass(int columnIndex) { return SModuleReference.class; } @Override public boolean isModified() { LanguageDescriptor languageDescriptor = (LanguageDescriptor) myModuleDescriptor; return !(languageDescriptor.getRuntimeModules().containsAll(myTableItems) && myTableItems.containsAll(languageDescriptor.getRuntimeModules())); } @Override public String getColumnName(int column) { return PropertiesBundle.message("mps.properties.runtime.solutionstable.header"); } @Override public void apply() { LanguageDescriptor languageDescriptor = (LanguageDescriptor) myModuleDescriptor; languageDescriptor.getRuntimeModules().clear(); languageDescriptor.getRuntimeModules().addAll(myTableItems); } } private class AccessoriesModelsTableModel extends AbstractTableModel implements ItemRemovable, Modifiable { private List<SModelReference> myTableItems = new LinkedList<SModelReference>(); public AccessoriesModelsTableModel() { } @Override public void init() { myTableItems.addAll(((LanguageDescriptor) myModuleDescriptor).getAccessoryModels()); } @Override public int getColumnCount() { return 1; } @Override public int getRowCount() { return myTableItems.size(); } public void addItem(SModelReference modelReference) { if (modelReference == null || myTableItems.contains(modelReference)) return; myTableItems.add(modelReference); fireTableDataChanged(); } public SModelReference getValueAt(int rowIndex) { return myTableItems.get(rowIndex); } @Override public Object getValueAt(int rowIndex, int columnIndex) { return this.getValueAt(rowIndex); } @Override public void removeRow(int idx) { myTableItems.remove(idx); } @Override public Class<?> getColumnClass(int columnIndex) { return SModelReference.class; } @Override public boolean isModified() { LanguageDescriptor languageDescriptor = (LanguageDescriptor) myModuleDescriptor; return !(languageDescriptor.getAccessoryModels().containsAll(getAccessoryModels()) && myTableItems.containsAll( languageDescriptor.getAccessoryModels())); } @Override public String getColumnName(int column) { return PropertiesBundle.message("mps.properties.runtime.accessorytable.header"); } private LinkedList<jetbrains.mps.smodel.SModelReference> getAccessoryModels() { LinkedList<jetbrains.mps.smodel.SModelReference> linkedList = new LinkedList<jetbrains.mps.smodel.SModelReference>(); for (SModelReference modelReference : myTableItems) linkedList.add((jetbrains.mps.smodel.SModelReference) modelReference); return linkedList; } @Override public void apply() { LanguageDescriptor languageDescriptor = (LanguageDescriptor) myModuleDescriptor; languageDescriptor.getAccessoryModels().clear(); languageDescriptor.getAccessoryModels().addAll(getAccessoryModels()); } } } public class ModuleUsedLanguagesTab extends UsedLanguagesTab { @Override protected UsedLangsTableModel getUsedLangsTableModel() { final List<SLanguage> usedLanguages = new ModelAccessHelper(myProject.getModelAccess()).runReadAction(new Computable<List<SLanguage>>() { @Override public List<SLanguage> compute() { return new ArrayList<SLanguage>(myModule.getUsedLanguages()); } }); final UsedLangsTableModel rv = new UsedLangsTableModel(myProject.getRepository()); Collections.sort(usedLanguages, new ToStringComparator()); rv.init(usedLanguages, Collections.<SModuleReference>emptySet()); return rv; } @Override protected ToolbarDecorator createToolbar(JBTable usedLangsTable) { ToolbarDecorator decorator = super.createToolbar(usedLangsTable); decorator.addExtraAction(new FindActionButton(usedLangsTable) { @Override public void actionPerformed(AnActionEvent e) { final List<SLanguage> languages = getSelectedLanguages(); findLanguageUsages(languages); } }); return decorator; } @Override public void apply() { // no-op } } /** * Supply set of accessible/visible generators, so that Advanced tab could utilize actual dependencies from ModuleDependenciesTab */ static class GeneratorDependencyProvider { private final ModuleDependenciesTab myDependenciesTab; GeneratorDependencyProvider(ModuleDependenciesTab dependenciesTab) { myDependenciesTab = dependenciesTab; } Set<SModuleReference> getGenerators() { final HashSet<SModuleReference> depGenerators = new LinkedHashSet<SModuleReference>(); for (DependenciesTableItem dependencyItem : myDependenciesTab.getActualDependencies()) { if (dependencyItem.getModuleType() == ModuleType.GENERATOR) { depGenerators.add(dependencyItem.getItem().getModuleRef()); } } return depGenerators; } } public class GeneratorAdvancesTab extends BaseTab { private final Generator myGenerator; private final GeneratorDependencyProvider myDepGenerators; private GenPrioritiesTableModel myPrioritiesTableModel; private JBCheckBox myGenerateTemplates; @ToRemove(version = 2017.2) private JBCheckBox myReflectiveQueries; private final Map<MappingConfig_AbstractRef, GeneratorPrioritiesTree> myMappings = new java.util.HashMap<>(); private JBTable myTable; public GeneratorAdvancesTab(Generator generator, GeneratorDependencyProvider depGenerators) { super(PropertiesBundle.message("module.generator.title"), IdeIcons.DEFAULT_ICON, PropertiesBundle.message("module.generator.tip")); myGenerator = generator; myDepGenerators = depGenerators; } @Override public void apply() { if (myTable.isEditing()) { myTable.getCellEditor().stopCellEditing(); } final GeneratorDescriptor genDescr = myGenerator.getModuleDescriptor(); genDescr.setGenerateTemplates(myGenerateTemplates.isSelected()); genDescr.setReflectiveQueries(myReflectiveQueries.isSelected()); myPrioritiesTableModel.apply(myDepGenerators); } @Override public void init() { JPanel panel = new JPanel(); panel.setLayout(new GridLayoutManager(2, 1, INSETS, -1, -1)); myTable = new JBTable(); myTable.setAutoscrolls(true); myTable.getTableHeader().setReorderingAllowed(false); myPrioritiesTableModel = new GenPrioritiesTableModel(myGenerator.getModuleDescriptor()); myTable.setModel(myPrioritiesTableModel); myTable.setDefaultRenderer(RuleType.class, new RuleTypeRenderer()); myTable.setDefaultEditor(RuleType.class, new RuleTypeEditor()); myTable.setDefaultRenderer(MappingConfig_AbstractRef.class, new TableCellRenderer() { private GeneratorPrioritiesTree myCurrentTree = null; @Override public Component getTableCellRendererComponent(final JTable table, Object value, boolean isSelected, boolean hasFocus, final int row, final int column) { if (value instanceof MappingConfig_AbstractRef) { MappingConfig_AbstractRef mapping = (MappingConfig_AbstractRef) value; myCurrentTree = new GeneratorPrioritiesTree(myProject.getRepository(), myGenerator, mapping, column == 0, myDepGenerators.getGenerators()); myMappings.put(mapping, myCurrentTree); CheckedTreeNode rootNode = (CheckedTreeNode) myCurrentTree.getTree().getModel().getRoot(); rootNode = column == 0 ? (CheckedTreeNode) rootNode.getFirstChild() : rootNode; allChildrenChecked(rootNode); noCheckedChildren(rootNode); CheckboxTree checkboxTree = new CheckboxTree(GeneratorPrioritiesTree.getCheckboxTreeCellRenderer(false), rootNode, new CheckPolicy(true, true, false, true)); checkboxTree.setRootVisible(true); GeneratorPrioritiesTree.expandAllRows(checkboxTree); table.setRowHeight( row, Math.max(checkboxTree.getPreferredSize().height + 10, table.getRowHeight(row)) ); checkboxTree.setBackground(isSelected && !hasFocus ? table.getSelectionBackground() : table.getBackground()); return checkboxTree; } return null; } private boolean allChildrenChecked(CheckedTreeNode node) { List<CheckedTreeNode> children = Collections.list(node.children()); boolean allChildrenChecked = true; for (int i = 0; i < children.size(); i++) { CheckedTreeNode child = children.get(i); if (!allChildrenChecked(child) || !child.isChecked()) { allChildrenChecked = false; } } if (allChildrenChecked && node.isChecked()) { for (int i = 0; i < children.size(); i++) { CheckedTreeNode child = children.get(i); node.remove(child); child.removeFromParent(); } } return allChildrenChecked; } private boolean noCheckedChildren(CheckedTreeNode node) { List<CheckedTreeNode> children = Collections.list(node.children()); for (int i = 0; i < children.size(); i++) { CheckedTreeNode child = children.get(i); if (noCheckedChildren(child) && !child.isChecked()) { node.remove(child); child.removeFromParent(); } } return node.isLeaf(); } }); myTable.setDefaultEditor(MappingConfig_AbstractRef.class, new AbstractTableCellEditor() { private GeneratorPrioritiesTree myCurrentTree = null; @Override public boolean isCellEditable(EventObject e) { return e != null && e instanceof MouseEvent && ((MouseEvent) e).getClickCount() >= 2; } @Override public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, int row, int column) { if (value instanceof MappingConfig_AbstractRef) { MappingConfig_AbstractRef mapping = (MappingConfig_AbstractRef) value; myCurrentTree = new GeneratorPrioritiesTree(myProject.getRepository(), myGenerator, mapping, column == 0, myDepGenerators.getGenerators()); final DialogWrapper dialogWrapper = new DialogWrapper(ProjectHelper.toIdeaProject(myProject)) { { setModal(true); init(); } @Nullable @Override protected JComponent createCenterPanel() { final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myCurrentTree.getTreePanel(), true); final Dimension preferredSize = myCurrentTree.getTreePanel().getPreferredSize(); if (preferredSize.getHeight() > 600) { preferredSize.setSize(preferredSize.getWidth(), 600); } scrollPane.setPreferredSize(preferredSize); return scrollPane; } @Nullable @Override public JComponent getPreferredFocusedComponent() { return myCurrentTree.getTreePanel(); } @Override protected void doOKAction() { table.editingStopped(new ChangeEvent(this)); table.revalidate(); table.repaint(); super.doOKAction(); } @Override public void doCancelAction() { //myCurrentTree = null; table.editingCanceled(new ChangeEvent(this)); super.doCancelAction(); } }; final Component tableCellRendererComponent = myTable.getCellRenderer(row, column).getTableCellRendererComponent(table, value, isSelected, true, row, column); tableCellRendererComponent.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { dialogWrapper.show(); } @Override public void focusLost(FocusEvent e) { tableCellRendererComponent.removeFocusListener(this); } }); return tableCellRendererComponent; } return null; } @Override public Object getCellEditorValue() { GeneratorPrioritiesTree result = myCurrentTree; myCurrentTree = null; return result != null ? result.getEditResult() : null; } }); TableColumn column = myTable.getColumnModel().getColumn(1); column.setMaxWidth(50); column.setMinWidth(column.getMaxWidth()); column.setResizable(false); ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTable); decorator.setAddAction(new AnActionButtonRunnable() { @Override public void run(AnActionButton anActionButton) { myPrioritiesTableModel.addItem(new MappingPriorityRule()); myPrioritiesTableModel.fireTableDataChanged(); } }).setRemoveAction(new RemoveEntryAction(myTable)); decorator.setToolbarBorder(IdeBorderFactory.createBorder()); decorator.setPreferredSize(new Dimension(500, 300)); panel.add(decorator.createPanel(), new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false)); final GeneratorDescriptor genDescr = myGenerator.getModuleDescriptor(); JPanel generationOptions = new JPanel(); generationOptions.setLayout(new FlowLayout(FlowLayout.LEFT)); myGenerateTemplates = new JBCheckBox(PropertiesBundle.message("module.generator.gentemplates.name"), genDescr.isGenerateTemplates()); myGenerateTemplates.setToolTipText(PropertiesBundle.message("module.generator.gentemplates.tip")); myReflectiveQueries = new JBCheckBox("Reflective queries"); myReflectiveQueries.setToolTipText("Invoke generated queries via reflection. Legacy option, scheduled for removal"); myReflectiveQueries.addItemListener(e -> { boolean isSelected = e.getStateChange() == ItemEvent.SELECTED; if (isSelected && !myGenerateTemplates.isSelected()) { myReflectiveQueries.setOpaque(true); myReflectiveQueries.setBackground(Color.yellow); } else { myReflectiveQueries.setOpaque(false); } }); generationOptions.add(myGenerateTemplates); generationOptions.add(myReflectiveQueries); myReflectiveQueries.setSelected(genDescr.isReflectiveQueries()); panel.add(generationOptions, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false)); setTabComponent(panel); } @Override public boolean isModified() { final GeneratorDescriptor genDescr = myGenerator.getModuleDescriptor(); final boolean b1 = genDescr.isGenerateTemplates(); final boolean b2 = genDescr.isReflectiveQueries(); return myPrioritiesTableModel.isModified() || myGenerateTemplates.isSelected() != b1 || myReflectiveQueries.isSelected() != b2; } } private static class GenPrioritiesTableModel extends AbstractTableModel implements ItemRemovable { private final GeneratorDescriptor myModuleDescriptor; private final List<MappingPriorityRule> myMappingPriorityRules = new LinkedList<>(); public GenPrioritiesTableModel(GeneratorDescriptor moduleDescriptor) { super(); myModuleDescriptor = moduleDescriptor; for (MappingPriorityRule rule : myModuleDescriptor.getPriorityRules()) myMappingPriorityRules.add(rule.copy()); } @Override public int getColumnCount() { return 3; } @Override public int getRowCount() { return myMappingPriorityRules.size(); } public void addItem(MappingPriorityRule mappingPriorityRule) { if (mappingPriorityRule != null) myMappingPriorityRules.add(mappingPriorityRule); } @Override public Object getValueAt(int rowIndex, int columnIndex) { MappingPriorityRule rule = myMappingPriorityRules.get(rowIndex); if (columnIndex == 0) return rule.getLeft(); if (columnIndex == 1) return rule.getType(); if (columnIndex == 2) return rule.getRight(); return null; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { MappingPriorityRule rule = myMappingPriorityRules.get(rowIndex); if (columnIndex == 0 && aValue instanceof MappingConfig_AbstractRef) rule.setLeft((MappingConfig_AbstractRef) aValue); if (columnIndex == 1 && aValue instanceof RuleType) rule.setType((RuleType) aValue); if (columnIndex == 2 && aValue instanceof MappingConfig_AbstractRef) rule.setRight((MappingConfig_AbstractRef) aValue); } @Override public void removeRow(int idx) { myMappingPriorityRules.remove(idx); } @Override public Class<?> getColumnClass(int columnIndex) { if (columnIndex == 0 || columnIndex == 2) return MappingConfig_AbstractRef.class; if (columnIndex == 1) return RuleType.class; return super.getColumnClass(columnIndex); } @Override public String getColumnName(int column) { switch (column) { case 0: return "Language Generator"; case 1: return "Rule"; case 2: return "Extended Generators"; default: return ""; } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } public boolean isModified() { return !(myModuleDescriptor.getPriorityRules().containsAll(myMappingPriorityRules) && myMappingPriorityRules.containsAll(myModuleDescriptor.getPriorityRules()) ); } public void apply(GeneratorDependencyProvider generatorDependencies) { // Dubious code. The idea seems to be to remove rules when module import is gone. However: // (a) it's not nice to alter user data without notice; // (b) external refs inside RefSet only are considered for removal, ExternalRef with missing generator right in a rule (not under RefSet) is ignored // Would be better to warn user instead. final Set<SModuleReference> accessibleGenerators = generatorDependencies.getGenerators(); accessibleGenerators.add(myModuleDescriptor.getModuleReference()); // generator module being edited is always accessible for (MappingPriorityRule rule : myMappingPriorityRules) { Queue<Pair<MappingConfig_AbstractRef, MappingConfig_RefSet>> queue = new LinkedList<Pair<MappingConfig_AbstractRef, MappingConfig_RefSet>>(); // map entry to set it lives in queue.add(new Pair<MappingConfig_AbstractRef, MappingConfig_RefSet>(rule.getRight(), null)); while (!queue.isEmpty()) { Pair<MappingConfig_AbstractRef, MappingConfig_RefSet> ref = queue.poll(); if (ref.o1 instanceof MappingConfig_RefSet) { final MappingConfig_RefSet refSet = (MappingConfig_RefSet) ref.o1; for (MappingConfig_AbstractRef ref1 : refSet.getMappingConfigs()) { // record children of RefSet along with RefSet itself for further processing queue.add(new Pair<MappingConfig_AbstractRef, MappingConfig_RefSet>(ref1, refSet)); } } else if (ref.o1 instanceof MappingConfig_ExternalRef) { final MappingConfig_ExternalRef extRef = (MappingConfig_ExternalRef) ref.o1; if (!accessibleGenerators.contains(extRef.getGenerator()) && ref.o2 != null) { ref.o2.getMappingConfigs().remove(ref.o1); } } } } myModuleDescriptor.getPriorityRules().clear(); myModuleDescriptor.getPriorityRules().addAll(myMappingPriorityRules); } } public class AddFacetsTab extends BaseTab { public AddFacetsTab() { super(PropertiesBundle.message("module.facets.title"), AllIcons.General.Settings, PropertiesBundle.message("module.facets.tip")); } @Override public void init() { final HashMap<String, SModuleFacet> existingFacetTypes = new HashMap<String, SModuleFacet>(); for (final SModuleFacet moduleFacet : myModule.getFacets()) { existingFacetTypes.put(moduleFacet.getFacetType(), moduleFacet); } Set<String> applicableFacetTypes = new ModelAccessHelper(myProject.getModelAccess()).runReadAction(new Computable<Set<String>>() { @Override public Set<String> compute() { return FacetsFacade.getInstance().getApplicableFacetTypes(myModule.getUsedLanguages()); } }); for (String facet : FacetsFacade.getInstance().getFacetTypes()) { SModuleFacet sModuleFacet = existingFacetTypes.get(facet); if (sModuleFacet == null) { // i.e. !existingFacetTypes.contains(facet) sModuleFacet = FacetsFacade.getInstance().getFacetFactory(facet).create(); } String facetPresentation = sModuleFacet instanceof ModuleFacetBase ? ((ModuleFacetBase) sModuleFacet).getFacetPresentation() : sModuleFacet.getFacetType(); String fmt = PropertiesBundle.message("module.facets.checkbox.title"); facetPresentation = applicableFacetTypes.contains(facet) ? String.format(fmt, facetPresentation) : facetPresentation; FacetCheckBox checkBox = existingFacetTypes.containsKey(facet) ? new FacetCheckBox(AddFacetsTab.this, sModuleFacet, myFacetTabsPersistence.getFacetTab(sModuleFacet), facetPresentation) : new FacetCheckBox(AddFacetsTab.this, facet, facetPresentation); myCheckBoxes.add(checkBox); } Collections.sort(myCheckBoxes); final JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); final int i = 5; panel.setBorder(BorderFactory.createEmptyBorder(i, i, i, i)); for (FacetCheckBox checkBox : myCheckBoxes) { checkBox.addTo(panel); } setTabComponent(panel); } @Override public boolean isModified() { for (FacetCheckBox checkBox : myCheckBoxes) { if (checkBox.isExistingToRemove() || checkBox.isNewlyCreated()) { return true; } } return false; } @Override public void apply() { for (FacetCheckBox checkBox : myCheckBoxes) { SModuleFacet facet = checkBox.getFacet(); if (checkBox.isNewlyCreated()) { Tab tab = checkBox.getTab(); if (tab != null) { // not all facets necessarily feature UI component, but in case they do, let the tab populate facet with updated values. // The reason is that apply() for AddFacetsTab comes earlier than apply to any newly added tab (due to natural order of tab addition). // Should not be an issue to apply twice (once here and subsequently from MPSPropertiesConfigurable#apply()) tab.apply(); } myModuleDescriptor.addFacetDescriptor(facet); } else if (checkBox.isExistingToRemove()) { myModuleDescriptor.removeFacetDescriptor(checkBox.getFacet()); } } } } /*package*/ class FacetCheckBox implements ItemListener, Comparable<FacetCheckBox> { private final JBCheckBox myCheckBox; private final String myFacetType; private final boolean myExisting; private final Tab myAnchorTab; private Tab myFacetTab; private SModuleFacet myFacet; public FacetCheckBox(@NotNull Tab anchorTab, @NotNull SModuleFacet facet, @Nullable Tab tab, @NotNull String label) { myAnchorTab = anchorTab; myCheckBox = new JBCheckBox(label, myExisting = true); myCheckBox.addItemListener(this); myFacet = facet; myFacetType = facet.getFacetType(); myFacetTab = tab; } public FacetCheckBox(@NotNull Tab anchorTab, @NotNull String facetType, @NotNull String label) { myAnchorTab = anchorTab; myFacetType = facetType; myCheckBox = new JBCheckBox(label, myExisting = false); myCheckBox.addItemListener(this); myFacetTab = null; } public boolean isExistingToRemove() { // unchecked ==> scheduled for removal return myExisting && !myCheckBox.isSelected(); } public boolean isNewlyCreated() { // created and still checked in UI // (myFacet stays != null once created, even if newly created facet is unchecked, to preserve page values) return !myExisting && myFacet != null && myCheckBox.isSelected(); } @Override public void itemStateChanged(ItemEvent e) { if (!e.getSource().equals(myCheckBox)) { return; } if (myCheckBox.isSelected()) { if (myFacet == null) { myFacet = FacetsFacade.getInstance().getFacetFactory(myFacetType).create(); if (myFacet instanceof ModuleFacetBase) // XXX why do we need to set module here, and why do we ignore return value? ((ModuleFacetBase) myFacet).setModule(myModule); } if (myFacetTab == null) { myFacetTab = myFacetTabsPersistence.getFacetTab(myFacet); if (myFacetTab != null) { // perhaps, would be better if MPSPropertiesConfigurable is responsible for tab intialization, // and keeps track of which one is already initialized to avoid multiple initializations. myFacetTab.init(); } } if (myFacetTab != null) { ModulePropertiesConfigurable.this.insertTab(myFacetTab, ModulePropertiesConfigurable.this.indexOfTab(myAnchorTab)); } } else { if (myFacetTab != null) { ModulePropertiesConfigurable.this.removeTab(myFacetTab); } } } /*package*/ SModuleFacet getFacet() { return myFacet; } /*package*/ void addTo(JPanel panel) { panel.add(myCheckBox); } /** * @return <code>null</code> if there's no UI component (Tab) for the facet. */ /*package*/ Tab getTab() { return myFacetTab; } @Override public int compareTo(FacetCheckBox o) { return myCheckBox.getText().toLowerCase().compareTo(o.myCheckBox.getText().toLowerCase()); } } /* * FIXME myModule.getRepository requires read action (implementation, not API), while mpsProject.getRepository does not * Not sure whether which one is right (both seem reasonable, repository of a module might change, repository of the project could not) * and I need module's repo to check for dependency availability. * The problem is that I need a repo to run the command to get the repo, which does look stupid, perhaps, * myModule.getRepository shall be relaxed to give SRepo without read action or we shall use SModuleReference and project's repo instead of * myModule (SModule instance) throughout whole ModulePropertiesConfigurable. One more alternative is to have myModule.getProject().getRepo() * (i.e. something that gives access to module's repo without need for read action. Present use of myProject.getRepo to run the command * basically does exactly that, although a bit indirectly) */ private static class GetModuleRepo implements Computable<SRepository> { private final SModule myModule; public GetModuleRepo(@NotNull SModule module) { myModule = module; } @Override public SRepository compute() { return myModule.getRepository(); } } }