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
;
39 import java
.util
.Arrays
;
40 import java
.util
.HashSet
;
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
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("");
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
) {
86 URL curl
= BrowserUtil
.getURL(url
);
89 final String urlString
= curl
.toString();
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
) + "\"";
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
+ "\"";
104 commandLine
[commandLine
.length
- 1] = urlString
.replaceAll(" ", "%20");
107 Runtime
.getRuntime().exec(commandLine
);
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>");
130 return VfsUtil
.pathToUrl(redirect
.getAbsolutePath());
133 private static boolean isUseDefaultBrowser() {
134 Application application
= ApplicationManager
.getApplication();
135 if (application
== null) {
139 return getGeneralSettingsInstance().isUseDefaultBrowser();
143 private static void showErrorMessage(final String message
, final String title
) {
144 final Application app
= ApplicationManager
.getApplication();
146 return; // Not started yet. Not able to show message up. (Could happen in License panel under Linux).
149 Runnable runnable
= new Runnable() {
151 Messages
.showMessageDialog(message
,
153 Messages
.getErrorIcon());
157 if (app
.isDispatchThread()) {
161 app
.invokeLater(runnable
, ModalityState
.NON_MODAL
);
165 private static void launchBrowserUsingStandardWay(final String url
) {
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"));
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"));
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());
208 launchBrowserUsingStandardWay(url
);
212 private static String
extractFiles(VirtualFile file
) {
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()) {
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
);
244 ZipUtil
.extractEntry(entry
, is
, outputDir
);
250 ApplicationManager
.getApplication().invokeLater(new Runnable() {
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
);
270 indicator
.setFraction(((double)counter
[0]) / size
);
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
) {
290 return VfsUtil
.pathToUrl(FileUtil
.toSystemIndependentName(new File(outputDir
, targetFileRelativePath
).getPath()));
292 catch (IOException e
) {
294 Messages
.showErrorDialog("Cannot extract files: " + e
.getMessage(), "Error");
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);
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"};
330 public static boolean canStartDefaultBrowser() {
331 if (SystemInfo
.isMac
) {
335 if (SystemInfo
.isWindows
) {
342 private static class ConfirmExtractDialog
extends OptionsDialog
{
343 private ConfirmExtractDialog() {
345 setTitle("Confirmation");
349 protected boolean isToBeShown() {
350 return getGeneralSettingsInstance().isConfirmExtractFiles();
353 protected void setToBeShown(boolean value
, boolean onOk
) {
354 getGeneralSettingsInstance().setConfirmExtractFiles(value
);
357 protected boolean shouldSaveOptionsOnCancel() {
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
);