clean up
[idea/community.git] / plugins / android / src / org / jetbrains / android / AndroidResourcesLineMarkerProvider.java
1 /*
2  * Copyright 2000-2010 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 org.jetbrains.android;
17
18 import com.intellij.codeHighlighting.Pass;
19 import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
20 import com.intellij.codeInsight.daemon.LineMarkerInfo;
21 import com.intellij.codeInsight.daemon.LineMarkerProvider;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.Computable;
25 import com.intellij.openapi.util.IconLoader;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.*;
28 import com.intellij.psi.util.PsiTreeUtil;
29 import com.intellij.psi.util.PsiUtilCore;
30 import com.intellij.psi.xml.XmlAttribute;
31 import com.intellij.psi.xml.XmlAttributeValue;
32 import com.intellij.psi.xml.XmlTag;
33 import com.intellij.ui.awt.RelativePoint;
34 import com.intellij.util.ConstantFunction;
35 import com.intellij.util.containers.HashMap;
36 import com.intellij.util.xml.GenericAttributeValue;
37 import org.jetbrains.android.dom.resources.Attr;
38 import org.jetbrains.android.dom.resources.DeclareStyleable;
39 import org.jetbrains.android.dom.resources.ResourceElement;
40 import org.jetbrains.android.dom.resources.Resources;
41 import org.jetbrains.android.facet.AndroidFacet;
42 import org.jetbrains.android.resourceManagers.LocalResourceManager;
43 import org.jetbrains.android.resourceManagers.ResourceManager;
44 import org.jetbrains.android.util.AndroidResourceUtil;
45 import org.jetbrains.android.util.AndroidUtils;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48
49 import javax.swing.*;
50 import java.awt.event.MouseEvent;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.List;
54 import java.util.Map;
55
56 /**
57  * @author coyote
58  */
59 public class AndroidResourcesLineMarkerProvider implements LineMarkerProvider {
60   private static final Icon ICON = IconLoader.getIcon("/icons/navigate.png");
61
62   public LineMarkerInfo getLineMarkerInfo(PsiElement psiElement) {
63     return null;
64   }
65
66   public void collectSlowLineMarkers(List<PsiElement> psiElements, Collection<LineMarkerInfo> lineMarkerInfos) {
67     //noinspection ForLoopReplaceableByForEach
68     for (int i = 0; i < psiElements.size(); i++) {
69       PsiElement element = psiElements.get(i);
70       addMarkerInfo(element, lineMarkerInfos);
71     }
72   }
73
74   @NotNull
75   private static String getToolTip(@NotNull PsiElement element) {
76     String s = "Go to ";
77     if (element instanceof PsiField) {
78       PsiField field = (PsiField)element;
79       PsiClass resClass = field.getContainingClass();
80       assert resClass != null;
81       PsiClass rClass = resClass.getContainingClass();
82       assert rClass != null;
83       return s + rClass.getName() + '.' + resClass.getName() + '.' + field.getName();
84     }
85     else {
86       PsiFile file = AndroidUtils.getFileTarget(element).getOriginalFile();
87       String name = file.getName();
88       PsiDirectory dir = file.getContainingDirectory();
89       if (dir == null) return s + name;
90       return s + dir.getName() + '/' + name;
91     }
92   }
93
94   private static LineMarkerInfo createLineMarkerInfo(@NotNull PsiElement element, @NotNull PsiElement... targets) {
95     final String toolTip = targets.length == 1 ? getToolTip(targets[0]) : "Resource not found";
96     return new LineMarkerInfo<PsiElement>(element,
97                                           element.getTextOffset(),
98                                           ICON,
99                                           Pass.UPDATE_OVERRIDEN_MARKERS, 
100                                           new ConstantFunction<PsiElement, String>(toolTip),
101                                           new MyNavigationHandler(targets));
102   }
103
104   private static LineMarkerInfo createLazyLineMarkerInfo(@NotNull PsiElement element,
105                                                          @NotNull final Computable<PsiElement[]> targetProvider) {
106     return new LineMarkerInfo<PsiElement>(element,
107                                           element.getTextOffset(),
108                                           ICON,
109                                           Pass.UPDATE_OVERRIDEN_MARKERS,
110                                           new ConstantFunction<PsiElement, String>("Go to resource"),
111                                           new MyLazyNavigationHandler(targetProvider));
112   }
113
114   private static void annotateXmlAttributeValue(@NotNull XmlAttributeValue attrValue, @NotNull Collection<LineMarkerInfo> result) {
115     final AndroidFacet facet = AndroidFacet.getInstance(attrValue);
116     if (facet != null) {
117       PsiElement parent = attrValue.getParent();
118       if (!(parent instanceof XmlAttribute)) return;
119       final XmlAttribute attr = (XmlAttribute)parent;
120       if (attr.getLocalName().equals("name")) {
121         final XmlTag tag = PsiTreeUtil.getParentOfType(attr, XmlTag.class);
122         if (tag != null) {
123           final String resType = AndroidResourceUtil.getResClassNameByValueResourceTag(facet, tag);
124           if (resType != null) {
125             result.add(createLazyLineMarkerInfo(tag, new Computable<PsiElement[]>() {
126               @Override
127               public PsiElement[] compute() {
128                 String name = tag.getAttributeValue("name");
129                 return name != null ? AndroidResourceUtil.findResourceFields(facet, resType, name, false) : PsiElement.EMPTY_ARRAY;
130               }
131             }));
132           }
133         }
134       }
135       else if (AndroidResourceUtil.isIdDeclaration(attrValue)) {
136         result.add(createLazyLineMarkerInfo(attrValue, new Computable<PsiElement[]>() {
137           @Override
138           public PsiElement[] compute() {
139             return AndroidResourceUtil.findIdFields(attr);
140           }
141         }));
142       }
143     }
144   }
145
146   private static void addMarkerInfo(@NotNull final PsiElement element, @NotNull Collection<LineMarkerInfo> result) {
147     if (element instanceof PsiFile) {
148       PsiField[] fields = AndroidResourceUtil.findResourceFieldsForFileResource((PsiFile)element, false);
149       if (fields.length > 0) result.add(createLineMarkerInfo(element, fields));
150     }
151     else if (element instanceof PsiClass) {
152       PsiClass c = (PsiClass)element;
153       if (AndroidUtils.R_CLASS_NAME.equals(c.getName())) {
154         PsiFile containingFile = element.getContainingFile();
155         AndroidFacet facet = AndroidFacet.getInstance(containingFile);
156         if (facet != null && AndroidUtils.isRClassFile(facet, containingFile)) {
157           LocalResourceManager manager = facet.getLocalResourceManager();
158           annotateRClass((PsiClass)element, result, manager);
159         }
160       }
161     }
162     else if (element instanceof XmlAttributeValue) {
163       annotateXmlAttributeValue((XmlAttributeValue)element, result);
164     }
165   }
166
167   @NotNull
168   private static Map<MyResourceEntry, List<PsiElement>> buildLocalResourceMap(@NotNull Project project,
169                                                                               @NotNull final LocalResourceManager resManager) {
170     final Map<MyResourceEntry, List<PsiElement>> result = new HashMap<MyResourceEntry, List<PsiElement>>();
171     Collection<Resources> resourceFiles = resManager.getResourceElements();
172     for (Resources res : resourceFiles) {
173       for (String valueResourceType : ResourceManager.VALUE_RESOURCE_TYPES) {
174         for (ResourceElement valueResource : ResourceManager.getValueResources(valueResourceType, res)) {
175           addResource(valueResourceType, valueResource, result);
176         }
177       }
178       for (Attr attr : res.getAttrs()) {
179         addResource("attr", attr, result);
180       }
181       for (DeclareStyleable styleable : res.getDeclareStyleables()) {
182         addResource("styleable", styleable, result);
183         for (Attr attr : styleable.getAttrs()) {
184           addResource("attr", attr, result);
185         }
186       }
187     }
188     collectFileResources(project, resManager, result);
189     return result;
190   }
191
192   private static void collectFileResources(Project project,
193                                            final LocalResourceManager resManager,
194                                            final Map<MyResourceEntry, List<PsiElement>> result) {
195     final PsiManager psiManager = PsiManager.getInstance(project);
196     ApplicationManager.getApplication().runReadAction(new Runnable() {
197       public void run() {
198         List<VirtualFile> resourceSubdirs = resManager.getResourceSubdirs(null);
199         for (VirtualFile dir : resourceSubdirs) {
200           String resType = ResourceManager.getResourceTypeByDirName(dir.getName());
201           if (resType != null) {
202             for (VirtualFile resourceFile : dir.getChildren()) {
203               if (!resourceFile.isDirectory()) {
204                 PsiFile resourcePsiFile = psiManager.findFile(resourceFile);
205                 if (resourcePsiFile != null) {
206                   String resName = ResourceManager.getResourceName(resType, resourceFile.getName());
207                   MyResourceEntry key = new MyResourceEntry(resName, resType);
208                   List<PsiElement> list = result.get(key);
209                   if (list == null) {
210                     list = new ArrayList<PsiElement>();
211                     result.put(key, list);
212                   }
213                   list.add(resourcePsiFile);
214                 }
215               }
216             }
217           }
218         }
219       }
220     });
221   }
222
223   private static void addResource(String resType, ResourceElement resElement, Map<MyResourceEntry, List<PsiElement>> result) {
224     GenericAttributeValue<String> nameValue = resElement.getName();
225     if (nameValue != null) {
226       String name = nameValue.getValue();
227       if (name != null) {
228         MyResourceEntry key = new MyResourceEntry(name, resType);
229         List<PsiElement> list = result.get(key);
230         if (list == null) {
231           list = new ArrayList<PsiElement>();
232           result.put(key, list);
233         }
234         list.add(nameValue.getXmlAttributeValue());
235       }
236     }
237   }
238
239   private static void annotateRClass(@NotNull PsiClass rClass,
240                                      @NotNull Collection<LineMarkerInfo> result,
241                                      @NotNull LocalResourceManager manager) {
242     Map<MyResourceEntry, List<PsiElement>> resourceMap = buildLocalResourceMap(rClass.getProject(), manager);
243     for (PsiClass c : rClass.getInnerClasses()) {
244       for (PsiField field : c.getFields()) {
245         annotateElementNavToResource(field, field, manager, result, resourceMap, false);
246       }
247     }
248   }
249
250   private static void annotateElementNavToResource(PsiElement element,
251                                                    final PsiField resField,
252                                                    final LocalResourceManager manager,
253                                                    Collection<LineMarkerInfo> result,
254                                                    @Nullable final Map<MyResourceEntry, List<PsiElement>> resourceMap,
255                                                    boolean lazy) {
256     final String fieldName = resField.getName();
257     if (fieldName != null) {
258       final String resType = AndroidResourceUtil.getResourceClassName(resField);
259       if (resType != null) {
260         Computable<PsiElement[]> targetProvider = new Computable<PsiElement[]>() {
261           @Override
262           public PsiElement[] compute() {
263             final List<PsiElement> targets;
264             if (resourceMap != null) {
265               targets = new ArrayList<PsiElement>();
266               if (resType.equals("id")) {
267                 AndroidResourceUtil.collectIdDeclarations(fieldName, manager.getModule(), targets);
268               }
269               List<PsiElement> resources = resourceMap.get(new MyResourceEntry(fieldName, resType));
270               if (resources != null) {
271                 targets.addAll(resources);
272               }
273             }
274             else {
275               targets = AndroidResourceUtil.findResourcesByField(manager, resField);
276             }
277             return PsiUtilCore.toPsiElementArray(targets);
278           }
279         };
280         if (lazy) {
281           result.add(createLazyLineMarkerInfo(element, targetProvider));
282         }
283         else {
284           PsiElement[] targets = targetProvider.compute();
285           if (targets != null && targets.length > 0) {
286             result.add(createLineMarkerInfo(element, targets));
287           }
288         }
289       }
290     }
291   }
292
293   static class MyResourceEntry {
294     final String myName;
295     final String myType;
296
297     private MyResourceEntry(@NotNull String name, @NotNull String type) {
298       myName = name;
299       myType = type;
300     }
301
302     @Override
303     public boolean equals(Object o) {
304       if (this == o) return true;
305       if (o == null || getClass() != o.getClass()) return false;
306
307       MyResourceEntry that = (MyResourceEntry)o;
308
309       if (!ResourceManager.equal(myName, that.myName, false)) return false;
310       if (!myType.equals(that.myType)) return false;
311
312       return true;
313     }
314
315     @Override
316     public int hashCode() {
317       int result = 0;
318       for (int i = 0; i < myName.length(); i++) {
319         char c = myName.charAt(i);
320         if (Character.isLetterOrDigit(c)) {
321           result = 31 * result + (int)c;
322         }
323       }
324       result = 31 * result + myType.hashCode();
325       return result;
326     }
327   }
328
329   public static class MyNavigationHandler implements GutterIconNavigationHandler<PsiElement> {
330     private final PsiElement[] myTargets;
331
332     private MyNavigationHandler(@NotNull PsiElement[] targets) {
333       myTargets = targets;
334     }
335
336     public void navigate(MouseEvent event, PsiElement psiElement) {
337       AndroidUtils.navigateTo(myTargets, event != null ? new RelativePoint(event) : null);
338     }
339
340     public PsiElement[] getTargets() {
341       return myTargets;
342     }
343   }
344
345   public static class MyLazyNavigationHandler implements GutterIconNavigationHandler<PsiElement> {
346     private final Computable<PsiElement[]> myTargetProvider;
347
348     private MyLazyNavigationHandler(Computable<PsiElement[]> targetProvider) {
349       myTargetProvider = targetProvider;
350     }
351
352     @Override
353     public void navigate(MouseEvent event, PsiElement psiElement) {
354       PsiElement[] targets = myTargetProvider.compute();
355       if (targets != null && targets.length > 0) {
356         AndroidUtils.navigateTo(targets, event != null ? new RelativePoint(event) : null);
357       }
358     }
359
360     public Computable<PsiElement[]> getTargetProvider() {
361       return myTargetProvider;
362     }
363   }
364 }