2 * Copyright 2000-2015 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.openapi.paths;
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;
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;
43 * @author Eugene.Kudelevsky
45 public abstract class WebReferencesAnnotatorBase extends ExternalAnnotator<WebReferencesAnnotatorBase.MyInfo[], WebReferencesAnnotatorBase.MyInfo[]> {
46 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.paths.WebReferencesAnnotatorBase");
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;
52 protected static final WebReference[] EMPTY_ARRAY = new WebReference[0];
55 protected abstract WebReference[] collectWebReferences(@NotNull PsiFile file);
58 protected static WebReference lookForWebReference(@NotNull PsiElement element) {
59 return lookForWebReference(Arrays.asList(element.getReferences()));
62 @SuppressWarnings("unchecked")
64 private static WebReference lookForWebReference(Collection<PsiReference> references) {
65 for (PsiReference reference : references) {
66 if (reference instanceof WebReference) {
67 return (WebReference)reference;
69 else if (reference instanceof PsiDynaReference) {
70 final WebReference webReference = lookForWebReference(((PsiDynaReference)reference).getReferences());
71 if (webReference != null) {
80 public MyInfo[] collectInformation(@NotNull PsiFile file) {
81 final WebReference[] references = collectWebReferences(file);
82 final MyInfo[] infos = new MyInfo[references.length];
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());
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);
98 boolean containsAvailableHosts = false;
100 for (MyFetchResult fetchResult : fetchResults) {
101 if (fetchResult != MyFetchResult.UNKNOWN_HOST) {
102 containsAvailableHosts = true;
106 for (int i = 0; i < fetchResults.length; i++) {
107 final MyFetchResult result = fetchResults[i];
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;
119 public void apply(@NotNull PsiFile file, MyInfo[] infos, @NotNull AnnotationHolder holder) {
120 if (infos.length == 0) {
124 final HighlightDisplayLevel displayLevel = getHighlightDisplayLevel(file);
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);
135 final Annotation annotation;
137 if (displayLevel == HighlightDisplayLevel.ERROR) {
138 annotation = holder.createErrorAnnotation(range, message);
140 else if (displayLevel == HighlightDisplayLevel.WARNING) {
141 annotation = holder.createWarningAnnotation(range, message);
143 else if (displayLevel == HighlightDisplayLevel.WEAK_WARNING) {
144 annotation = holder.createInfoAnnotation(range, message);
147 annotation = holder.createWarningAnnotation(range, message);
150 for (IntentionAction action : getQuickFixes()) {
151 annotation.registerFix(action);
159 protected abstract String getErrorMessage(@NotNull String url);
162 protected IntentionAction[] getQuickFixes() {
163 return IntentionAction.EMPTY_ARRAY;
167 protected abstract HighlightDisplayLevel getHighlightDisplayLevel(@NotNull PsiElement context);
170 private MyFetchResult checkUrl(String url) {
171 synchronized (myFetchCacheLock) {
172 final MyFetchCacheEntry entry = myFetchCache.get(url);
173 final long currentTime = System.currentTimeMillis();
175 if (entry != null && currentTime - entry.getTime() < FETCH_CACHE_TIMEOUT) {
176 return entry.getFetchResult();
179 final MyFetchResult fetchResult = doCheckUrl(url);
180 myFetchCache.put(url, new MyFetchCacheEntry(currentTime, fetchResult));
185 private static MyFetchResult doCheckUrl(@NotNull String url) {
186 if (url.startsWith("mailto")) {
187 return MyFetchResult.OK;
191 return HttpRequests.request(url).connectTimeout(3000).readTimeout(3000).connect(new HttpRequests.RequestProcessor<MyFetchResult>() {
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;
200 catch (UnknownHostException e) {
202 return MyFetchResult.UNKNOWN_HOST;
204 catch (IOException e) {
206 return MyFetchResult.OK;
208 catch (IllegalArgumentException e) {
210 return MyFetchResult.OK;
214 private static class MyFetchCacheEntry {
215 private final long myTime;
216 private final MyFetchResult myFetchResult;
218 private MyFetchCacheEntry(long time, @NotNull MyFetchResult fetchResult) {
220 myFetchResult = fetchResult;
223 public long getTime() {
228 public MyFetchResult getFetchResult() {
229 return myFetchResult;
233 private static enum MyFetchResult {
234 OK, UNKNOWN_HOST, NONEXISTENCE
237 protected static class MyInfo {
238 final PsiAnchor myAnchor;
240 final TextRange myRangeInElement;
242 volatile boolean myResult;
244 private MyInfo(PsiAnchor anchor, TextRange rangeInElement, String url) {
246 myRangeInElement = rangeInElement;