java.lang.IllegalStateException: unsupported protocol: 'mailto'
[idea/community.git] / platform / lang-impl / src / com / intellij / openapi / paths / WebReferencesAnnotatorBase.java
1 /*
2  * Copyright 2000-2015 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.openapi.paths;
17
18 import com.intellij.codeHighlighting.HighlightDisplayLevel;
19 import com.intellij.codeInsight.intention.IntentionAction;
20 import com.intellij.lang.annotation.Annotation;
21 import com.intellij.lang.annotation.AnnotationHolder;
22 import com.intellij.lang.annotation.ExternalAnnotator;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.util.TextRange;
25 import com.intellij.psi.PsiAnchor;
26 import com.intellij.psi.PsiElement;
27 import com.intellij.psi.PsiFile;
28 import com.intellij.psi.PsiReference;
29 import com.intellij.util.containers.HashMap;
30 import com.intellij.util.io.HttpRequests;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import java.io.IOException;
35 import java.net.HttpURLConnection;
36 import java.net.URLConnection;
37 import java.net.UnknownHostException;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.Map;
41
42 /**
43  * @author Eugene.Kudelevsky
44  */
45 public abstract class WebReferencesAnnotatorBase extends ExternalAnnotator<WebReferencesAnnotatorBase.MyInfo[], WebReferencesAnnotatorBase.MyInfo[]> {
46   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.paths.WebReferencesAnnotatorBase");
47
48   private final Map<String, MyFetchCacheEntry> myFetchCache = new HashMap<String, MyFetchCacheEntry>();
49   private final Object myFetchCacheLock = new Object();
50   private static final long FETCH_CACHE_TIMEOUT = 10000;
51
52   protected static final WebReference[] EMPTY_ARRAY = new WebReference[0];
53
54   @NotNull
55   protected abstract WebReference[] collectWebReferences(@NotNull PsiFile file);
56
57   @Nullable
58   protected static WebReference lookForWebReference(@NotNull PsiElement element) {
59     return lookForWebReference(Arrays.asList(element.getReferences()));
60   }
61
62   @SuppressWarnings("unchecked")
63   @Nullable
64   private static WebReference lookForWebReference(Collection<PsiReference> references) {
65     for (PsiReference reference : references) {
66       if (reference instanceof WebReference) {
67         return (WebReference)reference;
68       }
69       else if (reference instanceof PsiDynaReference) {
70         final WebReference webReference = lookForWebReference(((PsiDynaReference)reference).getReferences());
71         if (webReference != null) {
72           return webReference;
73         }
74       }
75     }
76     return null;
77   }
78
79   @Override
80   public MyInfo[] collectInformation(@NotNull PsiFile file) {
81     final WebReference[] references = collectWebReferences(file);
82     final MyInfo[] infos = new MyInfo[references.length];
83
84     for (int i = 0; i < infos.length; i++) {
85       final WebReference reference = references[i];
86       infos[i] = new MyInfo(PsiAnchor.create(reference.getElement()), reference.getRangeInElement(), reference.getValue());
87     }
88     return infos;
89   }
90
91   @Override
92   public MyInfo[] doAnnotate(MyInfo[] infos) {
93     final MyFetchResult[] fetchResults = new MyFetchResult[infos.length];
94     for (int i = 0; i < fetchResults.length; i++) {
95       fetchResults[i] = checkUrl(infos[i].myUrl);
96     }
97
98     boolean containsAvailableHosts = false;
99     
100     for (MyFetchResult fetchResult : fetchResults) {
101       if (fetchResult != MyFetchResult.UNKNOWN_HOST) {
102         containsAvailableHosts = true;
103       }
104     }
105
106     for (int i = 0; i < fetchResults.length; i++) {
107       final MyFetchResult result = fetchResults[i];
108
109       // if all hosts are not available, internet connection may be disabled, so it's better to not report warnings for unknown hosts
110       if (result == MyFetchResult.OK || (!containsAvailableHosts && result == MyFetchResult.UNKNOWN_HOST)) {
111         infos[i].myResult = true;
112       }
113     }
114
115     return infos;
116   }
117
118   @Override
119   public void apply(@NotNull PsiFile file, MyInfo[] infos, @NotNull AnnotationHolder holder) {
120     if (infos.length == 0) {
121       return;
122     }
123
124     final HighlightDisplayLevel displayLevel = getHighlightDisplayLevel(file);
125
126     for (MyInfo info : infos) {
127       if (!info.myResult) {
128         final PsiElement element = info.myAnchor.retrieve();
129         if (element != null) {
130           final int start = element.getTextRange().getStartOffset();
131           final TextRange range = new TextRange(start + info.myRangeInElement.getStartOffset(),
132                                                 start + info.myRangeInElement.getEndOffset());
133           final String message = getErrorMessage(info.myUrl);
134
135           final Annotation annotation;
136
137           if (displayLevel == HighlightDisplayLevel.ERROR) {
138             annotation = holder.createErrorAnnotation(range, message);
139           }
140           else if (displayLevel == HighlightDisplayLevel.WARNING) {
141             annotation = holder.createWarningAnnotation(range, message);
142           }
143           else if (displayLevel == HighlightDisplayLevel.WEAK_WARNING) {
144             annotation = holder.createInfoAnnotation(range, message);
145           }
146           else {
147             annotation = holder.createWarningAnnotation(range, message);
148           }
149
150           for (IntentionAction action : getQuickFixes()) {
151             annotation.registerFix(action);
152           }
153         }
154       }
155     }
156   }
157   
158   @NotNull
159   protected abstract String getErrorMessage(@NotNull String url);
160
161   @NotNull
162   protected IntentionAction[] getQuickFixes() {
163     return IntentionAction.EMPTY_ARRAY;
164   }
165   
166   @NotNull
167   protected abstract HighlightDisplayLevel getHighlightDisplayLevel(@NotNull PsiElement context);
168
169   @NotNull
170   private MyFetchResult checkUrl(String url) {
171     synchronized (myFetchCacheLock) {
172       final MyFetchCacheEntry entry = myFetchCache.get(url);
173       final long currentTime = System.currentTimeMillis();
174
175       if (entry != null && currentTime - entry.getTime() < FETCH_CACHE_TIMEOUT) {
176         return entry.getFetchResult();
177       }
178
179       final MyFetchResult fetchResult = doCheckUrl(url);
180       myFetchCache.put(url, new MyFetchCacheEntry(currentTime, fetchResult));
181       return fetchResult;
182     }
183   }
184
185   private static MyFetchResult doCheckUrl(@NotNull String url) {
186     if (url.startsWith("mailto")) {
187       return MyFetchResult.OK;
188     }
189
190     try {
191       return HttpRequests.request(url).connectTimeout(3000).readTimeout(3000).connect(new HttpRequests.RequestProcessor<MyFetchResult>() {
192         @Override
193         public MyFetchResult process(@NotNull HttpRequests.Request request) throws IOException {
194           URLConnection connection = request.getConnection();
195           int code = connection instanceof HttpURLConnection ? ((HttpURLConnection)connection).getResponseCode() : 0;
196           return code == 200 || code == 408 ? MyFetchResult.OK : MyFetchResult.NONEXISTENCE;
197         }
198       });
199     }
200     catch (UnknownHostException e) {
201       LOG.info(e);
202       return MyFetchResult.UNKNOWN_HOST;
203     }
204     catch (IOException e) {
205       LOG.info(e);
206       return MyFetchResult.OK;
207     }
208     catch (IllegalArgumentException e) {
209       LOG.debug(e);
210       return MyFetchResult.OK;
211     }
212   }
213
214   private static class MyFetchCacheEntry {
215     private final long myTime;
216     private final MyFetchResult myFetchResult;
217
218     private MyFetchCacheEntry(long time, @NotNull MyFetchResult fetchResult) {
219       myTime = time;
220       myFetchResult = fetchResult;
221     }
222
223     public long getTime() {
224       return myTime;
225     }
226
227     @NotNull
228     public MyFetchResult getFetchResult() {
229       return myFetchResult;
230     }
231   }
232   
233   private static enum MyFetchResult {
234     OK, UNKNOWN_HOST, NONEXISTENCE
235   }
236
237   protected static class MyInfo {
238     final PsiAnchor myAnchor;
239     final String myUrl;
240     final TextRange myRangeInElement;
241
242     volatile boolean myResult;
243
244     private MyInfo(PsiAnchor anchor, TextRange rangeInElement, String url) {
245       myAnchor = anchor;
246       myRangeInElement = rangeInElement;
247       myUrl = url;
248     }
249   }
250 }