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