• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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 
17 package com.android.ide.eclipse.adt.internal.resources.manager;
18 
19 import com.android.ide.common.resources.IntArrayWrapper;
20 import com.android.ide.eclipse.adt.AdtConstants;
21 import com.android.ide.eclipse.adt.AdtPlugin;
22 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
23 import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
24 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
25 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener;
26 import com.android.resources.ResourceType;
27 import com.android.sdklib.xml.ManifestData;
28 import com.android.util.Pair;
29 
30 import org.eclipse.core.resources.IFile;
31 import org.eclipse.core.resources.IMarkerDelta;
32 import org.eclipse.core.resources.IProject;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.core.resources.IResourceDelta;
35 import org.eclipse.core.runtime.CoreException;
36 import org.eclipse.core.runtime.IPath;
37 import org.eclipse.core.runtime.IStatus;
38 
39 import java.io.File;
40 import java.lang.reflect.Field;
41 import java.lang.reflect.Modifier;
42 import java.util.EnumMap;
43 import java.util.HashMap;
44 import java.util.Map;
45 import java.util.regex.Pattern;
46 
47 /**
48  * A monitor for the compiled resources. This only monitors changes in the resources of type
49  *  {@link ResourceType#ID}.
50  */
51 public final class CompiledResourcesMonitor implements IFileListener, IProjectListener {
52 
53     private final static CompiledResourcesMonitor sThis = new CompiledResourcesMonitor();
54 
55     /**
56      * Sets up the monitoring system.
57      * @param monitor The main Resource Monitor.
58      */
setupMonitor(GlobalProjectMonitor monitor)59     public static void setupMonitor(GlobalProjectMonitor monitor) {
60         monitor.addFileListener(sThis, IResourceDelta.ADDED | IResourceDelta.CHANGED);
61         monitor.addProjectListener(sThis);
62     }
63 
64     /**
65      * private constructor to prevent construction.
66      */
CompiledResourcesMonitor()67     private CompiledResourcesMonitor() {
68     }
69 
70 
71     /* (non-Javadoc)
72      * Sent when a file changed : if the file is the R class, then it is parsed again to update
73      * the internal data.
74      *
75      * @param file The file that changed.
76      * @param markerDeltas The marker deltas for the file.
77      * @param kind The change kind. This is equivalent to
78      * {@link IResourceDelta#accept(IResourceDeltaVisitor)}
79      *
80      * @see IFileListener#fileChanged
81      */
fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind)82     public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
83         IProject project = file.getProject();
84 
85         if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)) {
86             // create the classname
87             String className = getRClassName(project);
88             if (className == null) {
89                 // We need to abort.
90                 AdtPlugin.log(IStatus.ERROR,
91                         "fileChanged: failed to find manifest package for project %1$s", //$NON-NLS-1$
92                         project.getName());
93                 return;
94             }
95             // path will begin with /projectName/bin/classes so we'll ignore that
96             IPath relativeClassPath = file.getFullPath().removeFirstSegments(3);
97             if (packagePathMatches(relativeClassPath.toString(), className)) {
98                 loadAndParseRClass(project, className);
99             }
100         }
101     }
102 
103     /**
104      * Check to see if the package section of the given path matches the packageName.
105      * For example, /project/bin/classes/com/foo/app/R.class should match com.foo.app.R
106      * @param path the pathname of the file to look at
107      * @param packageName the package qualified name of the class
108      * @return true if the package section of the path matches the package qualified name
109      */
packagePathMatches(String path, String packageName)110     private boolean packagePathMatches(String path, String packageName) {
111         // First strip the ".class" off the end of the path
112         String pathWithoutExtension = path.substring(0, path.indexOf(AdtConstants.DOT_CLASS));
113 
114         // then split the components of each path by their separators
115         String [] pathArray = pathWithoutExtension.split(Pattern.quote(File.separator));
116         String [] packageArray = packageName.split(AdtConstants.RE_DOT);
117 
118 
119         int pathIndex = 0;
120         int packageIndex = 0;
121 
122         while (pathIndex < pathArray.length && packageIndex < packageArray.length) {
123             if (pathArray[pathIndex].equals(packageArray[packageIndex]) == false) {
124                 return false;
125             }
126             pathIndex++;
127             packageIndex++;
128         }
129         // We may have matched all the way up to this point, but we're not sure it's a match
130         // unless BOTH paths done
131         return (pathIndex == pathArray.length && packageIndex == packageArray.length);
132     }
133 
134     /**
135      * Processes project close event.
136      */
projectClosed(IProject project)137     public void projectClosed(IProject project) {
138         // the ProjectResources object will be removed by the ResourceManager.
139     }
140 
141     /**
142      * Processes project delete event.
143      */
projectDeleted(IProject project)144     public void projectDeleted(IProject project) {
145         // the ProjectResources object will be removed by the ResourceManager.
146     }
147 
148     /**
149      * Processes project open event.
150      */
projectOpened(IProject project)151     public void projectOpened(IProject project) {
152         // when the project is opened, we get an ADDED event for each file, so we don't
153         // need to do anything here.
154     }
155 
projectRenamed(IProject project, IPath from)156     public void projectRenamed(IProject project, IPath from) {
157         // renamed projects also trigger delete/open event,
158         // so nothing to be done here.
159     }
160 
161     /**
162      * Processes existing project at init time.
163      */
projectOpenedWithWorkspace(IProject project)164     public void projectOpenedWithWorkspace(IProject project) {
165         try {
166             // check this is an android project
167             if (project.hasNature(AdtConstants.NATURE_DEFAULT)) {
168                 String className = getRClassName(project);
169                 // Find the classname
170                 if (className == null) {
171                     // We need to abort.
172                     AdtPlugin.log(IStatus.ERROR,
173                             "projectOpenedWithWorkspace: failed to find manifest package for project %1$s", //$NON-NLS-1$
174                             project.getName());
175                     return;
176                 }
177                 loadAndParseRClass(project, className);
178             }
179         } catch (CoreException e) {
180             // pass
181         }
182     }
183 
loadAndParseRClass(IProject project, String className)184     private void loadAndParseRClass(IProject project, String className) {
185         try {
186             // first check there's a ProjectResources to store the content
187             ProjectResources projectResources = ResourceManager.getInstance().getProjectResources(
188                     project);
189 
190             if (projectResources != null) {
191                 // create a temporary class loader to load the class
192                 ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */,
193                         project);
194 
195                 try {
196                     Class<?> clazz = loader.loadClass(className);
197 
198                     if (clazz != null) {
199                         // create the maps to store the result of the parsing
200                         Map<ResourceType, Map<String, Integer>> resourceValueMap =
201                             new EnumMap<ResourceType, Map<String, Integer>>(ResourceType.class);
202                         Map<Integer, Pair<ResourceType, String>> genericValueToNameMap =
203                             new HashMap<Integer, Pair<ResourceType, String>>();
204                         Map<IntArrayWrapper, String> styleableValueToNameMap =
205                             new HashMap<IntArrayWrapper, String>();
206 
207                         // parse the class
208                         if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap,
209                                 resourceValueMap)) {
210                             // now we associate the maps to the project.
211                             projectResources.setCompiledResources(genericValueToNameMap,
212                                     styleableValueToNameMap, resourceValueMap);
213                         }
214                     }
215                 } catch (Error e) {
216                     // Log this error with the class name we're trying to load and abort.
217                     AdtPlugin.log(e, "loadAndParseRClass failed to find class %1$s", className); //$NON-NLS-1$
218                 }
219             }
220         } catch (ClassNotFoundException e) {
221             // pass
222         }
223     }
224 
225     /**
226      * Parses a R class, and fills maps.
227      * @param rClass the class to parse
228      * @param genericValueToNameMap
229      * @param styleableValueToNameMap
230      * @param resourceValueMap
231      * @return True if we managed to parse the R class.
232      */
parseClass(Class<?> rClass, Map<Integer, Pair<ResourceType, String>> genericValueToNameMap, Map<IntArrayWrapper, String> styleableValueToNameMap, Map<ResourceType, Map<String, Integer>> resourceValueMap)233     private boolean parseClass(Class<?> rClass,
234             Map<Integer, Pair<ResourceType, String>> genericValueToNameMap,
235             Map<IntArrayWrapper, String> styleableValueToNameMap, Map<ResourceType,
236             Map<String, Integer>> resourceValueMap) {
237         try {
238             for (Class<?> inner : rClass.getDeclaredClasses()) {
239                 String resTypeName = inner.getSimpleName();
240                 ResourceType resType = ResourceType.getEnum(resTypeName);
241 
242                 if (resType != null) {
243                     Map<String, Integer> fullMap = new HashMap<String, Integer>();
244                     resourceValueMap.put(resType, fullMap);
245 
246                     for (Field f : inner.getDeclaredFields()) {
247                         // only process static final fields.
248                         int modifiers = f.getModifiers();
249                         if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
250                             Class<?> type = f.getType();
251                             if (type.isArray() && type.getComponentType() == int.class) {
252                                 // if the object is an int[] we put it in the styleable map
253                                 styleableValueToNameMap.put(
254                                         new IntArrayWrapper((int[]) f.get(null)),
255                                         f.getName());
256                             } else if (type == int.class) {
257                                 Integer value = (Integer) f.get(null);
258                                 genericValueToNameMap.put(value, Pair.of(resType, f.getName()));
259                                 fullMap.put(f.getName(), value);
260                             } else {
261                                 assert false;
262                             }
263                         }
264                     }
265                 }
266             }
267 
268             return true;
269         } catch (IllegalArgumentException e) {
270         } catch (IllegalAccessException e) {
271         }
272         return false;
273     }
274 
275     /**
276      * Returns the class name of the R class, based on the project's manifest's package.
277      *
278      * @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest.
279      */
getRClassName(IProject project)280     private String getRClassName(IProject project) {
281         IFile manifestFile = ProjectHelper.getManifest(project);
282         if (manifestFile != null && manifestFile.isSynchronized(IResource.DEPTH_ZERO)) {
283             ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
284             if (data != null) {
285                 String javaPackage = data.getPackage();
286                 return javaPackage + ".R"; //$NON-NLS-1$
287             }
288         }
289         return null;
290     }
291 
292 }
293