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