Java tutorial
/* * Copyright 2000-2014 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 com.intellij.ide.plugins; import com.intellij.CommonBundle; import com.intellij.icons.AllIcons; import com.intellij.ide.BrowserUtil; import com.intellij.ide.DataManager; import com.intellij.ide.IdeBundle; import com.intellij.ide.plugins.sorters.SortByStatusAction; import com.intellij.ide.ui.search.SearchUtil; import com.intellij.ide.ui.search.SearchableOptionsRegistrar; import com.intellij.notification.*; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.application.ex.ApplicationEx; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.updateSettings.impl.PluginDownloader; import com.intellij.openapi.updateSettings.impl.UpdateChecker; import com.intellij.openapi.updateSettings.impl.UpdateSettings; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.*; import com.intellij.ui.border.CustomLineBorder; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBScrollPane; import com.intellij.util.Consumer; import com.intellij.util.concurrency.SwingWorker; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.update.UiNotifyConnector; import com.intellij.xml.util.XmlStringUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.*; import javax.swing.plaf.BorderUIResource; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLFrameHyperlinkEvent; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.awt.*; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.*; import java.util.List; import static com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces; /** * @author stathik * @since Dec 25, 2003 */ public abstract class PluginManagerMain implements Disposable { public static Logger LOG = Logger.getInstance("#com.intellij.ide.plugins.PluginManagerMain"); @NonNls private static final String TEXT_PREFIX = "<html><head>" + " <style type=\"text/css\">" + " p {" + " font-family: Arial,serif; font-size: 12pt; margin: 2px 2px" + " }" + " </style>" + "</head><body style=\"font-family: Arial,serif; font-size: 12pt; margin: 5px 5px;\">"; @NonNls private static final String TEXT_SUFFIX = "</body></html>"; @NonNls private static final String HTML_PREFIX = "<a href=\""; @NonNls private static final String HTML_SUFFIX = "</a>"; private boolean requireShutdown = false; private JPanel myToolbarPanel; private JPanel main; private JEditorPane myDescriptionTextArea; private JPanel myTablePanel; protected JPanel myActionsPanel; private JPanel myHeader; private PluginHeaderPanel myPluginHeaderPanel; private JPanel myInfoPanel; private JBScrollPane myScrollPane; protected JBLabel myPanelDescription; private JBScrollPane myPanel; protected PluginTableModel pluginsModel; protected PluginTable pluginTable; private ActionToolbar myActionToolbar; protected final MyPluginsFilter myFilter = new MyPluginsFilter(); protected PluginManagerUISettings myUISettings; private boolean myDisposed = false; private boolean myBusy = false; public PluginManagerMain(PluginManagerUISettings uiSettings) { myUISettings = uiSettings; } protected void init() { GuiUtils.replaceJSplitPaneWithIDEASplitter(main); myDescriptionTextArea.setEditorKit(new HTMLEditorKit()); myDescriptionTextArea.setEditable(false); myDescriptionTextArea.addHyperlinkListener(new MyHyperlinkListener()); JScrollPane installedScrollPane = createTable(); myPluginHeaderPanel = new PluginHeaderPanel(this, getPluginTable()); myHeader.setBackground(UIUtil.getTextFieldBackground()); myPluginHeaderPanel.getPanel().setBackground(UIUtil.getTextFieldBackground()); myPluginHeaderPanel.getPanel().setOpaque(true); myHeader.add(myPluginHeaderPanel.getPanel(), BorderLayout.CENTER); installTableActions(); myTablePanel.add(installedScrollPane, BorderLayout.CENTER); UIUtil.applyStyle(UIUtil.ComponentStyle.SMALL, myPanelDescription); myPanelDescription.setBorder(new EmptyBorder(0, 7, 0, 0)); final JPanel header = new JPanel(new BorderLayout()) { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); final Color bg = UIUtil.getTableBackground(false); ((Graphics2D) g).setPaint(new GradientPaint(0, 0, ColorUtil.shift(bg, 1.4), 0, getHeight(), ColorUtil.shift(bg, 0.9))); g.fillRect(0, 0, getWidth(), getHeight()); } }; header.setBorder(new CustomLineBorder(1, 1, 0, 1)); final JLabel mySortLabel = new JLabel(); mySortLabel.setForeground(UIUtil.getLabelDisabledForeground()); mySortLabel.setBorder(new EmptyBorder(1, 1, 1, 5)); mySortLabel.setIcon(AllIcons.General.SplitDown); mySortLabel.setHorizontalTextPosition(SwingConstants.LEADING); header.add(mySortLabel, BorderLayout.EAST); myTablePanel.add(header, BorderLayout.NORTH); myToolbarPanel.setLayout(new BorderLayout()); myActionToolbar = ActionManager.getInstance().createActionToolbar("PluginManager", getActionGroup(true), true); final JComponent component = myActionToolbar.getComponent(); myToolbarPanel.add(component, BorderLayout.CENTER); myToolbarPanel.add(myFilter, BorderLayout.WEST); new ClickListener() { @Override public boolean onClick(@NotNull MouseEvent event, int clickCount) { JBPopupFactory.getInstance() .createActionGroupPopup("Sort by:", createSortersGroup(), DataManager.getInstance().getDataContext(pluginTable), JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true) .showUnderneathOf(mySortLabel); return true; } }.installOn(mySortLabel); final TableModelListener modelListener = new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { String text = "Sort by:"; if (pluginsModel.isSortByStatus()) { text += " status,"; } if (pluginsModel.isSortByRating()) { text += " rating,"; } if (pluginsModel.isSortByDownloads()) { text += " downloads,"; } if (pluginsModel.isSortByUpdated()) { text += " updated,"; } text += " name"; mySortLabel.setText(text); } }; pluginTable.getModel().addTableModelListener(modelListener); modelListener.tableChanged(null); Border border = new BorderUIResource.LineBorderUIResource(new JBColor(Gray._220, Gray._55), 1); myInfoPanel.setBorder(border); } protected abstract JScrollPane createTable(); @Override public void dispose() { myDisposed = true; } public boolean isDisposed() { return myDisposed; } public void filter(String filter) { myFilter.setSelectedItem(filter); } public void reset() { UiNotifyConnector.doWhenFirstShown(getPluginTable(), new Runnable() { @Override public void run() { requireShutdown = false; TableUtil.ensureSelectionExists(getPluginTable()); } }); } public PluginTable getPluginTable() { return pluginTable; } public PluginTableModel getPluginsModel() { return pluginsModel; } protected void installTableActions() { pluginTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { refresh(); } }); PopupHandler.installUnknownPopupHandler(pluginTable, getActionGroup(false), ActionManager.getInstance()); new MySpeedSearchBar(pluginTable); } public void refresh() { final IdeaPluginDescriptor[] descriptors = pluginTable.getSelectedObjects(); pluginInfoUpdate(descriptors != null && descriptors.length == 1 ? descriptors[0] : null, myFilter.getFilter(), myDescriptionTextArea, myPluginHeaderPanel, this); myActionToolbar.updateActionsImmediately(); final JComponent parent = (JComponent) myHeader.getParent(); parent.revalidate(); parent.repaint(); } public void setRequireShutdown(boolean val) { requireShutdown |= val; } public ArrayList<IdeaPluginDescriptorImpl> getDependentList(IdeaPluginDescriptorImpl pluginDescriptor) { return pluginsModel.dependent(pluginDescriptor); } protected void modifyPluginsList(List<IdeaPluginDescriptor> list) { IdeaPluginDescriptor[] selected = pluginTable.getSelectedObjects(); pluginsModel.updatePluginsList(list); pluginsModel.filter(myFilter.getFilter().toLowerCase()); if (selected != null) { select(selected); } } protected abstract ActionGroup getActionGroup(boolean inToolbar); protected abstract PluginManagerMain getAvailable(); protected abstract PluginManagerMain getInstalled(); public JPanel getMainPanel() { return main; } protected boolean acceptHost(String host) { return true; } /** * Start a new thread which downloads new list of plugins from the site in * the background and updates a list of plugins in the table. */ protected void loadPluginsFromHostInBackground() { setDownloadStatus(true); new SwingWorker() { List<IdeaPluginDescriptor> list = null; List<String> errorMessages = new ArrayList<String>(); @Override public Object construct() { try { list = RepositoryHelper.loadPluginsFromRepository(null); } catch (Exception e) { LOG.info(e); errorMessages.add(e.getMessage()); } for (String host : UpdateSettings.getInstance().getStoredPluginHosts()) { if (!acceptHost(host)) continue; final ArrayList<PluginDownloader> downloaded = new ArrayList<PluginDownloader>(); try { UpdateChecker.checkPluginsHost(host, downloaded, false, null); for (PluginDownloader downloader : downloaded) { final PluginNode pluginNode = PluginDownloader.createPluginNode(host, downloader); if (pluginNode != null) { if (list == null) list = new ArrayList<IdeaPluginDescriptor>(); list.add(pluginNode); } } } catch (ProcessCanceledException ignore) { } catch (Exception e) { LOG.info(e); errorMessages.add(e.getMessage()); } } return list; } @Override public void finished() { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { setDownloadStatus(false); if (list != null) { modifyPluginsList(list); propagateUpdates(list); } if (!errorMessages.isEmpty()) { if (Messages.OK == Messages.showOkCancelDialog( IdeBundle.message("error.list.of.plugins.was.not.loaded", StringUtil.join(errorMessages, ", ")), IdeBundle.message("title.plugins"), CommonBundle.message("button.retry"), CommonBundle.getCancelButtonText(), Messages.getErrorIcon())) { loadPluginsFromHostInBackground(); } } } }); } }.start(); } protected abstract void propagateUpdates(List<IdeaPluginDescriptor> list); protected void setDownloadStatus(boolean status) { pluginTable.setPaintBusy(status); myBusy = status; } protected void loadAvailablePlugins() { ArrayList<IdeaPluginDescriptor> list; try { // If we already have a file with downloaded plugins from the last time, // then read it, load into the list and start the updating process. // Otherwise just start the process of loading the list and save it // into the persistent config file for later reading. File file = new File(PathManager.getPluginsPath(), RepositoryHelper.PLUGIN_LIST_FILE); if (file.exists()) { RepositoryContentHandler handler = new RepositoryContentHandler(); SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); parser.parse(file, handler); list = handler.getPluginsList(); modifyPluginsList(list); } } catch (Exception ex) { // Nothing to do, just ignore - if nothing can be read from the local // file just start downloading of plugins' list from the site. } loadPluginsFromHostInBackground(); } public static void downloadPlugins(@NotNull final List<PluginNode> plugins, @NotNull final List<IdeaPluginDescriptor> allPlugins, @NotNull final Consumer<Set<PluginNode>> onSuccess, @Nullable final Consumer<Set<PluginNode>> cleanup) throws IOException { try { ProgressManager.getInstance().run(new Task.Backgroundable(null, IdeBundle.message("progress.download.plugins"), true, PluginManagerUISettings.getInstance()) { @Override public void run(@NotNull ProgressIndicator indicator) { Set<PluginNode> set = null; try { set = PluginInstaller.prepareToInstall(plugins, allPlugins); if (set != null) { onSuccess.consume(set); } } finally { if (cleanup != null) cleanup.consume(set); } } }); } catch (RuntimeException e) { if (e.getCause() != null && e.getCause() instanceof IOException) { throw (IOException) e.getCause(); } else { throw e; } } } public boolean isRequireShutdown() { return requireShutdown; } public void ignoreChanges() { requireShutdown = false; } public static void pluginInfoUpdate(IdeaPluginDescriptor plugin, @Nullable String filter, @NotNull JEditorPane descriptionTextArea, @NotNull PluginHeaderPanel header, PluginManagerMain manager) { if (plugin == null) { setTextValue(null, filter, descriptionTextArea); header.getPanel().setVisible(false); return; } StringBuilder sb = new StringBuilder(); header.setPlugin(plugin); String description = plugin.getDescription(); if (!isEmptyOrSpaces(description)) { sb.append(description); } String changeNotes = plugin.getChangeNotes(); if (!isEmptyOrSpaces(changeNotes)) { sb.append("<h4>Change Notes</h4>"); sb.append(changeNotes); } if (!plugin.isBundled()) { String vendor = plugin.getVendor(); String vendorEmail = plugin.getVendorEmail(); String vendorUrl = plugin.getVendorUrl(); if (!isEmptyOrSpaces(vendor) || !isEmptyOrSpaces(vendorEmail) || !isEmptyOrSpaces(vendorUrl)) { sb.append("<h4>Vendor</h4>"); if (!isEmptyOrSpaces(vendor)) { sb.append(vendor); } if (!isEmptyOrSpaces(vendorUrl)) { sb.append("<br>").append(composeHref(vendorUrl)); } if (!isEmptyOrSpaces(vendorEmail)) { sb.append("<br>").append(HTML_PREFIX).append("mailto:").append(vendorEmail).append("\">") .append(vendorEmail).append(HTML_SUFFIX); } } String pluginDescriptorUrl = plugin.getUrl(); if (!isEmptyOrSpaces(pluginDescriptorUrl)) { sb.append("<h4>Plugin homepage</h4>").append(composeHref(pluginDescriptorUrl)); } String size = plugin instanceof PluginNode ? ((PluginNode) plugin).getSize() : null; if (!isEmptyOrSpaces(size)) { sb.append("<h4>Size</h4>").append(PluginManagerColumnInfo.getFormattedSize(size)); } } setTextValue(sb, filter, descriptionTextArea); } private static void setTextValue(@Nullable StringBuilder text, @Nullable String filter, JEditorPane pane) { if (text != null) { text.insert(0, TEXT_PREFIX); text.append(TEXT_SUFFIX); pane.setText(SearchUtil.markup(text.toString(), filter).trim()); pane.setCaretPosition(0); } else { pane.setText(TEXT_PREFIX + TEXT_SUFFIX); } } private static String composeHref(String vendorUrl) { return HTML_PREFIX + vendorUrl + "\">" + vendorUrl + HTML_SUFFIX; } public boolean isModified() { if (requireShutdown) return true; return false; } public String apply() { final String applyMessage = canApply(); if (applyMessage != null) return applyMessage; setRequireShutdown(true); return null; } @Nullable protected String canApply() { return null; } protected DefaultActionGroup createSortersGroup() { final DefaultActionGroup group = new DefaultActionGroup("Sort by", true); group.addAction(new SortByStatusAction(pluginTable, pluginsModel)); return group; } public static class MyHyperlinkListener implements HyperlinkListener { @Override public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { JEditorPane pane = (JEditorPane) e.getSource(); if (e instanceof HTMLFrameHyperlinkEvent) { HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent) e; HTMLDocument doc = (HTMLDocument) pane.getDocument(); doc.processHTMLFrameHyperlinkEvent(evt); } else { URL url = e.getURL(); if (url != null) { BrowserUtil.browse(url); } } } } } private static class MySpeedSearchBar extends SpeedSearchBase<PluginTable> { public MySpeedSearchBar(PluginTable cmp) { super(cmp); } @Override protected int convertIndexToModel(int viewIndex) { return getComponent().convertRowIndexToModel(viewIndex); } @Override public int getSelectedIndex() { return myComponent.getSelectedRow(); } @Override public Object[] getAllElements() { return myComponent.getElements(); } @Override public String getElementText(Object element) { return ((IdeaPluginDescriptor) element).getName(); } @Override public void selectElement(Object element, String selectedText) { for (int i = 0; i < myComponent.getRowCount(); i++) { if (myComponent.getObjectAt(i).getName().equals(((IdeaPluginDescriptor) element).getName())) { myComponent.setRowSelectionInterval(i, i); TableUtil.scrollSelectionToVisible(myComponent); break; } } } } public void select(IdeaPluginDescriptor... descriptors) { pluginTable.select(descriptors); } protected static boolean isAccepted(String filter, Set<String> search, IdeaPluginDescriptor descriptor) { if (StringUtil.isEmpty(filter)) return true; if (isAccepted(search, filter, descriptor.getName())) { return true; } else { final String description = descriptor.getDescription(); if (description != null && isAccepted(search, filter, description)) { return true; } final String category = descriptor.getCategory(); if (category != null && isAccepted(search, filter, category)) { return true; } final String changeNotes = descriptor.getChangeNotes(); if (changeNotes != null && isAccepted(search, filter, changeNotes)) { return true; } } return false; } private static boolean isAccepted(final Set<String> search, @NotNull final String filter, @NotNull final String description) { if (StringUtil.containsIgnoreCase(description, filter)) return true; final SearchableOptionsRegistrar optionsRegistrar = SearchableOptionsRegistrar.getInstance(); final HashSet<String> descriptionSet = new HashSet<String>(search); descriptionSet.removeAll(optionsRegistrar.getProcessedWords(description)); if (descriptionSet.isEmpty()) { return true; } return false; } public static void notifyPluginsWereInstalled(@NotNull Collection<? extends IdeaPluginDescriptor> installed, Project project) { String pluginName = installed.size() == 1 ? installed.iterator().next().getName() : null; notifyPluginsWereUpdated(pluginName != null ? "Plugin \'" + pluginName + "\' was successfully installed" : "Plugins were installed", project); } public static void notifyPluginsWereUpdated(final String title, final Project project) { final ApplicationEx app = ApplicationManagerEx.getApplicationEx(); final boolean restartCapable = app.isRestartCapable(); String message = restartCapable ? IdeBundle.message("message.idea.restart.required", ApplicationNamesInfo.getInstance().getFullProductName()) : IdeBundle.message("message.idea.shutdown.required", ApplicationNamesInfo.getInstance().getFullProductName()); message += "<br><a href="; message += restartCapable ? "\"restart\">Restart now" : "\"shutdown\">Shutdown"; message += "</a>"; new NotificationGroup("Plugins Lifecycle Group", NotificationDisplayType.STICKY_BALLOON, true) .createNotification(title, XmlStringUtil.wrapInHtml(message), NotificationType.INFORMATION, new NotificationListener() { @Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { notification.expire(); if (restartCapable) { app.restart(true); } else { app.exit(true, true); } } }) .notify(project); } public class MyPluginsFilter extends FilterComponent { public MyPluginsFilter() { super("PLUGIN_FILTER", 5); } @Override public void filter() { pluginsModel.filter(getFilter().toLowerCase()); TableUtil.ensureSelectionExists(getPluginTable()); } } protected class RefreshAction extends DumbAwareAction { public RefreshAction() { super("Reload List of Plugins", "Reload list of plugins", AllIcons.Actions.Refresh); } @Override public void actionPerformed(AnActionEvent e) { loadAvailablePlugins(); myFilter.setFilter(""); } @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(!myBusy); } } }