com.intellij.ide.browsers.BrowserLauncherAppless.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ide.browsers.BrowserLauncherAppless.java

Source

/*
 * 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.browsers;

import com.intellij.CommonBundle;
import com.intellij.Patches;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.util.ExecUtil;
import com.intellij.ide.BrowserUtil;
import com.intellij.ide.GeneralSettings;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.ui.GuiUtils;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.URLUtil;
import com.intellij.util.io.ZipUtil;
import com.intellij.util.ui.OptionsDialog;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class BrowserLauncherAppless extends BrowserLauncher {
    static final Logger LOG = Logger.getInstance(BrowserLauncherAppless.class);
    public static BrowserLauncherAppless INSTANCE = new BrowserLauncherAppless();

    private static boolean isDesktopActionSupported(Desktop.Action action) {
        return !Patches.SUN_BUG_ID_6457572 && !Patches.SUN_BUG_ID_6486393 && Desktop.isDesktopSupported()
                && Desktop.getDesktop().isSupported(action);
    }

    public static boolean canStartDefaultBrowser() {
        return isDesktopActionSupported(Desktop.Action.BROWSE) || SystemInfo.isMac || SystemInfo.isWindows
                || SystemInfo.isUnix && SystemInfo.hasXdgOpen();
    }

    private static GeneralSettings getGeneralSettingsInstance() {
        if (ApplicationManager.getApplication() != null) {
            GeneralSettings settings = GeneralSettings.getInstance();
            if (settings != null) {
                return settings;
            }
        }

        return new GeneralSettings();
    }

    @Nullable
    private static List<String> getDefaultBrowserCommand() {
        if (SystemInfo.isWindows) {
            return Arrays.asList(ExecUtil.getWindowsShellName(), "/c", "start",
                    GeneralCommandLine.inescapableQuote(""));
        } else if (SystemInfo.isMac) {
            return Collections.singletonList(ExecUtil.getOpenCommandPath());
        } else if (SystemInfo.isUnix && SystemInfo.hasXdgOpen()) {
            return Collections.singletonList("xdg-open");
        } else {
            return null;
        }
    }

    @Override
    public void open(@NotNull String url) {
        openOrBrowse(url, false);
    }

    @Override
    public void browse(@NotNull File file) {
        browse(VfsUtil.toUri(file));
    }

    @Override
    public void browse(@NotNull URI uri) {
        LOG.debug("Launch browser: [" + uri + "]");

        GeneralSettings settings = getGeneralSettingsInstance();
        if (settings.isUseDefaultBrowser()) {
            if (isDesktopActionSupported(Desktop.Action.BROWSE)) {
                try {
                    Desktop.getDesktop().browse(uri);
                    LOG.debug("Browser launched using JDK 1.6 API");
                    return;
                } catch (Exception e) {
                    LOG.warn("Error while using Desktop API, fallback to CLI", e);
                }
            }

            List<String> command = getDefaultBrowserCommand();
            if (command != null) {
                doLaunch(uri.toString(), command, null, null, ArrayUtil.EMPTY_STRING_ARRAY, null);
                return;
            }
        }

        browseUsingPath(uri.toString(), settings.getBrowserPath(), null, null, ArrayUtil.EMPTY_STRING_ARRAY);
    }

    private void openOrBrowse(@NotNull String url, boolean browse) {
        url = url.trim();

        if (url.startsWith("jar:")) {
            String files = extractFiles(url);
            if (files == null) {
                return;
            }
            url = files;
        }

        URI uri;
        if (BrowserUtil.isAbsoluteURL(url)) {
            uri = VfsUtil.toUri(url);
        } else {
            File file = new File(url);
            if (!browse && isDesktopActionSupported(Desktop.Action.OPEN)) {
                if (!file.exists()) {
                    doShowError(IdeBundle.message("error.file.does.not.exist", file.getPath()), null, null, null,
                            null);
                    return;
                }

                try {
                    Desktop.getDesktop().open(file);
                    return;
                } catch (IOException e) {
                    LOG.debug(e);
                }
            }

            browse(file);
            return;
        }

        if (uri == null) {
            doShowError(IdeBundle.message("error.malformed.url", url), null, null, null, null);
        } else {
            browse(uri);
        }
    }

    @Nullable
    private static String extractFiles(String url) {
        try {
            int sharpPos = url.indexOf('#');
            String anchor = "";
            if (sharpPos != -1) {
                anchor = url.substring(sharpPos);
                url = url.substring(0, sharpPos);
            }

            Pair<String, String> pair = URLUtil.splitJarUrl(url);
            if (pair == null)
                return null;

            File jarFile = new File(FileUtil.toSystemDependentName(pair.first));
            if (!jarFile.canRead())
                return null;

            String jarUrl = StandardFileSystems.FILE_PROTOCOL_PREFIX
                    + FileUtil.toSystemIndependentName(jarFile.getPath());
            String jarLocationHash = jarFile.getName() + "." + Integer.toHexString(jarUrl.hashCode());
            final File outputDir = new File(getExtractedFilesDir(), jarLocationHash);

            final String currentTimestamp = String.valueOf(new File(jarFile.getPath()).lastModified());
            final File timestampFile = new File(outputDir, ".idea.timestamp");

            String previousTimestamp = null;
            if (timestampFile.exists()) {
                previousTimestamp = FileUtilRt.loadFile(timestampFile);
            }

            if (!currentTimestamp.equals(previousTimestamp)) {
                final Ref<Boolean> extract = new Ref<Boolean>();
                Runnable r = new Runnable() {
                    @Override
                    public void run() {
                        final ConfirmExtractDialog dialog = new ConfirmExtractDialog();
                        if (dialog.isToBeShown()) {
                            dialog.show();
                            extract.set(dialog.isOK());
                        } else {
                            dialog.close(DialogWrapper.OK_EXIT_CODE);
                            extract.set(true);
                        }
                    }
                };

                try {
                    GuiUtils.runOrInvokeAndWait(r);
                } catch (InvocationTargetException ignored) {
                    extract.set(false);
                } catch (InterruptedException ignored) {
                    extract.set(false);
                }

                if (!extract.get()) {
                    return null;
                }

                boolean closeZip = true;
                final ZipFile zipFile = new ZipFile(jarFile);
                try {
                    ZipEntry entry = zipFile.getEntry(pair.second);
                    if (entry == null) {
                        return null;
                    }
                    InputStream is = zipFile.getInputStream(entry);
                    ZipUtil.extractEntry(entry, is, outputDir);
                    closeZip = false;
                } finally {
                    if (closeZip) {
                        zipFile.close();
                    }
                }

                ApplicationManager.getApplication().invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        new Task.Backgroundable(null, "Extracting files...", true) {
                            @Override
                            public void run(@NotNull final ProgressIndicator indicator) {
                                final int size = zipFile.size();
                                final int[] counter = new int[] { 0 };

                                class MyFilter implements FilenameFilter {
                                    private final Set<File> myImportantDirs = ContainerUtil.newHashSet(outputDir,
                                            new File(outputDir, "resources"));
                                    private final boolean myImportantOnly;

                                    private MyFilter(boolean importantOnly) {
                                        myImportantOnly = importantOnly;
                                    }

                                    @Override
                                    public boolean accept(@NotNull File dir, @NotNull String name) {
                                        indicator.checkCanceled();
                                        boolean result = myImportantOnly == myImportantDirs.contains(dir);
                                        if (result) {
                                            indicator.setFraction(((double) counter[0]) / size);
                                            counter[0]++;
                                        }
                                        return result;
                                    }
                                }

                                try {
                                    try {
                                        ZipUtil.extract(zipFile, outputDir, new MyFilter(true));
                                        ZipUtil.extract(zipFile, outputDir, new MyFilter(false));
                                        FileUtil.writeToFile(timestampFile, currentTimestamp);
                                    } finally {
                                        zipFile.close();
                                    }
                                } catch (IOException ignore) {
                                }
                            }
                        }.queue();
                    }
                });
            }

            return VfsUtilCore.pathToUrl(
                    FileUtil.toSystemIndependentName(new File(outputDir, pair.second).getPath())) + anchor;
        } catch (IOException e) {
            LOG.warn(e);
            Messages.showErrorDialog("Cannot extract files: " + e.getMessage(), "Error");
            return null;
        }
    }

    private static File getExtractedFilesDir() {
        return new File(PathManager.getSystemPath(), "ExtractedFiles");
    }

    public static void clearExtractedFiles() {
        FileUtil.delete(getExtractedFilesDir());
    }

    private static class ConfirmExtractDialog extends OptionsDialog {
        private ConfirmExtractDialog() {
            super(null);
            setTitle("Confirmation");
            init();
        }

        @Override
        protected boolean isToBeShown() {
            return getGeneralSettingsInstance().isConfirmExtractFiles();
        }

        @Override
        protected void setToBeShown(boolean value, boolean onOk) {
            getGeneralSettingsInstance().setConfirmExtractFiles(value);
        }

        @Override
        protected boolean shouldSaveOptionsOnCancel() {
            return true;
        }

        @Override
        @NotNull
        protected Action[] createActions() {
            setOKButtonText(CommonBundle.getYesButtonText());
            return new Action[] { getOKAction(), getCancelAction() };
        }

        @Override
        protected JComponent createCenterPanel() {
            JPanel panel = new JPanel(new BorderLayout());
            String message = "The files are inside an archive, do you want them to be extracted?";
            JLabel label = new JLabel(message);

            label.setIconTextGap(10);
            label.setIcon(Messages.getQuestionIcon());

            panel.add(label, BorderLayout.CENTER);
            panel.add(Box.createVerticalStrut(10), BorderLayout.SOUTH);

            return panel;
        }
    }

    @Override
    public void browse(@NotNull String url, @Nullable WebBrowser browser) {
        browse(url, browser, null);
    }

    @Override
    public void browse(@NotNull String url, @Nullable WebBrowser browser, @Nullable Project project) {
        if (browser == null) {
            openOrBrowse(url, true);
        } else {
            for (UrlOpener urlOpener : UrlOpener.EP_NAME.getExtensions()) {
                if (urlOpener.openUrl(browser, url, project)) {
                    return;
                }
            }
        }
    }

    @Override
    public boolean browseUsingPath(@Nullable final String url, @Nullable String browserPath,
            @Nullable final WebBrowser browser, @Nullable final Project project,
            @NotNull final String[] additionalParameters) {
        Runnable launchTask = null;
        if (browserPath == null && browser != null) {
            browserPath = PathUtil.toSystemDependentName(browser.getPath());
            launchTask = new Runnable() {
                @Override
                public void run() {
                    browseUsingPath(url, null, browser, project, additionalParameters);
                }
            };
        }
        return doLaunch(url, browserPath, browser, project, additionalParameters, launchTask);
    }

    private boolean doLaunch(@Nullable String url, @Nullable String browserPath, @Nullable WebBrowser browser,
            @Nullable Project project, @NotNull String[] additionalParameters, @Nullable Runnable launchTask) {
        if (!checkPath(browserPath, browser, project, launchTask)) {
            return false;
        }
        return doLaunch(url, BrowserUtil.getOpenBrowserCommand(browserPath, false), browser, project,
                additionalParameters, launchTask);
    }

    @Contract("null, _, _, _ -> false")
    public boolean checkPath(@Nullable String browserPath, @Nullable WebBrowser browser, @Nullable Project project,
            @Nullable Runnable launchTask) {
        if (!StringUtil.isEmptyOrSpaces(browserPath)) {
            return true;
        }

        String message = browser != null ? browser.getBrowserNotFoundMessage()
                : IdeBundle.message("error.please.specify.path.to.web.browser", CommonBundle.settingsActionPath());
        doShowError(message, browser, project, IdeBundle.message("title.browser.not.found"), launchTask);
        return false;
    }

    private boolean doLaunch(@Nullable String url, @NotNull List<String> command,
            @Nullable final WebBrowser browser, @Nullable final Project project,
            @NotNull String[] additionalParameters, @Nullable Runnable launchTask) {
        GeneralCommandLine commandLine = new GeneralCommandLine(command);

        if (url != null && url.startsWith("jar:")) {
            String files = extractFiles(url);
            if (files == null) {
                return false;
            }
            url = files;
        }

        if (url != null) {
            commandLine.addParameter(url);
        }

        addArgs(commandLine, browser == null ? null : browser.getSpecificSettings(), additionalParameters);
        try {
            Process process = commandLine.createProcess();
            checkCreatedProcess(browser, project, commandLine, process, launchTask);
            return true;
        } catch (ExecutionException e) {
            doShowError(e.getMessage(), browser, project, null, null);
            return false;
        }
    }

    protected void checkCreatedProcess(@Nullable WebBrowser browser, @Nullable Project project,
            @NotNull GeneralCommandLine commandLine, @NotNull Process process, @Nullable Runnable launchTask) {
    }

    protected void doShowError(@Nullable String error, @Nullable WebBrowser browser, @Nullable Project project,
            String title, @Nullable Runnable launchTask) {
        // Not started yet. Not able to show message up. (Could happen in License panel under Linux).
        LOG.warn(error);
    }

    private static void addArgs(@NotNull GeneralCommandLine command, @Nullable BrowserSpecificSettings settings,
            @NotNull String[] additional) {
        List<String> specific = settings == null ? Collections.<String>emptyList()
                : settings.getAdditionalParameters();
        if (specific.size() + additional.length > 0) {
            if (isOpenCommandUsed(command)) {
                if (BrowserUtil.isOpenCommandSupportArgs()) {
                    command.addParameter("--args");
                } else {
                    LOG.warn("'open' command doesn't allow to pass command line arguments so they will be ignored: "
                            + StringUtil.join(specific, ", ") + " " + Arrays.toString(additional));
                    return;
                }
            }

            command.addParameters(specific);
            command.addParameters(additional);
        }
    }

    public static boolean isOpenCommandUsed(@NotNull GeneralCommandLine command) {
        return SystemInfo.isMac && ExecUtil.getOpenCommandPath().equals(command.getExePath());
    }
}