2 * Copyright 2000-2009 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
.diagnostic
.Logger
;
21 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
22 import com
.intellij
.openapi
.progress
.Task
;
23 import com
.intellij
.openapi
.ui
.Messages
;
24 import com
.intellij
.openapi
.util
.SystemInfo
;
25 import com
.intellij
.openapi
.util
.io
.FileUtil
;
26 import com
.intellij
.openapi
.vfs
.JarFileSystem
;
27 import com
.intellij
.openapi
.vfs
.VfsUtil
;
28 import com
.intellij
.openapi
.vfs
.VirtualFile
;
29 import com
.intellij
.openapi
.vfs
.VirtualFileManager
;
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 commandLine
[commandLine
.length
- 1] = escapeUrl(urlString
);
102 Runtime
.getRuntime().exec(commandLine
);
105 showErrorMessage(IdeBundle
.message("error.malformed.url", url
), CommonBundle
.getErrorTitle());
108 catch (final IOException e
) {
109 showErrorMessage(IdeBundle
.message("error.cannot.start.browser", e
.getMessage()),
110 CommonBundle
.getErrorTitle());
115 * This method works around Windows 'start' command behaivor of dropping anchors from the url for local urls.
117 private static String
redirectUrl(String url
, @NonNls String urlString
) throws IOException
{
118 if (url
.indexOf('&') == -1 && (!urlString
.startsWith("file:") || urlString
.indexOf("#") == -1)) return urlString
;
120 File redirect
= File
.createTempFile("redirect", ".html");
121 redirect
.deleteOnExit();
122 FileWriter writer
= new FileWriter(redirect
);
123 writer
.write("<html><head></head><body><script type=\"text/javascript\">window.location=\"" + url
+ "\";</script></body></html>");
125 return VfsUtil
.pathToUrl(redirect
.getAbsolutePath());
128 private static boolean isUseDefaultBrowser() {
129 Application application
= ApplicationManager
.getApplication();
130 if (application
== null) {
134 return getGeneralSettingsInstance().isUseDefaultBrowser();
138 private static void showErrorMessage(final String message
, final String title
) {
139 final Application app
= ApplicationManager
.getApplication();
141 return; // Not started yet. Not able to show message up. (Could happen in License panel under Linux).
144 Runnable runnable
= new Runnable() {
146 Messages
.showMessageDialog(message
,
148 Messages
.getErrorIcon());
152 if (app
.isDispatchThread()) {
156 app
.invokeLater(runnable
, ModalityState
.NON_MODAL
);
160 private static void launchBrowserUsingStandardWay(final String url
) {
163 String browserPath
= getGeneralSettingsInstance().getBrowserPath();
164 if (browserPath
== null || browserPath
.trim().length() == 0) {
165 showErrorMessage(IdeBundle
.message("error.please.specify.path.to.web.browser"),
166 IdeBundle
.message("title.browser.not.found"));
170 command
= new String
[]{browserPath
};
172 catch (NullPointerException e
) {
173 // todo: fix the possible problem on startup, see SCR #35066
174 command
= getDefaultBrowserCommand();
175 if (command
== null) {
176 showErrorMessage(IdeBundle
.message("error.please.open.url.manually", url
, ApplicationNamesInfo
.getInstance().getProductName()),
177 IdeBundle
.message("title.browser.path.not.found"));
181 // We do not need to check browserPath under Win32
183 launchBrowser(url
, command
);
186 private static GeneralSettings
getGeneralSettingsInstance() {
187 final GeneralSettings settings
= GeneralSettings
.getInstance();
188 if (settings
!= null) return settings
;
189 return new GeneralSettings();
192 public static void launchBrowser(String url
, String name
) {
193 if (url
.startsWith("jar:")) {
194 url
= extractFiles(url
);
195 if (url
== null) return;
197 if (canStartDefaultBrowser() && isUseDefaultBrowser()) {
198 launchBrowser(url
, getDefaultBrowserCommand());
201 launchBrowserUsingStandardWay(url
);
206 public static String
escapeUrl(@NotNull @NonNls String url
) {
207 if (SystemInfo
.isWindows
) {
208 return url
.indexOf(' ') > 0?
"\"" + url
+ "\"" : url
;
211 return url
.replaceAll(" ", "%20");
215 private static String
extractFiles(String url
) {
217 int sharpPos
= url
.indexOf('#');
219 if (sharpPos
!= -1) {
220 anchor
= url
.substring(sharpPos
);
221 url
= url
.substring(0, sharpPos
);
224 VirtualFile file
= VirtualFileManager
.getInstance().findFileByUrl(url
);
225 if (file
== null || !(file
.getFileSystem() instanceof JarFileSystem
)) return null;
227 JarFileSystem jarFileSystem
= (JarFileSystem
)file
.getFileSystem();
228 VirtualFile jarVirtualFile
= jarFileSystem
.getVirtualFileForJar(file
);
230 String targetFilePath
= file
.getPath();
231 String targetFileRelativePath
= targetFilePath
.substring(
232 targetFilePath
.indexOf(JarFileSystem
.JAR_SEPARATOR
) + JarFileSystem
.JAR_SEPARATOR
.length());
234 String jarVirtualFileLocationHash
= jarVirtualFile
.getName() + Integer
.toHexString(jarVirtualFile
.getUrl().hashCode());
235 final File outputDir
= new File(getExtractedFilesDir(), jarVirtualFileLocationHash
);
237 final String currentTimestamp
= String
.valueOf(new File(jarVirtualFile
.getPath()).lastModified());
238 final File timestampFile
= new File(outputDir
, ".idea.timestamp");
240 String previousTimestamp
= null;
241 if (timestampFile
.exists()) {
242 previousTimestamp
= new String(FileUtil
.loadFileText(timestampFile
));
245 if (!currentTimestamp
.equals(previousTimestamp
)) {
246 ConfirmExtractDialog dialog
= new ConfirmExtractDialog();
247 if (dialog
.isToBeShown()) {
249 if (!dialog
.isOK()) return null;
252 final ZipFile jarFile
= jarFileSystem
.getJarFile(file
);
253 ZipEntry entry
= jarFile
.getEntry(targetFileRelativePath
);
254 if (entry
== null) return null;
255 InputStream is
= jarFile
.getInputStream(entry
);
257 ZipUtil
.extractEntry(entry
, is
, outputDir
);
263 ApplicationManager
.getApplication().invokeLater(new Runnable() {
265 new Task
.Backgroundable(null, "Extracting files...", true) {
266 public void run(@NotNull final ProgressIndicator indicator
) {
267 final int size
= jarFile
.size();
268 final int[] counter
= new int[]{0};
270 class MyFilter
implements FilenameFilter
{
271 private final Set
<File
> myImportantDirs
= new HashSet
<File
>(
272 Arrays
.asList(outputDir
, new File(outputDir
, "resources")));
273 private final boolean myImportantOnly
;
275 private MyFilter(boolean importantOnly
) {
276 myImportantOnly
= importantOnly
;
279 public boolean accept(File dir
, String name
) {
280 indicator
.checkCanceled();
281 boolean result
= myImportantOnly
== myImportantDirs
.contains(dir
);
283 indicator
.setFraction(((double)counter
[0]) / size
);
291 ZipUtil
.extract(jarFile
, outputDir
, new MyFilter(true));
292 ZipUtil
.extract(jarFile
, outputDir
, new MyFilter(false));
293 FileUtil
.writeToFile(timestampFile
, currentTimestamp
.getBytes());
295 catch (IOException ignore
) {
303 return VfsUtil
.pathToUrl(FileUtil
.toSystemIndependentName(new File(outputDir
, targetFileRelativePath
).getPath())) + anchor
;
305 catch (IOException e
) {
307 Messages
.showErrorDialog("Cannot extract files: " + e
.getMessage(), "Error");
312 public static void clearExtractedFiles() {
313 FileUtil
.delete(getExtractedFilesDir());
316 private static File
getExtractedFilesDir() {
317 return new File(PathManager
.getSystemPath(), "ExtractedFiles");
320 public static void launchBrowser(@NonNls final String url
) {
321 launchBrowser(url
, (String
)null);
325 private static String
[] getDefaultBrowserCommand() {
326 if (SystemInfo
.isWindows9x
) {
327 return new String
[]{"command.com", "/c", "start"};
329 else if (SystemInfo
.isWindows
) {
330 return new String
[]{"cmd.exe", "/c", "start"};
332 else if (SystemInfo
.isMac
) {
333 return new String
[]{"open"};
335 else if (SystemInfo
.isUnix
) {
336 return new String
[]{"mozilla"};
343 public static boolean canStartDefaultBrowser() {
344 if (SystemInfo
.isMac
) {
348 if (SystemInfo
.isWindows
) {
355 private static class ConfirmExtractDialog
extends OptionsDialog
{
356 private ConfirmExtractDialog() {
358 setTitle("Confirmation");
362 protected boolean isToBeShown() {
363 return getGeneralSettingsInstance().isConfirmExtractFiles();
366 protected void setToBeShown(boolean value
, boolean onOk
) {
367 getGeneralSettingsInstance().setConfirmExtractFiles(value
);
370 protected boolean shouldSaveOptionsOnCancel() {
374 protected Action
[] createActions() {
375 setOKButtonText(CommonBundle
.getYesButtonText());
376 return new Action
[]{getOKAction(), getCancelAction()};
379 protected JComponent
createCenterPanel() {
380 JPanel panel
= new JPanel(new BorderLayout());
381 String message
= "The files are inside an archive, do you want them to be extracted?";
382 JLabel label
= new JLabel(message
);
384 label
.setIconTextGap(10);
385 label
.setIcon(Messages
.getQuestionIcon());
387 panel
.add(label
, BorderLayout
.CENTER
);
388 panel
.add(Box
.createVerticalStrut(10), BorderLayout
.SOUTH
);