Merge branch 'tree-ui'
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / IconLineMarkerProvider.java
1 /*
2  * Copyright 2000-2009 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.codeInsight.daemon.impl;
17
18 import com.intellij.codeHighlighting.Pass;
19 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
20 import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
21 import com.intellij.codeInsight.daemon.LineMarkerInfo;
22 import com.intellij.codeInsight.daemon.LineMarkerProvider;
23 import com.intellij.openapi.editor.markup.GutterIconRenderer;
24 import com.intellij.openapi.util.Pair;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.psi.*;
27 import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference;
28 import com.intellij.psi.util.InheritanceUtil;
29 import com.intellij.psi.util.PsiTreeUtil;
30 import org.jetbrains.annotations.NonNls;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import javax.swing.*;
35 import java.awt.event.MouseEvent;
36 import java.util.*;
37
38 /**
39  * Shows small (16x16 or less) icons as gutters
40  * Works in places where it's possible to resolve from literal expression
41  * to an icon image
42  *
43  * @author Konstantin Bulenkov
44  */
45 public class IconLineMarkerProvider implements LineMarkerProvider {
46   private static final @NonNls String JAVAX_SWING_ICON = "javax.swing.Icon";
47   private static final int ICON_MAX_WEIGHT = 16;
48   private static final int ICON_MAX_HEIGHT = 16;
49   private static final int ICON_MAX_SIZE = 2 * 1024 * 1024; //2Kb
50   private static final List<String> ICON_EXTS = Arrays.asList("png", "ico", "bmp", "gif", "jpg");
51
52   //TODO: remove old unused icons from the cache
53   private final HashMap<String, Pair<Long, Icon>> iconsCache = new HashMap<String, Pair<Long, Icon>>();
54
55   public LineMarkerInfo getLineMarkerInfo(PsiElement element) {
56     if (! DaemonCodeAnalyzerSettings.getInstance().SHOW_SMALL_ICONS_IN_GUTTER) return null;
57
58     if (element instanceof PsiAssignmentExpression) {
59       final PsiExpression lExpression = ((PsiAssignmentExpression)element).getLExpression();
60       final PsiExpression expr = ((PsiAssignmentExpression)element).getRExpression();
61       if (lExpression instanceof PsiReferenceExpression) {
62         PsiElement var = ((PsiReferenceExpression)lExpression).resolve();
63         if (var instanceof PsiVariable) {
64           return resolveIconInfo(((PsiVariable)var).getType(), expr);
65         }
66       }
67     }
68     else if (element instanceof PsiReturnStatement) {
69       PsiReturnStatement psiReturnStatement = (PsiReturnStatement)element;
70       final PsiExpression value = psiReturnStatement.getReturnValue();
71       final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
72       if (method != null) {
73         final PsiType returnType = method.getReturnType();
74         final LineMarkerInfo<PsiElement> result = resolveIconInfo(returnType, value);
75
76         if (result != null || !isIconClassType(returnType) || value == null) return result;
77
78         if (methodContainsReturnStatementOnly(method)) {
79           for (PsiReference ref : value.getReferences()) {
80             final PsiElement field = ref.resolve();
81             if (field instanceof PsiField) {
82               return resolveIconInfo(returnType, ((PsiField)field).getInitializer(), psiReturnStatement);
83             }
84           }
85         }
86       }
87     }
88     else if (element instanceof PsiVariable) {
89       PsiVariable var = (PsiVariable)element;
90       return resolveIconInfo(var.getType(), var.getInitializer());
91     }
92     return null;
93   }
94
95   private static boolean methodContainsReturnStatementOnly(@NotNull PsiMethod method) {
96     final PsiCodeBlock body = method.getBody();
97     if (body == null || body.getStatements().length != 1) return false;
98
99     return body.getStatements()[0] instanceof PsiReturnStatement;
100   }
101
102   @Nullable
103   private LineMarkerInfo<PsiElement> resolveIconInfo(PsiType type, PsiExpression initializer) {
104     return resolveIconInfo(type, initializer, initializer);
105   }
106
107   @Nullable
108   private LineMarkerInfo<PsiElement> resolveIconInfo(PsiType type, PsiExpression initializer, PsiElement bindingElement) {
109     if (initializer != null && isIconClassType(type)) {
110
111       final List<FileReference> refs = new ArrayList<FileReference>();
112       initializer.accept(new JavaRecursiveElementWalkingVisitor() {
113         @Override
114         public void visitElement(PsiElement element) {
115           if (element instanceof PsiLiteralExpression) {
116             for (PsiReference ref : element.getReferences()) {
117               if (ref instanceof FileReference) {
118                 refs.add((FileReference)ref);
119               }
120             }
121           }
122           super.visitElement(element);
123         }
124       });
125
126       for (FileReference ref : refs) {
127         final PsiFileSystemItem psiFileSystemItem = ref.resolve();
128         VirtualFile file = null;
129         if (psiFileSystemItem == null) {
130           final ResolveResult[] results = ref.multiResolve(false);
131           for (ResolveResult result : results) {
132             final PsiElement element = result.getElement();
133             if (element instanceof PsiBinaryFile) {
134               file = ((PsiFile)element).getVirtualFile();
135               break;
136             }
137           }
138         } else {
139           file = psiFileSystemItem.getVirtualFile();
140         }
141
142         if (file == null || file.isDirectory()
143             || !isIconFileExtension(file.getExtension())
144             || file.getLength() > ICON_MAX_SIZE) continue;
145
146         final Icon icon = getIcon(file);
147
148         if (icon != null) {
149           final GutterIconNavigationHandler<PsiElement> navHandler = new GutterIconNavigationHandler<PsiElement>() {
150             public void navigate(MouseEvent e, PsiElement elt) {
151               psiFileSystemItem.navigate(true);
152             }
153           };
154           return new LineMarkerInfo<PsiElement>(bindingElement, bindingElement.getTextRange(), icon,
155                                                 Pass.UPDATE_ALL, null, navHandler,
156                                                 GutterIconRenderer.Alignment.LEFT);
157         }
158       }
159     }
160     return null;
161   }
162
163   private static boolean isIconFileExtension(String extension) {
164     return extension != null && ICON_EXTS.contains(extension.toLowerCase());
165   }
166
167   public void collectSlowLineMarkers(List<PsiElement> elements, Collection<LineMarkerInfo> result) {
168   }
169
170   private static boolean hasProperSize(Icon icon) {
171     return icon.getIconHeight() <= ICON_MAX_HEIGHT && icon.getIconWidth() <= ICON_MAX_WEIGHT;
172   }
173
174   @Nullable
175   private Icon getIcon(VirtualFile file) {
176     final String path = file.getPath();
177     final long stamp = file.getModificationStamp();
178     Pair<Long, Icon> iconInfo = iconsCache.get(path);
179     if (iconInfo == null || iconInfo.getFirst() < stamp) {
180       try {
181         final Icon icon = new ImageIcon(file.contentsToByteArray());
182         iconInfo = new Pair<Long, Icon>(stamp, hasProperSize(icon) ? icon : null);
183         iconsCache.put(file.getPath(), iconInfo);
184       }
185       catch (Exception e) {//
186         iconInfo = null;
187         iconsCache.remove(path);
188       }
189     }
190     return iconInfo == null ? null : iconInfo.getSecond();
191   }
192
193   private static boolean isIconClassType(PsiType type) {
194     return InheritanceUtil.isInheritor(type, JAVAX_SWING_ICON);
195   }
196 }