Merge remote-tracking branch 'origin/master' into IDEA-CR-10038
[idea/community.git] / platform / platform-impl / src / com / intellij / util / Urls.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.util;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.SystemInfoRt;
20 import com.intellij.openapi.util.io.FileUtil;
21 import com.intellij.openapi.util.io.FileUtilRt;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.openapi.vfs.StandardFileSystems;
24 import com.intellij.openapi.vfs.VfsUtilCore;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.util.io.URLUtil;
27 import gnu.trove.TObjectHashingStrategy;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.net.URI;
32 import java.net.URISyntaxException;
33 import java.util.Collection;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36
37 public final class Urls {
38   private static final Logger LOG = Logger.getInstance(Urls.class);
39
40   // about ";" see WEB-100359
41   private static final Pattern URI_PATTERN = Pattern.compile("^([^:/?#]+):(//)?([^/?#]*)([^?#;]*)(.*)");
42
43   @NotNull
44   public static Url newUri(@NotNull String scheme, @NotNull String path) {
45     return new UrlImpl(scheme, null, path);
46   }
47
48   @NotNull
49   public static Url newLocalFileUrl(@NotNull String path) {
50     return new LocalFileUrl(FileUtilRt.toSystemIndependentName(path));
51   }
52
53   @NotNull
54   public static Url newLocalFileUrl(@NotNull VirtualFile file) {
55     return new LocalFileUrl(file.getPath());
56   }
57
58   @NotNull
59   public static Url newFromEncoded(@NotNull String url) {
60     Url result = parseEncoded(url);
61     LOG.assertTrue(result != null, url);
62     return result;
63   }
64
65   @Nullable
66   public static Url parseEncoded(@NotNull String url) {
67     return parse(url, false);
68   }
69
70   @NotNull
71   public static Url newHttpUrl(@NotNull String authority, @Nullable String path) {
72     return newUrl("http", authority, path);
73   }
74
75   @NotNull
76   public static Url newHttpUrl(@NotNull String authority, @Nullable String path, @Nullable String parameters) {
77     return new UrlImpl("http", authority, path, parameters);
78   }
79
80   @NotNull
81   public static Url newUrl(@NotNull String scheme, @NotNull String authority, @Nullable String path) {
82     return new UrlImpl(scheme, authority, path);
83   }
84
85   /**
86    * Url will not be normalized (see {@link VfsUtilCore#toIdeaUrl(String)}), parsed as is
87    */
88   @NotNull
89   public static Url newFromIdea(@NotNull CharSequence url) {
90     Url result = parseFromIdea(url);
91     LOG.assertTrue(result != null, url);
92     return result;
93   }
94
95   // java.net.URI.create cannot parse "file:///Test Stuff" - but you don't need to worry about it - this method is aware
96   @Nullable
97   public static Url parseFromIdea(@NotNull CharSequence url) {
98     for (int i = 0, n = url.length(); i < n; i++) {
99       char c = url.charAt(i);
100       if (c == ':') {
101         // file:// or dart:core/foo
102         return parseUrl(url);
103       }
104       else if (c == '/' || c == '\\') {
105         return newLocalFileUrl(url.toString());
106       }
107     }
108     return newLocalFileUrl(url.toString());
109   }
110
111   @Nullable
112   public static Url parse(@NotNull String url, boolean asLocalIfNoScheme) {
113     if (url.isEmpty()) {
114       return null;
115     }
116
117     if (asLocalIfNoScheme && !URLUtil.containsScheme(url)) {
118       // nodejs debug - files only in local filesystem
119       return newLocalFileUrl(url);
120     }
121     return parseUrl(VfsUtilCore.toIdeaUrl(url));
122   }
123
124   @Nullable
125   public static URI parseAsJavaUriWithoutParameters(@NotNull String url) {
126     Url asUrl = parseUrl(url);
127     if (asUrl == null) {
128       return null;
129     }
130
131     try {
132       return toUriWithoutParameters(asUrl);
133     }
134     catch (Exception e) {
135       LOG.info("Cannot parse url " + url, e);
136       return null;
137     }
138   }
139
140   @Nullable
141   private static Url parseUrl(@NotNull CharSequence url) {
142     CharSequence urlToParse;
143     if (StringUtil.startsWith(url, "jar:file://")) {
144       urlToParse = url.subSequence("jar:".length(), url.length());
145     }
146     else {
147       urlToParse = url;
148     }
149
150     Matcher matcher = URI_PATTERN.matcher(urlToParse);
151     if (!matcher.matches()) {
152       return null;
153     }
154     String scheme = matcher.group(1);
155     if (urlToParse != url) {
156       scheme = "jar:" + scheme;
157     }
158
159     String authority = StringUtil.nullize(matcher.group(3));
160     String path = StringUtil.nullize(matcher.group(4));
161     boolean hasUrlSeparator = !StringUtil.isEmpty(matcher.group(2));
162     if (authority == null) {
163       if (hasUrlSeparator) {
164         authority = "";
165       }
166     }
167     else if (StandardFileSystems.FILE_PROTOCOL.equals(scheme) || !hasUrlSeparator) {
168       path = path == null ? authority : (authority + path);
169       authority = hasUrlSeparator ? "" : null;
170     }
171
172     // canonicalize only if authority is not empty or file url - we should not canonicalize URL with unknown scheme (webpack:///./modules/flux-orion-plugin/fluxPlugin.ts)
173     if (path != null && (!StringUtil.isEmpty(authority) || StandardFileSystems.FILE_PROTOCOL.equals(scheme))) {
174       path = FileUtil.toCanonicalUriPath(path);
175     }
176     return new UrlImpl(scheme, authority, path, matcher.group(5));
177   }
178
179   @NotNull
180   public static Url newFromVirtualFile(@NotNull VirtualFile file) {
181     if (file.isInLocalFileSystem()) {
182       return newUri(file.getFileSystem().getProtocol(), file.getPath());
183     }
184     else {
185       Url url = parseUrl(file.getUrl());
186       return url == null ? new UrlImpl(file.getPath()) : url;
187     }
188   }
189
190   public static boolean equalsIgnoreParameters(@NotNull Url url, @NotNull Collection<Url> urls) {
191     return equalsIgnoreParameters(url, urls, true);
192   }
193
194   public static boolean equalsIgnoreParameters(@NotNull Url url, @NotNull Collection<Url> urls, boolean caseSensitive) {
195     for (Url otherUrl : urls) {
196       if (equals(url, otherUrl, caseSensitive, true)) {
197         return true;
198       }
199     }
200     return false;
201   }
202
203   public static boolean equalsIgnoreParameters(@NotNull Url url, @NotNull VirtualFile file) {
204     if (file.isInLocalFileSystem()) {
205       return url.isInLocalFileSystem() && (SystemInfoRt.isFileSystemCaseSensitive
206                                            ? url.getPath().equals(file.getPath()) :
207                                            url.getPath().equalsIgnoreCase(file.getPath()));
208     }
209     else if (url.isInLocalFileSystem()) {
210       return false;
211     }
212
213     Url fileUrl = parseUrl(file.getUrl());
214     return fileUrl != null && fileUrl.equalsIgnoreParameters(url);
215   }
216
217   public static boolean equals(@Nullable Url url1, @Nullable Url url2, boolean caseSensitive, boolean ignoreParameters) {
218     if (url1 == null || url2 == null){
219       return url1 == url2;
220     }
221
222     Url o1 = ignoreParameters ? url1.trimParameters() : url1;
223     Url o2 = ignoreParameters ? url2.trimParameters() : url2;
224     return caseSensitive ? o1.equals(o2) : o1.equalsIgnoreCase(o2);
225   }
226
227   @NotNull
228   public static URI toUriWithoutParameters(@NotNull Url url) {
229     try {
230       String externalPath = url.getPath();
231       boolean inLocalFileSystem = url.isInLocalFileSystem();
232       if (inLocalFileSystem && SystemInfoRt.isWindows && externalPath.charAt(0) != '/') {
233         externalPath = '/' + externalPath;
234       }
235       return new URI(inLocalFileSystem ? "file" : url.getScheme(), inLocalFileSystem ? "" : url.getAuthority(), externalPath, null, null);
236     }
237     catch (URISyntaxException e) {
238       throw new RuntimeException(e);
239     }
240   }
241
242   public static TObjectHashingStrategy<Url> getCaseInsensitiveUrlHashingStrategy() {
243     return CaseInsensitiveUrlHashingStrategy.INSTANCE;
244   }
245
246   private static final class CaseInsensitiveUrlHashingStrategy implements TObjectHashingStrategy<Url> {
247     private static final TObjectHashingStrategy<Url> INSTANCE = new CaseInsensitiveUrlHashingStrategy();
248
249     @Override
250     public int computeHashCode(Url url) {
251       return url == null ? 0 : url.hashCodeCaseInsensitive();
252     }
253
254     @Override
255     public boolean equals(Url url1, Url url2) {
256       return Urls.equals(url1, url2, false, false);
257     }
258   }
259 }