do not suggest value local value resources
[idea/community.git] / plugins / android / src / org / jetbrains / android / resourceManagers / ResourceManager.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.resourceManagers;
17
18 import com.android.sdklib.SdkConstants;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.fileTypes.StdFileTypes;
21 import com.intellij.openapi.module.Module;
22 import com.intellij.openapi.module.ModuleUtil;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.Computable;
25 import com.intellij.openapi.util.io.FileUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.PsiDirectory;
28 import com.intellij.psi.PsiElement;
29 import com.intellij.psi.PsiFile;
30 import com.intellij.psi.PsiManager;
31 import com.intellij.psi.xml.XmlTag;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.containers.HashSet;
34 import com.intellij.util.xml.DomElement;
35 import org.jetbrains.android.dom.attrs.AttributeDefinitions;
36 import org.jetbrains.android.dom.resources.Item;
37 import org.jetbrains.android.dom.resources.ResourceElement;
38 import org.jetbrains.android.dom.resources.Resources;
39 import org.jetbrains.android.facet.AndroidFacet;
40 import org.jetbrains.android.util.AndroidResourceUtil;
41 import org.jetbrains.android.util.AndroidUtils;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import java.util.*;
46
47 import static java.util.Collections.addAll;
48
49 /**
50  * @author coyote
51  */
52 public abstract class ResourceManager {
53   public static final Set<String> REFERABLE_RESOURCE_TYPES = new HashSet<String>();
54   public static final String[] FILE_RESOURCE_TYPES = new String[]{"drawable", "anim", "layout", "values", "menu", "xml", "raw", "color"};
55   public static final String[] VALUE_RESOURCE_TYPES =
56     new String[]{"drawable", "dimen", "color", "string", "style", "array", "id", "bool", "integer", "integer-array"};
57   private static final String[] DRAWABLE_EXTENSIONS = new String[]{AndroidUtils.PNG_EXTENSION, "jpg", "gif"};
58
59   protected final Module myModule;
60
61   static {
62     addAll(REFERABLE_RESOURCE_TYPES, FILE_RESOURCE_TYPES);
63     addAll(REFERABLE_RESOURCE_TYPES, VALUE_RESOURCE_TYPES);
64     REFERABLE_RESOURCE_TYPES.remove("values");
65   }
66
67   protected ResourceManager(@NotNull Module module) {
68     myModule = module;
69   }
70
71   public Module getModule() {
72     return myModule;
73   }
74
75   @Nullable
76   public static String getDefaultResourceFileName(@NotNull String resourceType) {
77     if (ArrayUtil.find(VALUE_RESOURCE_TYPES, resourceType) < 0) {
78       return null;
79     }
80     return resourceType + "s.xml";
81   }
82
83   @NotNull
84   public abstract VirtualFile[] getAllResourceDirs();
85
86   @Nullable
87   public abstract VirtualFile getResourceDir();
88
89   public boolean isResourceDir(@NotNull VirtualFile dir) {
90     return dir.equals(getResourceDir());
91   }
92
93   @NotNull
94   public VirtualFile[] getResourceOverlayDirs() {
95     return VirtualFile.EMPTY_ARRAY;
96   }
97
98   @NotNull
99   public List<VirtualFile> getResourceSubdirs(@Nullable String resourceType) {
100     return AndroidResourceUtil.getResourceSubdirs(resourceType, getAllResourceDirs());
101   }
102
103   @NotNull
104   public static String getResourceName(@NotNull String resourceType, @NotNull String fileName) {
105     String extension = FileUtil.getExtension(fileName);
106     String s = FileUtil.getNameWithoutExtension(fileName);
107     if (resourceType.equals("drawable") && ArrayUtil.find(DRAWABLE_EXTENSIONS, extension) >= 0) {
108       if (s.endsWith(".9") && extension.equals(AndroidUtils.PNG_EXTENSION)) {
109         return s.substring(0, s.length() - 2);
110       }
111       return s;
112     }
113     return s;
114   }
115
116   private static boolean isCorrectFileName(@NotNull String resourceType, @NotNull String fileName) {
117     return getResourceName(resourceType, fileName) != null;
118   }
119
120   public static boolean equal(@Nullable String s1, @Nullable String s2, boolean distinguishDelimeters) {
121     if (s1 == null || s2 == null) {
122       return false;
123     }
124     if (s1.length() != s2.length()) return false;
125     for (int i = 0, n = s1.length(); i < n; i++) {
126       char c1 = s1.charAt(i);
127       char c2 = s2.charAt(i);
128       if (distinguishDelimeters || (Character.isLetterOrDigit(c1) && Character.isLetterOrDigit(c2))) {
129         if (c1 != c2) return false;
130       }
131     }
132     return true;
133   }
134
135   @NotNull
136   public List<PsiFile> findResourceFiles(@NotNull String resType,
137                                          @Nullable String resName,
138                                          boolean distinguishDelimetersInName,
139                                          @NotNull String... extensions) {
140     List<PsiFile> result = new ArrayList<PsiFile>();
141     Set<String> extensionSet = new HashSet<String>();
142     addAll(extensionSet, extensions);
143     for (VirtualFile dir : getResourceSubdirs(resType)) {
144       for (final VirtualFile resFile : dir.getChildren()) {
145         String extension = resFile.getExtension();
146         if (extensions.length == 0 || extensionSet.contains(extension)) {
147           String s = getResourceName(resType, resFile.getName());
148           if (resName == null || equal(resName, s, distinguishDelimetersInName)) {
149             PsiFile file = ApplicationManager.getApplication().runReadAction(new Computable<PsiFile>() {
150               @Nullable
151               public PsiFile compute() {
152                 return PsiManager.getInstance(myModule.getProject()).findFile(resFile);
153               }
154             });
155             if (file != null) {
156               result.add(file);
157             }
158           }
159         }
160       }
161     }
162     return result;
163   }
164
165   public List<PsiFile> findResourceFiles(@NotNull String resType, @NotNull String resName, @NotNull String... extensions) {
166     return findResourceFiles(resType, resName, true, extensions);
167   }
168
169   @NotNull
170   public List<PsiFile> findResourceFiles(@NotNull String resType) {
171     return findResourceFiles(resType, null, true);
172   }
173
174   protected List<Resources> getResourceElements(@Nullable Set<VirtualFile> files) {
175     return getRootDomElements(Resources.class, files);
176   }
177
178   private <T extends DomElement> List<T> getRootDomElements(@NotNull Class<T> elementType,
179                                                             @Nullable Set<VirtualFile> files) {
180     final List<T> result = new ArrayList<T>();
181     for (VirtualFile file : getAllResourceFiles()) {
182       if ((files == null || files.contains(file)) && file.isValid()) {
183         T element = AndroidUtils.loadDomElement(myModule, file, elementType);
184         if (element != null) result.add(element);
185       }
186     }
187     return result;
188   }
189
190   @NotNull
191   protected Set<VirtualFile> getAllResourceFiles() {
192     final Set<VirtualFile> files = new HashSet<VirtualFile>();
193
194     for (VirtualFile valueResourceDir : getResourceSubdirs("values")) {
195       for (VirtualFile valueResourceFile : valueResourceDir.getChildren()) {
196         if (!valueResourceFile.isDirectory() && valueResourceFile.getFileType().equals(StdFileTypes.XML)) {
197           files.add(valueResourceFile);
198         }
199       }
200     }
201     return files;
202   }
203
204   protected List<ResourceElement> getValueResources(@NotNull final String resourceType, @Nullable Set<VirtualFile> files) {
205     final List<ResourceElement> result = new ArrayList<ResourceElement>();
206     Collection<Resources> resourceFiles = getResourceElements(files);
207     for (final Resources resources : resourceFiles) {
208       ApplicationManager.getApplication().runReadAction(new Runnable() {
209         @Override
210         public void run() {
211           if (!resources.isValid() || myModule.isDisposed() || myModule.getProject().isDisposed()) {
212             return;
213           }
214           result.addAll(getValueResourcesFromElement(resourceType, resources));
215         }
216       });
217     }
218     return result;
219   }
220
221   @Nullable
222   public String getValueResourceType(@NotNull XmlTag tag) {
223     String fileResType = getFileResourceType(tag.getContainingFile());
224     if ("values".equals(fileResType)) {
225       return tag.getName();
226     }
227     return null;
228   }
229
230   @Nullable
231   public String getFileResourceType(@NotNull final PsiFile file) {
232     return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
233       @Nullable
234       @Override
235       public String compute() {
236         PsiDirectory dir = file.getContainingDirectory();
237         if (dir == null) return null;
238         PsiDirectory possibleResDir = dir.getParentDirectory();
239         if (possibleResDir == null || !isResourceDir(possibleResDir.getVirtualFile())) {
240           return null;
241         }
242         String type = AndroidResourceUtil.getResourceTypeByDirName(dir.getName());
243         if (type == null) return null;
244         return isCorrectFileName(type, file.getName()) ? type : null;
245       }
246     });
247   }
248
249   @NotNull
250   public Set<String> getFileResourcesNames(@NotNull String resourceType) {
251     Set<String> result = new HashSet<String>();
252     List<VirtualFile> dirs = getResourceSubdirs(resourceType);
253     for (VirtualFile dir : dirs) {
254       for (VirtualFile resourceFile : dir.getChildren()) {
255         if (resourceFile.isDirectory()) continue;
256         String resName = getResourceName(resourceType, resourceFile.getName());
257         if (resName != null) result.add(resName);
258       }
259     }
260     return result;
261   }
262
263   @NotNull
264   public abstract Collection<String> getValueResourceNames(@NotNull final String resourceType);
265
266   @NotNull
267   public static List<ResourceElement> getValueResourcesFromElement(@NotNull String resourceType, Resources resources) {
268     List<ResourceElement> result = new ArrayList<ResourceElement>();
269     if (resourceType.equals("string")) {
270       result.addAll(resources.getStrings());
271     }
272     else if (resourceType.equals("drawable")) {
273       result.addAll(resources.getDrawables());
274     }
275     else if (resourceType.equals("color")) {
276       result.addAll(resources.getColors());
277     }
278     else if (resourceType.equals("dimen")) {
279       result.addAll(resources.getDimens());
280     }
281     else if (resourceType.equals("style")) {
282       result.addAll(resources.getStyles());
283     }
284     else if (resourceType.equals("array")) {
285       result.addAll(resources.getStringArrays());
286       result.addAll(resources.getIntegerArrays());
287       result.addAll(resources.getArrays());
288     }
289     else if (resourceType.equals("integer")) {
290       result.addAll(resources.getIntegers());
291     }
292     else if (resourceType.equals("bool")) {
293       result.addAll(resources.getBools());
294     }
295     for (Item item : resources.getItems()) {
296       String type = item.getType().getValue();
297       if (resourceType.equals(type)) {
298         result.add(item);
299       }
300     }
301     return result;
302   }
303
304   @Nullable
305   public abstract AttributeDefinitions getAttributeDefinitions();
306
307   // searches only declarations such as "@+id/..."
308   @Nullable
309   public abstract List<PsiElement> findIdDeclarations(@NotNull String id);
310
311   @NotNull
312   public abstract Collection<String> getIds();
313
314   public List<ResourceElement> findValueResources(@NotNull String resType, @NotNull String resName) {
315     return findValueResources(resType, resName, true);
316   }
317
318   @NotNull
319   public abstract List<ResourceElement> findValueResources(@NotNull String resourceType,
320                                                            @NotNull String resourceName,
321                                                            boolean distinguishDelimetersInName);
322
323   public static boolean isInResourceSubdirectory(@NotNull PsiFile file, @Nullable String resourceType) {
324     file = file.getOriginalFile();
325     PsiDirectory dir = file.getContainingDirectory();
326     if (dir == null) return false;
327     return isResourceSubdirectory(dir, resourceType);
328   }
329
330   public static boolean isResourceSubdirectory(PsiDirectory dir, String resourceType) {
331     if (resourceType != null && !dir.getName().startsWith(resourceType)) return false;
332     dir = dir.getParent();
333     if (dir == null) return false;
334     if ("default".equals(dir.getName())) {
335       dir = dir.getParentDirectory();
336     }
337     return dir != null && isResourceDirectory(dir);
338   }
339
340   public static boolean isResourceDirectory(VirtualFile dir, Project project) {
341     Module module = ModuleUtil.findModuleForFile(dir, project);
342     if (module != null) {
343       AndroidFacet facet = AndroidFacet.getInstance(module);
344       return facet != null && facet.getLocalResourceManager().isResourceDir(dir);
345     }
346     return false;
347   }
348
349   public static boolean isResourceDirectory(PsiDirectory dir) {
350     // check facet settings
351     VirtualFile vf = dir.getVirtualFile();
352
353     if (isResourceDirectory(vf, dir.getProject())) {
354       return true;
355     }
356
357     // method can be invoked for system resource dir, so we should check it
358     if (!SdkConstants.FD_RES.equals(dir.getName())) return false;
359     dir = dir.getParent();
360     if (dir != null) {
361       if (dir.findFile(SdkConstants.FN_ANDROID_MANIFEST_XML) != null) {
362         return true;
363       }
364       dir = dir.getParent();
365       if (dir != null) {
366         if (containsAndroidJar(dir)) return true;
367         dir = dir.getParent();
368         if (dir != null) {
369           return containsAndroidJar(dir);
370         }
371       }
372     }
373     return false;
374   }
375
376   private static boolean containsAndroidJar(@NotNull PsiDirectory psiDirectory) {
377     return psiDirectory.findFile(SdkConstants.FN_FRAMEWORK_LIBRARY) != null;
378   }
379 }