• 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      */
82     @Override
fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind)83     public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
84         IProject project = file.getProject();
85 
86         if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)) {
87             // create the classname
88             String className = getRClassName(project);
89             if (className == null) {
90                 // We need to abort.
91                 AdtPlugin.log(IStatus.ERROR,
92                         "fileChanged: failed to find manifest package for project %1$s", //$NON-NLS-1$
93                         project.getName());
94                 return;
95             }
96             // path will begin with /projectName/bin/classes so we'll ignore that
97             IPath relativeClassPath = file.getFullPath().removeFirstSegments(3);
98             if (packagePathMatches(relativeClassPath.toString(), className)) {
99                 loadAndParseRClass(project, className);
100             }
101         }
102     }
103 
104     /**
105      * Check to see if the package section of the given path matches the packageName.
106      * For example, /project/bin/classes/com/foo/app/R.class should match com.foo.app.R
107      * @param path the pathname of the file to look at
108      * @param packageName the package qualified name of the class
109      * @return true if the package section of the path matches the package qualified name
110      */
packagePathMatches(String path, String packageName)111     private boolean packagePathMatches(String path, String packageName) {
112         // First strip the ".class" off the end of the path
113         String pathWithoutExtension = path.substring(0, path.indexOf(AdtConstants.DOT_CLASS));
114 
115         // then split the components of each path by their separators
116         String [] pathArray = pathWithoutExtension.split(Pattern.quote(File.separator));
117         String [] packageArray = packageName.split(AdtConstants.RE_DOT);
118 
119 
120         int pathIndex = 0;
121         int packageIndex = 0;
122 
123         while (pathIndex < pathArray.length && packageIndex < packageArray.length) {
124             if (pathArray[pathIndex].equals(packageArray[packageIndex]) == false) {
125                 return false;
126             }
127             pathIndex++;
128             packageIndex++;
129         }
130         // We may have matched all the way up to this point, but we're not sure it's a match
131         // unless BOTH paths done
132         return (pathIndex == pathArray.length && packageIndex == packageArray.length);
133     }
134 
135     /**
136      * Processes project close event.
137      */
138     @Override
projectClosed(IProject project)139     public void projectClosed(IProject project) {
140         // the ProjectResources object will be removed by the ResourceManager.
141     }
142 
143     /**
144      * Processes project delete event.
145      */
146     @Override
projectDeleted(IProject project)147     public void projectDeleted(IProject project) {
148         // the ProjectResources object will be removed by the ResourceManager.
149     }
150 
151     /**
152      * Processes project open event.
153      */
154     @Override
projectOpened(IProject project)155     public void projectOpened(IProject project) {
156         // when the project is opened, we get an ADDED event for each file, so we don't
157         // need to do anything here.
158     }
159 
160     @Override
projectRenamed(IProject project, IPath from)161     public void projectRenamed(IProject project, IPath from) {
162         // renamed projects also trigger delete/open event,
163         // so nothing to be done here.
164     }
165 
166     /**
167      * Processes existing project at init time.
168      */
169     @Override
projectOpenedWithWorkspace(IProject project)170     public void projectOpenedWithWorkspace(IProject project) {
171         try {
172             // check this is an android project
173             if (project.hasNature(AdtConstants.NATURE_DEFAULT)) {
174                 String className = getRClassName(project);
175                 // Find the classname
176                 if (className == null) {
177                     // We need to abort.
178                     AdtPlugin.log(IStatus.ERROR,
179                             "projectOpenedWithWorkspace: failed to find manifest package for project %1$s", //$NON-NLS-1$
180                             project.getName());
181                     return;
182                 }
183                 loadAndParseRClass(project, className);
184             }
185         } catch (CoreException e) {
186             // pass
187         }
188     }
189 
190     @Override
allProjectsOpenedWithWorkspace()191     public void allProjectsOpenedWithWorkspace() {
192         // nothing to do.
193     }
194 
195 
loadAndParseRClass(IProject project, String className)196     private void loadAndParseRClass(IProject project, String className) {
197         try {
198             // first check there's a ProjectResources to store the content
199             ProjectResources projectResources = ResourceManager.getInstance().getProjectResources(
200                     project);
201 
202             if (projectResources != null) {
203                 // create a temporary class loader to load the class
204                 ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */,
205                         project);
206 
207                 try {
208                     Class<?> clazz = loader.loadClass(className);
209 
210                     if (clazz != null) {
211                         // create the maps to store the result of the parsing
212                         Map<ResourceType, Map<String, Integer>> resourceValueMap =
213                             new EnumMap<ResourceType, Map<String, Integer>>(ResourceType.class);
214                         Map<Integer, Pair<ResourceType, String>> genericValueToNameMap =
215                             new HashMap<Integer, Pair<ResourceType, String>>();
216                         Map<IntArrayWrapper, String> styleableValueToNameMap =
217                             new HashMap<IntArrayWrapper, String>();
218 
219                         // parse the class
220                         if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap,
221                                 resourceValueMap)) {
222                             // now we associate the maps to the project.
223                             projectResources.setCompiledResources(genericValueToNameMap,
224                                     styleableValueToNameMap, resourceValueMap);
225                         }
226                     }
227                 } catch (Error e) {
228                     // Log this error with the class name we're trying to load and abort.
229                     AdtPlugin.log(e, "loadAndParseRClass failed to find class %1$s", className); //$NON-NLS-1$
230                 }
231             }
232         } catch (ClassNotFoundException e) {
233             // pass
234         }
235     }
236 
237     /**
238      * Parses a R class, and fills maps.
239      * @param rClass the class to parse
240      * @param genericValueToNameMap
241      * @param styleableValueToNameMap
242      * @param resourceValueMap
243      * @return True if we managed to parse the R class.
244      */
parseClass(Class<?> rClass, Map<Integer, Pair<ResourceType, String>> genericValueToNameMap, Map<IntArrayWrapper, String> styleableValueToNameMap, Map<ResourceType, Map<String, Integer>> resourceValueMap)245     private boolean parseClass(Class<?> rClass,
246             Map<Integer, Pair<ResourceType, String>> genericValueToNameMap,
247             Map<IntArrayWrapper, String> styleableValueToNameMap, Map<ResourceType,
248             Map<String, Integer>> resourceValueMap) {
249         try {
250             for (Class<?> inner : rClass.getDeclaredClasses()) {
251                 String resTypeName = inner.getSimpleName();
252                 ResourceType resType = ResourceType.getEnum(resTypeName);
253 
254                 if (resType != null) {
255                     Map<String, Integer> fullMap = new HashMap<String, Integer>();
256                     resourceValueMap.put(resType, fullMap);
257 
258                     for (Field f : inner.getDeclaredFields()) {
259                         // only process static final fields.
260                         int modifiers = f.getModifiers();
261                         if (Modifier.isStatic(modifiers)) {
262                             Class<?> type = f.getType();
263                             if (type.isArray() && type.getComponentType() == int.class) {
264                                 // if the object is an int[] we put it in the styleable map
265                                 styleableValueToNameMap.put(
266                                         new IntArrayWrapper((int[]) f.get(null)),
267                                         f.getName());
268                             } else if (type == int.class) {
269                                 Integer value = (Integer) f.get(null);
270                                 genericValueToNameMap.put(value, Pair.of(resType, f.getName()));
271                                 fullMap.put(f.getName(), value);
272                             } else {
273                                 assert false;
274                             }
275                         }
276                     }
277                 }
278             }
279 
280             return true;
281         } catch (IllegalArgumentException e) {
282         } catch (IllegalAccessException e) {
283         }
284         return false;
285     }
286 
287     /**
288      * Returns the class name of the R class, based on the project's manifest's package.
289      *
290      * @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest.
291      */
getRClassName(IProject project)292     private String getRClassName(IProject project) {
293         IFile manifestFile = ProjectHelper.getManifest(project);
294         if (manifestFile != null && manifestFile.isSynchronized(IResource.DEPTH_ZERO)) {
295             ManifestData data = AndroidManifestHelper.parseForData(manifestFile);
296             if (data != null) {
297                 String javaPackage = data.getPackage();
298                 return javaPackage + ".R"; //$NON-NLS-1$
299             }
300         }
301         return null;
302     }
303 
304 }
305