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