made all fields final. Could not resist, sorry
[fedora-idea.git] / platform-api / src / com / intellij / ide / BrowserUtil.java
blobddccd4e08bc2de4c20dd6c8c1da0f303d808971b
1 /*
2 * Copyright 2000-2007 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ide;
18 import com.intellij.CommonBundle;
19 import com.intellij.openapi.application.*;
20 import com.intellij.openapi.progress.ProgressIndicator;
21 import com.intellij.openapi.progress.Task;
22 import com.intellij.openapi.ui.Messages;
23 import com.intellij.openapi.util.SystemInfo;
24 import com.intellij.openapi.util.io.FileUtil;
25 import com.intellij.openapi.vfs.JarFileSystem;
26 import com.intellij.openapi.vfs.VfsUtil;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.openapi.vfs.VirtualFileManager;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.util.io.ZipUtil;
31 import com.intellij.util.ui.OptionsDialog;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
35 import javax.swing.*;
36 import java.awt.*;
37 import java.io.*;
38 import java.net.URL;
39 import java.util.Arrays;
40 import java.util.HashSet;
41 import java.util.Set;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 import java.util.zip.ZipEntry;
45 import java.util.zip.ZipFile;
47 public class BrowserUtil {
48 private static final Logger LOG = Logger.getInstance("#" + BrowserUtil.class.getName());
50 // The pattern for 'scheme' mainly according to RFC1738.
51 // We have to violate the RFC since we need to distinguish
52 // real schemes from local Windows paths; The only difference
53 // with RFC is that we do not allow schemes with length=1 (in other case
54 // local paths like "C:/temp/index.html" whould be erroneously interpreted as
55 // external URLs.)
56 @NonNls private static final Pattern ourExternalPrefix = Pattern.compile("^[\\w\\+\\.\\-]{2,}:");
57 private static final Pattern ourAnchorsuffix = Pattern.compile("#(.*)$");
59 private BrowserUtil() {
62 public static boolean isAbsoluteURL(String url) {
63 return ourExternalPrefix.matcher(url.toLowerCase()).find();
66 public static String getDocURL(String url) {
67 Matcher anchorMatcher = ourAnchorsuffix.matcher(url);
69 if (anchorMatcher.find()) {
70 return anchorMatcher.reset().replaceAll("");
73 return url;
76 public static URL getURL(String url) throws java.net.MalformedURLException {
77 if (!isAbsoluteURL(url)) {
78 return new URL("file", "", url);
81 return VfsUtil.convertToURL(url);
84 private static void launchBrowser(final String url, String[] command) {
85 try {
86 URL curl = BrowserUtil.getURL(url);
88 if (curl != null) {
89 final String urlString = curl.toString();
90 String[] commandLine;
91 if (SystemInfo.isWindows && isUseDefaultBrowser()) {
92 commandLine = new String[command.length + 2];
93 System.arraycopy(command, 0, commandLine, 0, command.length);
94 commandLine[commandLine.length - 2] = "\"\"";
95 commandLine[commandLine.length - 1] = "\"" + redirectUrl(url, urlString) + "\"";
97 else {
98 commandLine = new String[command.length + 1];
99 System.arraycopy(command, 0, commandLine, 0, command.length);
100 if (SystemInfo.isWindows) {
101 commandLine[commandLine.length - 1] = "\"" + urlString + "\"";
103 else {
104 commandLine[commandLine.length - 1] = urlString.replaceAll(" ", "%20");
107 Runtime.getRuntime().exec(commandLine);
109 else {
110 showErrorMessage(IdeBundle.message("error.malformed.url", url), CommonBundle.getErrorTitle());
113 catch (final IOException e) {
114 showErrorMessage(IdeBundle.message("error.cannot.start.browser", e.getMessage()),
115 CommonBundle.getErrorTitle());
120 * This method works around Windows 'start' command behaivor of dropping anchors from the url for local urls.
122 private static String redirectUrl(String url, @NonNls String urlString) throws IOException {
123 if (url.indexOf('&') == -1 && (!urlString.startsWith("file:") || urlString.indexOf("#") == -1)) return urlString;
125 File redirect = File.createTempFile("redirect", ".html");
126 redirect.deleteOnExit();
127 FileWriter writer = new FileWriter(redirect);
128 writer.write("<html><head></head><body><script type=\"text/javascript\">window.location=\"" + url + "\";</script></body></html>");
129 writer.close();
130 return VfsUtil.pathToUrl(redirect.getAbsolutePath());
133 private static boolean isUseDefaultBrowser() {
134 Application application = ApplicationManager.getApplication();
135 if (application == null) {
136 return true;
138 else {
139 return getGeneralSettingsInstance().isUseDefaultBrowser();
143 private static void showErrorMessage(final String message, final String title) {
144 final Application app = ApplicationManager.getApplication();
145 if (app == null) {
146 return; // Not started yet. Not able to show message up. (Could happen in License panel under Linux).
149 Runnable runnable = new Runnable() {
150 public void run() {
151 Messages.showMessageDialog(message,
152 title,
153 Messages.getErrorIcon());
157 if (app.isDispatchThread()) {
158 runnable.run();
160 else {
161 app.invokeLater(runnable, ModalityState.NON_MODAL);
165 private static void launchBrowserUsingStandardWay(final String url) {
166 String[] command;
167 try {
168 String browserPath = getGeneralSettingsInstance().getBrowserPath();
169 if (browserPath == null || browserPath.trim().length() == 0) {
170 showErrorMessage(IdeBundle.message("error.please.specify.path.to.web.browser"),
171 IdeBundle.message("title.browser.not.found"));
172 return;
175 command = new String[]{browserPath};
177 catch (NullPointerException e) {
178 // todo: fix the possible problem on startup, see SCR #35066
179 command = getDefaultBrowserCommand();
180 if (command == null) {
181 showErrorMessage(IdeBundle.message("error.please.open.url.manually", url, ApplicationNamesInfo.getInstance().getProductName()),
182 IdeBundle.message("title.browser.path.not.found"));
183 return;
186 // We do not need to check browserPath under Win32
188 launchBrowser(url, command);
191 private static GeneralSettings getGeneralSettingsInstance() {
192 final GeneralSettings settings = GeneralSettings.getInstance();
193 if (settings != null) return settings;
194 return new GeneralSettings();
197 public static void launchBrowser(String url, String name) {
198 if (url.startsWith("jar:")) {
199 VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
200 if (file == null || !(file.getFileSystem() instanceof JarFileSystem)) return;
201 url = extractFiles(file);
202 if (url == null) return;
204 if (canStartDefaultBrowser() && isUseDefaultBrowser()) {
205 launchBrowser(url, getDefaultBrowserCommand());
207 else {
208 launchBrowserUsingStandardWay(url);
212 private static String extractFiles(VirtualFile file) {
213 try {
214 JarFileSystem jarFileSystem = (JarFileSystem)file.getFileSystem();
215 VirtualFile jarVirtualFile = jarFileSystem.getVirtualFileForJar(file);
217 String targetFilePath = file.getPath();
218 String targetFileRelativePath = targetFilePath.substring(
219 targetFilePath.indexOf(JarFileSystem.JAR_SEPARATOR) + JarFileSystem.JAR_SEPARATOR.length());
221 String jarVirtualFileLocationHash = jarVirtualFile.getName() + Integer.toHexString(jarVirtualFile.getUrl().hashCode());
222 final File outputDir = new File(getExtractedFilesDir(), jarVirtualFileLocationHash);
224 final String currentTimestamp = String.valueOf(new File(jarVirtualFile.getPath()).lastModified());
225 final File timestampFile = new File(outputDir, ".idea.timestamp");
227 String previousTimestamp = null;
228 if (timestampFile.exists()) {
229 previousTimestamp = new String(FileUtil.loadFileText(timestampFile));
232 if (!currentTimestamp.equals(previousTimestamp)) {
233 ConfirmExtractDialog dialog = new ConfirmExtractDialog();
234 if (dialog.isToBeShown()) {
235 dialog.show();
236 if (!dialog.isOK()) return null;
239 final ZipFile jarFile = jarFileSystem.getJarFile(file);
240 ZipEntry entry = jarFile.getEntry(targetFileRelativePath);
241 if (entry == null) return null;
242 InputStream is = jarFile.getInputStream(entry);
243 try {
244 ZipUtil.extractEntry(entry, is, outputDir);
246 finally {
247 is.close();
250 ApplicationManager.getApplication().invokeLater(new Runnable() {
251 public void run() {
252 new Task.Backgroundable(null, "Extracting files...", true) {
253 public void run(@NotNull final ProgressIndicator indicator) {
254 final int size = jarFile.size();
255 final int[] counter = new int[]{0};
257 class MyFilter implements FilenameFilter {
258 private final Set<File> myImportantDirs = new HashSet<File>(
259 Arrays.asList(outputDir, new File(outputDir, "resources")));
260 private final boolean myImportantOnly;
262 private MyFilter(boolean importantOnly) {
263 myImportantOnly = importantOnly;
266 public boolean accept(File dir, String name) {
267 indicator.checkCanceled();
268 boolean result = myImportantOnly == myImportantDirs.contains(dir);
269 if (result) {
270 indicator.setFraction(((double)counter[0]) / size);
271 counter[0]++;
273 return result;
277 try {
278 ZipUtil.extract(jarFile, outputDir, new MyFilter(true));
279 ZipUtil.extract(jarFile, outputDir, new MyFilter(false));
280 FileUtil.writeToFile(timestampFile, currentTimestamp.getBytes());
282 catch (IOException ignore) {
285 }.queue();
290 return VfsUtil.pathToUrl(FileUtil.toSystemIndependentName(new File(outputDir, targetFileRelativePath).getPath()));
292 catch (IOException e) {
293 LOG.warn(e);
294 Messages.showErrorDialog("Cannot extract files: " + e.getMessage(), "Error");
295 return null;
299 public static void clearExtractedFiles() {
300 FileUtil.delete(getExtractedFilesDir());
303 private static File getExtractedFilesDir() {
304 return new File(PathManager.getSystemPath(), "ExtractedFiles");
307 public static void launchBrowser(@NonNls final String url) {
308 launchBrowser(url, (String)null);
311 @NonNls
312 private static String[] getDefaultBrowserCommand() {
313 if (SystemInfo.isWindows9x) {
314 return new String[]{"command.com", "/c", "start"};
316 else if (SystemInfo.isWindows) {
317 return new String[]{"cmd.exe", "/c", "start"};
319 else if (SystemInfo.isMac) {
320 return new String[]{"open"};
322 else if (SystemInfo.isUnix) {
323 return new String[]{"mozilla"};
325 else {
326 return null;
330 public static boolean canStartDefaultBrowser() {
331 if (SystemInfo.isMac) {
332 return true;
335 if (SystemInfo.isWindows) {
336 return true;
339 return false;
342 private static class ConfirmExtractDialog extends OptionsDialog {
343 private ConfirmExtractDialog() {
344 super(null);
345 setTitle("Confirmation");
346 init();
349 protected boolean isToBeShown() {
350 return getGeneralSettingsInstance().isConfirmExtractFiles();
353 protected void setToBeShown(boolean value, boolean onOk) {
354 getGeneralSettingsInstance().setConfirmExtractFiles(value);
357 protected boolean shouldSaveOptionsOnCancel() {
358 return true;
361 protected Action[] createActions() {
362 setOKButtonText(CommonBundle.getYesButtonText());
363 return new Action[] {getOKAction(), getCancelAction()};
366 protected JComponent createCenterPanel() {
367 JPanel panel = new JPanel(new BorderLayout());
368 String message = "The files are inside an archive, do you want them to be extracted?";
369 JLabel label = new JLabel(message);
371 label.setIconTextGap(10);
372 label.setIcon(Messages.getQuestionIcon());
374 panel.add(label, BorderLayout.CENTER);
375 panel.add(Box.createVerticalStrut(10), BorderLayout.SOUTH);
377 return panel;