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.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AndroidConstants; 21 import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; 22 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 23 import com.android.ide.eclipse.adt.internal.resources.ResourceType; 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.sdklib.xml.ManifestData; 27 28 import org.eclipse.core.resources.IFile; 29 import org.eclipse.core.resources.IMarkerDelta; 30 import org.eclipse.core.resources.IProject; 31 import org.eclipse.core.resources.IResource; 32 import org.eclipse.core.resources.IResourceDelta; 33 import org.eclipse.core.runtime.CoreException; 34 import org.eclipse.core.runtime.IPath; 35 import org.eclipse.core.runtime.IStatus; 36 37 import java.lang.reflect.Field; 38 import java.lang.reflect.Modifier; 39 import java.util.HashMap; 40 import java.util.Map; 41 42 /** 43 * A monitor for the compiled resources. This only monitors changes in the resources of type 44 * {@link ResourceType#ID}. 45 */ 46 public final class CompiledResourcesMonitor implements IFileListener, IProjectListener { 47 48 private final static CompiledResourcesMonitor sThis = new CompiledResourcesMonitor(); 49 50 /** 51 * Sets up the monitoring system. 52 * @param monitor The main Resource Monitor. 53 */ setupMonitor(GlobalProjectMonitor monitor)54 public static void setupMonitor(GlobalProjectMonitor monitor) { 55 monitor.addFileListener(sThis, IResourceDelta.ADDED | IResourceDelta.CHANGED); 56 monitor.addProjectListener(sThis); 57 } 58 59 /** 60 * private constructor to prevent construction. 61 */ CompiledResourcesMonitor()62 private CompiledResourcesMonitor() { 63 } 64 65 66 /* (non-Javadoc) 67 * Sent when a file changed : if the file is the R class, then it is parsed again to update 68 * the internal data. 69 * 70 * @param file The file that changed. 71 * @param markerDeltas The marker deltas for the file. 72 * @param kind The change kind. This is equivalent to 73 * {@link IResourceDelta#accept(IResourceDeltaVisitor)} 74 * 75 * @see IFileListener#fileChanged 76 */ fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind)77 public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { 78 if (file.getName().equals(AndroidConstants.FN_COMPILED_RESOURCE_CLASS)) { 79 loadAndParseRClass(file.getProject()); 80 } 81 } 82 83 /** 84 * Processes project close event. 85 */ projectClosed(IProject project)86 public void projectClosed(IProject project) { 87 // the ProjectResources object will be removed by the ResourceManager. 88 } 89 90 /** 91 * Processes project delete event. 92 */ projectDeleted(IProject project)93 public void projectDeleted(IProject project) { 94 // the ProjectResources object will be removed by the ResourceManager. 95 } 96 97 /** 98 * Processes project open event. 99 */ projectOpened(IProject project)100 public void projectOpened(IProject project) { 101 // when the project is opened, we get an ADDED event for each file, so we don't 102 // need to do anything here. 103 } 104 projectRenamed(IProject project, IPath from)105 public void projectRenamed(IProject project, IPath from) { 106 // renamed projects also trigger delete/open event, 107 // so nothing to be done here. 108 } 109 110 /** 111 * Processes existing project at init time. 112 */ projectOpenedWithWorkspace(IProject project)113 public void projectOpenedWithWorkspace(IProject project) { 114 try { 115 // check this is an android project 116 if (project.hasNature(AndroidConstants.NATURE_DEFAULT)) { 117 loadAndParseRClass(project); 118 } 119 } catch (CoreException e) { 120 // pass 121 } 122 } 123 loadAndParseRClass(IProject project)124 private void loadAndParseRClass(IProject project) { 125 try { 126 // first check there's a ProjectResources to store the content 127 ProjectResources projectResources = ResourceManager.getInstance().getProjectResources( 128 project); 129 130 if (projectResources != null) { 131 // create the classname 132 String className = getRClassName(project); 133 if (className == null) { 134 // We need to abort. 135 AdtPlugin.log(IStatus.ERROR, 136 "loadAndParseRClass: failed to find manifest package for project %1$s", //$NON-NLS-1$ 137 project.getName()); 138 return; 139 } 140 141 // create a temporary class loader to load it. 142 ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */, 143 project); 144 145 try { 146 Class<?> clazz = loader.loadClass(className); 147 148 if (clazz != null) { 149 // create the maps to store the result of the parsing 150 Map<String, Map<String, Integer>> resourceValueMap = 151 new HashMap<String, Map<String, Integer>>(); 152 Map<Integer, String[]> genericValueToNameMap = 153 new HashMap<Integer, String[]>(); 154 Map<IntArrayWrapper, String> styleableValueToNameMap = 155 new HashMap<IntArrayWrapper, String>(); 156 157 // parse the class 158 if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap, 159 resourceValueMap)) { 160 // now we associate the maps to the project. 161 projectResources.setCompiledResources(genericValueToNameMap, 162 styleableValueToNameMap, resourceValueMap); 163 } 164 } 165 } catch (Error e) { 166 // Log this error with the class name we're trying to load and abort. 167 AdtPlugin.log(e, "loadAndParseRClass failed to find class %1$s", className); //$NON-NLS-1$ 168 } 169 } 170 } catch (ClassNotFoundException e) { 171 // pass 172 } 173 } 174 175 /** 176 * Parses a R class, and fills maps. 177 * @param rClass the class to parse 178 * @param genericValueToNameMap 179 * @param styleableValueToNameMap 180 * @param resourceValueMap 181 * @return True if we managed to parse the R class. 182 */ parseClass(Class<?> rClass, Map<Integer, String[]> genericValueToNameMap, Map<IntArrayWrapper, String> styleableValueToNameMap, Map<String, Map<String, Integer>> resourceValueMap)183 private boolean parseClass(Class<?> rClass, Map<Integer, String[]> genericValueToNameMap, 184 Map<IntArrayWrapper, String> styleableValueToNameMap, Map<String, 185 Map<String, Integer>> resourceValueMap) { 186 try { 187 for (Class<?> inner : rClass.getDeclaredClasses()) { 188 String resType = inner.getSimpleName(); 189 190 Map<String, Integer> fullMap = new HashMap<String, Integer>(); 191 resourceValueMap.put(resType, fullMap); 192 193 for (Field f : inner.getDeclaredFields()) { 194 // only process static final fields. 195 int modifiers = f.getModifiers(); 196 if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) { 197 Class<?> type = f.getType(); 198 if (type.isArray() && type.getComponentType() == int.class) { 199 // if the object is an int[] we put it in the styleable map 200 styleableValueToNameMap.put(new IntArrayWrapper((int[]) f.get(null)), 201 f.getName()); 202 } else if (type == int.class) { 203 Integer value = (Integer) f.get(null); 204 genericValueToNameMap.put(value, new String[] { f.getName(), resType }); 205 fullMap.put(f.getName(), value); 206 } else { 207 assert false; 208 } 209 } 210 } 211 } 212 213 return true; 214 } catch (IllegalArgumentException e) { 215 } catch (IllegalAccessException e) { 216 } 217 return false; 218 } 219 220 /** 221 * Returns the class name of the R class, based on the project's manifest's package. 222 * 223 * @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest. 224 */ getRClassName(IProject project)225 private String getRClassName(IProject project) { 226 IFile manifestFile = ProjectHelper.getManifest(project); 227 if (manifestFile != null && manifestFile.isSynchronized(IResource.DEPTH_ZERO)) { 228 ManifestData data = AndroidManifestHelper.parseForData(manifestFile); 229 if (data != null) { 230 String javaPackage = data.getPackage(); 231 return javaPackage + ".R"; //$NON-NLS-1$ 232 } 233 } 234 return null; 235 } 236 237 } 238