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 */ 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 */ 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 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 */ 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 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 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 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 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 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 191 public void allProjectsOpenedWithWorkspace() { 192 // nothing to do. 193 } 194 195 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 */ 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 */ 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