2 * Copyright 2000-2010 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 org.jetbrains.android;
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;
50 import java.awt.event.MouseEvent;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.List;
59 public class AndroidResourcesLineMarkerProvider implements LineMarkerProvider {
60 private static final Icon ICON = IconLoader.getIcon("/icons/navigate.png");
62 public LineMarkerInfo getLineMarkerInfo(PsiElement psiElement) {
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);
75 private static String getToolTip(@NotNull PsiElement element) {
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();
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;
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(),
99 Pass.UPDATE_OVERRIDEN_MARKERS,
100 new ConstantFunction<PsiElement, String>(toolTip),
101 new MyNavigationHandler(targets));
104 private static LineMarkerInfo createLazyLineMarkerInfo(@NotNull PsiElement element,
105 @NotNull final Computable<PsiElement[]> targetProvider) {
106 return new LineMarkerInfo<PsiElement>(element,
107 element.getTextOffset(),
109 Pass.UPDATE_OVERRIDEN_MARKERS,
110 new ConstantFunction<PsiElement, String>("Go to resource"),
111 new MyLazyNavigationHandler(targetProvider));
114 private static void annotateXmlAttributeValue(@NotNull XmlAttributeValue attrValue, @NotNull Collection<LineMarkerInfo> result) {
115 final AndroidFacet facet = AndroidFacet.getInstance(attrValue);
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);
123 final String resType = AndroidResourceUtil.getResClassNameByValueResourceTag(facet, tag);
124 if (resType != null) {
125 result.add(createLazyLineMarkerInfo(tag, new Computable<PsiElement[]>() {
127 public PsiElement[] compute() {
128 String name = tag.getAttributeValue("name");
129 return name != null ? AndroidResourceUtil.findResourceFields(facet, resType, name, false) : PsiElement.EMPTY_ARRAY;
135 else if (AndroidResourceUtil.isIdDeclaration(attrValue)) {
136 result.add(createLazyLineMarkerInfo(attrValue, new Computable<PsiElement[]>() {
138 public PsiElement[] compute() {
139 return AndroidResourceUtil.findIdFields(attr);
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));
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);
162 else if (element instanceof XmlAttributeValue) {
163 annotateXmlAttributeValue((XmlAttributeValue)element, result);
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);
178 for (Attr attr : res.getAttrs()) {
179 addResource("attr", attr, result);
181 for (DeclareStyleable styleable : res.getDeclareStyleables()) {
182 addResource("styleable", styleable, result);
183 for (Attr attr : styleable.getAttrs()) {
184 addResource("attr", attr, result);
188 collectFileResources(project, resManager, result);
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() {
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);
210 list = new ArrayList<PsiElement>();
211 result.put(key, list);
213 list.add(resourcePsiFile);
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();
228 MyResourceEntry key = new MyResourceEntry(name, resType);
229 List<PsiElement> list = result.get(key);
231 list = new ArrayList<PsiElement>();
232 result.put(key, list);
234 list.add(nameValue.getXmlAttributeValue());
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);
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,
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[]>() {
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);
269 List<PsiElement> resources = resourceMap.get(new MyResourceEntry(fieldName, resType));
270 if (resources != null) {
271 targets.addAll(resources);
275 targets = AndroidResourceUtil.findResourcesByField(manager, resField);
277 return PsiUtilCore.toPsiElementArray(targets);
281 result.add(createLazyLineMarkerInfo(element, targetProvider));
284 PsiElement[] targets = targetProvider.compute();
285 if (targets != null && targets.length > 0) {
286 result.add(createLineMarkerInfo(element, targets));
293 static class MyResourceEntry {
297 private MyResourceEntry(@NotNull String name, @NotNull String type) {
303 public boolean equals(Object o) {
304 if (this == o) return true;
305 if (o == null || getClass() != o.getClass()) return false;
307 MyResourceEntry that = (MyResourceEntry)o;
309 if (!ResourceManager.equal(myName, that.myName, false)) return false;
310 if (!myType.equals(that.myType)) return false;
316 public int hashCode() {
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;
324 result = 31 * result + myType.hashCode();
329 public static class MyNavigationHandler implements GutterIconNavigationHandler<PsiElement> {
330 private final PsiElement[] myTargets;
332 private MyNavigationHandler(@NotNull PsiElement[] targets) {
336 public void navigate(MouseEvent event, PsiElement psiElement) {
337 AndroidUtils.navigateTo(myTargets, event != null ? new RelativePoint(event) : null);
340 public PsiElement[] getTargets() {
345 public static class MyLazyNavigationHandler implements GutterIconNavigationHandler<PsiElement> {
346 private final Computable<PsiElement[]> myTargetProvider;
348 private MyLazyNavigationHandler(Computable<PsiElement[]> targetProvider) {
349 myTargetProvider = targetProvider;
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);
360 public Computable<PsiElement[]> getTargetProvider() {
361 return myTargetProvider;