Java tutorial
/* * Copyright (C) 2015 The Android Open Source Project * * 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 com.android.tools.idea.gradle.project.subset; import com.android.SdkConstants; import com.android.tools.idea.gradle.project.model.AndroidModuleModel; import com.android.tools.idea.gradle.project.model.GradleModuleModel; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.externalSystem.model.DataNode; import com.intellij.openapi.externalSystem.model.project.ModuleData; import com.intellij.openapi.fileChooser.*; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileWrapper; import com.intellij.ui.Cell; import com.intellij.ui.TableSpeedSearch; import com.intellij.ui.components.JBLabel; import com.intellij.ui.table.JBTable; import com.intellij.util.PairFunction; import com.intellij.util.PlatformIcons; import com.intellij.util.SystemProperties; import org.jdesktop.swingx.JXLabel; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableRowSorter; import java.awt.*; import java.io.File; import java.io.IOException; import java.text.Collator; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import static com.android.builder.model.AndroidProject.PROJECT_TYPE_APP; import static com.android.tools.idea.gradle.project.sync.idea.data.service.AndroidProjectKeys.ANDROID_MODEL; import static com.android.tools.idea.gradle.project.sync.idea.data.service.AndroidProjectKeys.GRADLE_MODULE_MODEL; import static com.intellij.icons.AllIcons.Nodes.PpJdk; import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.getChildren; import static com.intellij.openapi.util.JDOMUtil.writeDocument; import static com.intellij.openapi.util.text.StringUtil.isNotEmpty; import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile; import static com.intellij.util.containers.ContainerUtil.getFirstItem; import static icons.AndroidIcons.AppModule; import static icons.AndroidIcons.LibraryModule; import static java.awt.BorderLayout.NORTH; import static java.awt.BorderLayout.SOUTH; import static java.awt.event.InputEvent.CTRL_MASK; import static java.awt.event.InputEvent.META_MASK; import static java.awt.event.KeyEvent.*; import static java.util.Collections.emptyList; import static javax.swing.ListSelectionModel.SINGLE_SELECTION; public class ModulesToImportDialog extends DialogWrapper { private static final int SELECTED_MODULE_COLUMN = 0; private static final int MODULE_NAME_COLUMN = 1; @NotNull private final List<DataNode<ModuleData>> alwaysIncludedModules = Lists.newArrayList(); @Nullable private final Project myProject; private JPanel myPanel; private JBTable myModulesTable; private JXLabel myDescriptionLabel; private JPanel myContentsPanel; private JBLabel mySelectionStatusLabel; private volatile boolean mySkipValidation; private int myMaxSelectionCount = -1; public ModulesToImportDialog(@NotNull Collection<DataNode<ModuleData>> modules, @Nullable Project project) { super(project, true, IdeModalityType.IDE); setTitle("Select Modules to Include in Project Subset"); myProject = project; init(); ModuleTableModel model = getModulesTable().getModel(); for (DataNode<ModuleData> module : modules) { Collection<DataNode<GradleModuleModel>> gradleProjects = getChildren(module, GRADLE_MODULE_MODEL); if (gradleProjects.isEmpty()) { alwaysIncludedModules.add(module); } else { // We only show modules that are recognized in Gradle. // For example, in a multi-module project the top-level module is just a folder that contains the rest of // modules, which is not defined in settings.gradle. model.add(module); } } getModulesTable().sort(); myDescriptionLabel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); DefaultActionGroup group = new DefaultActionGroup(); group.addAll(new SelectAllAction(true), new SelectAllAction(false)); group.addSeparator(); group.add(new ShowSelectedModulesAction(getModulesTable())); group.addSeparator(); group.addAll(new LoadFromFileAction(), new SaveToFileAction()); ActionToolbar toolbar = ActionManager.getInstance() .createActionToolbar("android.gradle.module.selection.dialog.toolbar", group, true); myContentsPanel.add(toolbar.getComponent(), NORTH); mySelectionStatusLabel = new JBLabel(); myContentsPanel.add(mySelectionStatusLabel, SOUTH); updateSelectionStatus(); } @NotNull private ModuleTable getModulesTable() { return (ModuleTable) myModulesTable; } private void updateSelectionStatus() { ModuleTable table = getModulesTable(); ModuleTableModel model = table.getModel(); int rowCount = model.getRowCount(); int selectedRowCount = model.selectedRowCount; String msg = String.format("%1$d Modules. %2$d selected", rowCount, selectedRowCount); mySelectionStatusLabel.setText(msg); table.updateFilter(); } @NotNull private static String getNameOf(@NotNull DataNode<ModuleData> module) { return module.getData().getExternalName(); } public void setDescription(@NotNull String description) { myDescriptionLabel.setText(description); } @Override @Nullable protected ValidationInfo doValidate() { int selectionCount = getModulesTable().getModel().selectedRowCount; if (selectionCount <= 0) { return new ValidationInfo("Please select at least one Module", myModulesTable); } if (myMaxSelectionCount > 0 && selectionCount > myMaxSelectionCount) { String message = "Please select only " + myMaxSelectionCount + " module"; if (myMaxSelectionCount > 1) { message += "s"; } message += "."; return new ValidationInfo(message, myModulesTable); } return null; } private boolean hasSelectedModules() { return getModulesTable().getModel().selectedRowCount > 0; } private void setAllSelected(boolean selected) { ModuleTableModel model = getModulesTable().getModel(); int count = model.getRowCount(); mySkipValidation = true; for (int i = 0; i < count; i++) { model.setItemSelected(i, selected); } mySkipValidation = false; if (!selected) { initValidation(); } updateSelectionStatus(); } @NotNull public Collection<DataNode<ModuleData>> getSelectedModules() { List<DataNode<ModuleData>> modules = Lists.newArrayList(alwaysIncludedModules); modules.addAll(getUserSelectedModules()); return modules; } @VisibleForTesting @NotNull public Collection<String> getDisplayedModules() { return getModulesTable().getModel().getModuleNames(); } @NotNull private Collection<DataNode<ModuleData>> getUserSelectedModules() { List<DataNode<ModuleData>> modules = Lists.newArrayList(); ModuleTable table = getModulesTable(); ModuleTableModel model = table.getModel(); int count = model.getRowCount(); for (int i = 0; i < count; i++) { if (model.isItemSelected(i)) { modules.add(model.getItemAt(i)); } } return modules; } private void select(@NotNull List<String> moduleNames) { ModuleTableModel model = getModulesTable().getModel(); int count = model.getRowCount(); for (int i = 0; i < count; i++) { DataNode<ModuleData> module = model.getItemAt(i); String name = getNameOf(module); model.setItemSelected(i, moduleNames.contains(name)); } } @Override @NotNull protected JComponent createCenterPanel() { return myPanel; } @Override @Nullable public JComponent getPreferredFocusedComponent() { return myModulesTable; } private void createUIComponents() { myModulesTable = new ModuleTable(); new TableSpeedSearch(myModulesTable, new PairFunction<Object, Cell, String>() { @Override public String fun(Object o, Cell v) { if (o instanceof ModuleRow) { ModuleRow row = (ModuleRow) o; return getNameOf(row.module); } return o == null || o instanceof Boolean ? "" : o.toString(); } }); } public void updateSelection(@NotNull Collection<String> selection) { ModuleTableModel model = getModulesTable().getModel(); int count = model.getRowCount(); mySkipValidation = true; for (int i = 0; i < count; i++) { DataNode<ModuleData> module = model.getItemAt(i); String name = getNameOf(module); boolean selected = selection.contains(name); model.setItemSelected(i, selected); } mySkipValidation = false; initValidation(); updateSelectionStatus(); } public void setMaxSelectionCount(int maxSelectionCount) { if (maxSelectionCount == 0) { throw new IllegalArgumentException("Value must be different than zero"); } myMaxSelectionCount = maxSelectionCount; } public void clearSelection() { List<String> selection = Collections.emptyList(); updateSelection(selection); } private static class ShowSelectedModulesAction extends ToggleAction { private ModuleTable myTable; ShowSelectedModulesAction(@NotNull ModuleTable table) { super("Show Selected Modules Only", null, AllIcons.Actions.ShowHiddens); myTable = table; } @Override public boolean isSelected(AnActionEvent e) { return myTable.myShowSelectedRowsOnly; } @Override public void setSelected(AnActionEvent e, boolean state) { myTable.setShowSelectedRowsOnly(state); } } private class SelectAllAction extends DumbAwareAction { private final boolean mySelect; SelectAllAction(boolean select) { super(select ? "Select All" : "Unselect All", null, select ? PlatformIcons.SELECT_ALL_ICON : PlatformIcons.UNSELECT_ALL_ICON); mySelect = select; int keyCode = select ? VK_A : VK_N; registerCustomShortcutSet(keyCode, SystemInfo.isMac ? META_MASK : CTRL_MASK, myModulesTable); } @Override public void update(@NotNull AnActionEvent e) { int rowCount = getModulesTable().getModel().getRowCount(); e.getPresentation().setEnabled(rowCount > 0); } @Override public void actionPerformed(@NotNull AnActionEvent e) { setAllSelected(mySelect); } } private class LoadFromFileAction extends DumbAwareAction { LoadFromFileAction() { super("Load Selection from File", null, AllIcons.Actions.Menu_open); registerCustomShortcutSet(VK_O, SystemInfo.isMac ? META_MASK : CTRL_MASK, myModulesTable); } @Override public void actionPerformed(@NotNull AnActionEvent e) { FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) { @Override public boolean isFileSelectable(VirtualFile file) { boolean selectable = super.isFileSelectable(file); if (selectable) { selectable = SdkConstants.EXT_XML.equals(file.getExtension()); } return selectable; } }; String title = "Load Module Selection"; descriptor.setTitle(title); FileChooserDialog dialog = FileChooserFactory.getInstance().createFileChooser(descriptor, myProject, getWindow()); VirtualFile[] allSelected = dialog.choose(myProject); if (allSelected.length > 0) { File file = virtualToIoFile(allSelected[0]); try { List<String> loadedModuleNames = Selection.load(file); select(loadedModuleNames); } catch (Throwable error) { String msg = String.format("Failed to load Module selection from file '%1$s'", file.getPath()); Messages.showErrorDialog(getWindow(), msg, title); String cause = error.getMessage(); if (isNotEmpty(cause)) { msg = msg + ":\n" + cause; } Logger.getInstance(ModulesToImportDialog.class).info(msg, error); } } } } private class SaveToFileAction extends DumbAwareAction { SaveToFileAction() { super("Save Selection As", null, AllIcons.Actions.Menu_saveall); registerCustomShortcutSet(VK_S, SystemInfo.isMac ? META_MASK : CTRL_MASK, myModulesTable); } @Override public void update(@NotNull AnActionEvent e) { e.getPresentation().setEnabled(hasSelectedModules()); } @Override public void actionPerformed(@NotNull AnActionEvent e) { String title = "Save Module Selection"; FileSaverDescriptor descriptor = new FileSaverDescriptor(title, "Save the list of selected Modules to a file", SdkConstants.EXT_XML); FileSaverDialog dialog = FileChooserFactory.getInstance().createSaveFileDialog(descriptor, getWindow()); VirtualFile baseDir = myProject != null ? myProject.getBaseDir() : null; VirtualFileWrapper result = dialog.save(baseDir, null); if (result != null) { File file = result.getFile(); try { Selection.save(getUserSelectedModules(), file); } catch (IOException error) { String msg = String.format("Failed to save Module selection to file '%1$s'", file.getPath()); Messages.showErrorDialog(getWindow(), msg, title); String cause = error.getMessage(); if (isNotEmpty(cause)) { msg = msg + ":\n" + cause; } Logger.getInstance(ModulesToImportDialog.class).info(msg, error); } } } } // Module selection can be stored in XML files. Sample: // <?xml version="1.0" encoding="UTF-8"?> // <selectedModules> // <module name="app" /> // <module name="mylibrary" /> //</selectedModules> @VisibleForTesting static class Selection { @NonNls private static final String ROOT_ELEMENT_NAME = "selectedModules"; @NonNls private static final String MODULE_ELEMENT_NAME = "module"; @NonNls private static final String MODULE_NAME_ATTRIBUTE_NAME = "name"; @NotNull static List<String> load(@NotNull File file) throws JDOMException, IOException { List<String> modules = Lists.newArrayList(); Element rootElement = JDOMUtil.load(file); if (ROOT_ELEMENT_NAME.equals(rootElement.getName())) { for (Element child : rootElement.getChildren(MODULE_ELEMENT_NAME)) { String moduleName = child.getAttributeValue(MODULE_NAME_ATTRIBUTE_NAME); if (isNotEmpty(moduleName)) { modules.add(moduleName); } } } return modules; } static void save(@NotNull Collection<DataNode<ModuleData>> modules, @NotNull File file) throws IOException { Document document = new Document(new Element(ROOT_ELEMENT_NAME)); for (DataNode<ModuleData> module : modules) { Element child = new Element(MODULE_ELEMENT_NAME); child.setAttribute(MODULE_NAME_ATTRIBUTE_NAME, getNameOf(module)); document.getRootElement().addContent(child); } writeDocument(document, file, SystemProperties.getLineSeparator()); } } private class ModuleTable extends JBTable { private ModuleTableRowSorter myRowSorter; private boolean myShowSelectedRowsOnly; ModuleTable() { super(new ModuleTableModel()); setCheckBoxColumnWidth(); setModuleNameCellRenderer(); setAutoResizeMode(AUTO_RESIZE_LAST_COLUMN); setIntercellSpacing(new Dimension(0, 0)); setRowSelectionAllowed(true); setSelectionMode(SINGLE_SELECTION); setShowGrid(false); setTableHeader(null); } private void setCheckBoxColumnWidth() { TableColumn column = getColumnModel().getColumn(SELECTED_MODULE_COLUMN); int width = 30; column.setMaxWidth(width); column.setPreferredWidth(width); column.setWidth(width); } private void setModuleNameCellRenderer() { TableColumn column = getColumnModel().getColumn(MODULE_NAME_COLUMN); column.setCellRenderer(new DefaultTableCellRenderer() { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int rowIndex, int columnIndex) { Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, rowIndex, columnIndex); if (c instanceof JLabel && value instanceof ModuleRow) { JLabel label = (JLabel) c; ModuleRow row = (ModuleRow) value; label.setIcon(row.icon); label.setText(getNameOf(row.module)); } return c; } }); } void updateFilter() { setShowSelectedRowsOnly(myShowSelectedRowsOnly); } void setShowSelectedRowsOnly(boolean showSelectedRowsOnly) { myShowSelectedRowsOnly = showSelectedRowsOnly; if (myRowSorter == null) { sort(); } if (showSelectedRowsOnly) { myRowSorter.setRowFilter(new RowFilter<ModuleTableModel, Integer>() { @Override public boolean include(Entry<? extends ModuleTableModel, ? extends Integer> entry) { Object value = entry.getValue(MODULE_NAME_COLUMN); if (value instanceof ModuleRow) { ModuleRow row = (ModuleRow) value; return row.selected; } return false; } }); } else { myRowSorter.setRowFilter(null); } } void sort() { myRowSorter = new ModuleTableRowSorter(getModel()); setRowSorter(myRowSorter); } @Override @NotNull public ModuleTableModel getModel() { return (ModuleTableModel) super.getModel(); } } private static class ModuleTableRowSorter extends TableRowSorter<ModuleTableModel> { ModuleTableRowSorter(@NotNull ModuleTableModel model) { super(model); setComparator(MODULE_NAME_COLUMN, new Comparator<ModuleRow>() { @Override public int compare(ModuleRow row1, ModuleRow row2) { return Collator.getInstance().compare(row1.toString(), row2.toString()); } }); List<RowSorter.SortKey> sortKeys = Lists.newArrayList(); sortKeys.add(new RowSorter.SortKey(MODULE_NAME_COLUMN, SortOrder.ASCENDING)); setSortKeys(sortKeys); } } private class ModuleTableModel extends AbstractTableModel { public int selectedRowCount; @NotNull private final List<ModuleRow> rows = Lists.newArrayList(); @NotNull Collection<String> getModuleNames() { if (rows.isEmpty()) { return emptyList(); } List<String> names = Lists.newArrayListWithExpectedSize(rows.size()); for (ModuleRow row : rows) { names.add(getNameOf(row.module)); } return names; } @Override public int getRowCount() { return rows.size(); } @Override public int getColumnCount() { return 2; } @Override @Nullable public Object getValueAt(int rowIndex, int columnIndex) { if (rowIndex < rows.size()) { ModuleRow row = rows.get(rowIndex); switch (columnIndex) { case SELECTED_MODULE_COLUMN: return row.selected; default: return row; } } return null; } @Override public Class<?> getColumnClass(int columnIndex) { switch (columnIndex) { case SELECTED_MODULE_COLUMN: return Boolean.class; case MODULE_NAME_COLUMN: return ModuleRow.class; default: return Object.class; } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return columnIndex == SELECTED_MODULE_COLUMN; } @Override public void setValueAt(@Nullable Object aValue, int rowIndex, int columnIndex) { if (rowIndex < rows.size() && columnIndex == SELECTED_MODULE_COLUMN && aValue instanceof Boolean) { boolean selected = (Boolean) aValue; if (setItemSelected(rowIndex, selected) && !mySkipValidation) { initValidation(); updateSelectionStatus(); } } } void add(@NotNull DataNode<ModuleData> module) { rows.add(new ModuleRow(module)); selectedRowCount++; } boolean isItemSelected(int rowIndex) { ModuleRow row = rows.get(rowIndex); return row.selected; } @NotNull DataNode<ModuleData> getItemAt(int rowIndex) { ModuleRow row = rows.get(rowIndex); return row.module; } boolean setItemSelected(int rowIndex, boolean selected) { ModuleRow row = rows.get(rowIndex); if (row.selected != selected) { row.selected = selected; if (row.selected) { selectedRowCount++; } else { selectedRowCount--; } return true; } return false; } } private static class ModuleRow { @NotNull public final DataNode<ModuleData> module; @NotNull public final Icon icon; public boolean selected = true; ModuleRow(@NotNull DataNode<ModuleData> module) { this.module = module; icon = getModuleIcon(module); } @NotNull private static Icon getModuleIcon(@NotNull DataNode<ModuleData> module) { Collection<DataNode<AndroidModuleModel>> children = getChildren(module, ANDROID_MODEL); if (!children.isEmpty()) { DataNode<AndroidModuleModel> child = getFirstItem(children); if (child != null) { AndroidModuleModel androidModel = child.getData(); return androidModel.getProjectType() == PROJECT_TYPE_APP ? AppModule : LibraryModule; } } return PpJdk; } @Override public String toString() { return getNameOf(module); } } }