| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.eclipse.org/org/documents/epl-v10.php |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.ide.eclipse.adt.internal.resources.manager; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.common.resources.IntArrayWrapper; |
| import com.android.ide.common.xml.ManifestData; |
| import com.android.ide.eclipse.adt.AdtConstants; |
| import com.android.ide.eclipse.adt.AdtPlugin; |
| import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; |
| import com.android.ide.eclipse.adt.internal.project.ProjectHelper; |
| import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; |
| import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener; |
| import com.android.resources.ResourceType; |
| import com.android.util.Pair; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IMarkerDelta; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| |
| import java.io.File; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.util.EnumMap; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| /** |
| * A monitor for the compiled resources. This only monitors changes in the resources of type |
| * {@link ResourceType#ID}. |
| */ |
| public final class CompiledResourcesMonitor implements IFileListener, IProjectListener { |
| |
| private final static CompiledResourcesMonitor sThis = new CompiledResourcesMonitor(); |
| |
| /** |
| * Sets up the monitoring system. |
| * @param monitor The main Resource Monitor. |
| */ |
| public static void setupMonitor(GlobalProjectMonitor monitor) { |
| monitor.addFileListener(sThis, IResourceDelta.ADDED | IResourceDelta.CHANGED); |
| monitor.addProjectListener(sThis); |
| } |
| |
| /** |
| * private constructor to prevent construction. |
| */ |
| private CompiledResourcesMonitor() { |
| } |
| |
| |
| /* (non-Javadoc) |
| * Sent when a file changed : if the file is the R class, then it is parsed again to update |
| * the internal data. |
| * |
| * @param file The file that changed. |
| * @param markerDeltas The marker deltas for the file. |
| * @param kind The change kind. This is equivalent to |
| * {@link IResourceDelta#accept(IResourceDeltaVisitor)} |
| * |
| * @see IFileListener#fileChanged |
| */ |
| @Override |
| public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas, |
| int kind, @Nullable String extension, int flags, boolean isAndroidProject) { |
| if (!isAndroidProject || flags == IResourceDelta.MARKERS) { |
| // Not Android or only the markers changed: not relevant |
| return; |
| } |
| |
| IProject project = file.getProject(); |
| |
| if (file.getName().equals(SdkConstants.FN_COMPILED_RESOURCE_CLASS)) { |
| // create the classname |
| String className = getRClassName(project); |
| if (className == null) { |
| // We need to abort. |
| AdtPlugin.log(IStatus.ERROR, |
| "fileChanged: failed to find manifest package for project %1$s", //$NON-NLS-1$ |
| project.getName()); |
| return; |
| } |
| // path will begin with /projectName/bin/classes so we'll ignore that |
| IPath relativeClassPath = file.getFullPath().removeFirstSegments(3); |
| if (packagePathMatches(relativeClassPath.toString(), className)) { |
| loadAndParseRClass(project, className); |
| } |
| } |
| } |
| |
| /** |
| * Check to see if the package section of the given path matches the packageName. |
| * For example, /project/bin/classes/com/foo/app/R.class should match com.foo.app.R |
| * @param path the pathname of the file to look at |
| * @param packageName the package qualified name of the class |
| * @return true if the package section of the path matches the package qualified name |
| */ |
| private boolean packagePathMatches(String path, String packageName) { |
| // First strip the ".class" off the end of the path |
| String pathWithoutExtension = path.substring(0, path.indexOf(SdkConstants.DOT_CLASS)); |
| |
| // then split the components of each path by their separators |
| String [] pathArray = pathWithoutExtension.split(Pattern.quote(File.separator)); |
| String [] packageArray = packageName.split(AdtConstants.RE_DOT); |
| |
| |
| int pathIndex = 0; |
| int packageIndex = 0; |
| |
| while (pathIndex < pathArray.length && packageIndex < packageArray.length) { |
| if (pathArray[pathIndex].equals(packageArray[packageIndex]) == false) { |
| return false; |
| } |
| pathIndex++; |
| packageIndex++; |
| } |
| // We may have matched all the way up to this point, but we're not sure it's a match |
| // unless BOTH paths done |
| return (pathIndex == pathArray.length && packageIndex == packageArray.length); |
| } |
| |
| /** |
| * Processes project close event. |
| */ |
| @Override |
| public void projectClosed(IProject project) { |
| // the ProjectResources object will be removed by the ResourceManager. |
| } |
| |
| /** |
| * Processes project delete event. |
| */ |
| @Override |
| public void projectDeleted(IProject project) { |
| // the ProjectResources object will be removed by the ResourceManager. |
| } |
| |
| /** |
| * Processes project open event. |
| */ |
| @Override |
| public void projectOpened(IProject project) { |
| // when the project is opened, we get an ADDED event for each file, so we don't |
| // need to do anything here. |
| } |
| |
| @Override |
| public void projectRenamed(IProject project, IPath from) { |
| // renamed projects also trigger delete/open event, |
| // so nothing to be done here. |
| } |
| |
| /** |
| * Processes existing project at init time. |
| */ |
| @Override |
| public void projectOpenedWithWorkspace(IProject project) { |
| try { |
| // check this is an android project |
| if (project.hasNature(AdtConstants.NATURE_DEFAULT)) { |
| String className = getRClassName(project); |
| // Find the classname |
| if (className == null) { |
| // We need to abort. |
| AdtPlugin.log(IStatus.ERROR, |
| "projectOpenedWithWorkspace: failed to find manifest package for project %1$s", //$NON-NLS-1$ |
| project.getName()); |
| return; |
| } |
| loadAndParseRClass(project, className); |
| } |
| } catch (CoreException e) { |
| // pass |
| } |
| } |
| |
| @Override |
| public void allProjectsOpenedWithWorkspace() { |
| // nothing to do. |
| } |
| |
| |
| private void loadAndParseRClass(IProject project, String className) { |
| try { |
| // first check there's a ProjectResources to store the content |
| ProjectResources projectResources = ResourceManager.getInstance().getProjectResources( |
| project); |
| |
| if (projectResources != null) { |
| // create a temporary class loader to load the class |
| ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */, |
| project); |
| |
| try { |
| Class<?> clazz = loader.loadClass(className); |
| |
| if (clazz != null) { |
| // create the maps to store the result of the parsing |
| Map<ResourceType, Map<String, Integer>> resourceValueMap = |
| new EnumMap<ResourceType, Map<String, Integer>>(ResourceType.class); |
| Map<Integer, Pair<ResourceType, String>> genericValueToNameMap = |
| new HashMap<Integer, Pair<ResourceType, String>>(); |
| Map<IntArrayWrapper, String> styleableValueToNameMap = |
| new HashMap<IntArrayWrapper, String>(); |
| |
| // parse the class |
| if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap, |
| resourceValueMap)) { |
| // now we associate the maps to the project. |
| projectResources.setCompiledResources(genericValueToNameMap, |
| styleableValueToNameMap, resourceValueMap); |
| } |
| } |
| } catch (Error e) { |
| // Log this error with the class name we're trying to load and abort. |
| AdtPlugin.log(e, "loadAndParseRClass failed to find class %1$s", className); //$NON-NLS-1$ |
| } |
| } |
| } catch (ClassNotFoundException e) { |
| // pass |
| } |
| } |
| |
| /** |
| * Parses a R class, and fills maps. |
| * @param rClass the class to parse |
| * @param genericValueToNameMap |
| * @param styleableValueToNameMap |
| * @param resourceValueMap |
| * @return True if we managed to parse the R class. |
| */ |
| private boolean parseClass(Class<?> rClass, |
| Map<Integer, Pair<ResourceType, String>> genericValueToNameMap, |
| Map<IntArrayWrapper, String> styleableValueToNameMap, Map<ResourceType, |
| Map<String, Integer>> resourceValueMap) { |
| try { |
| for (Class<?> inner : rClass.getDeclaredClasses()) { |
| String resTypeName = inner.getSimpleName(); |
| ResourceType resType = ResourceType.getEnum(resTypeName); |
| |
| if (resType != null) { |
| Map<String, Integer> fullMap = new HashMap<String, Integer>(); |
| resourceValueMap.put(resType, fullMap); |
| |
| for (Field f : inner.getDeclaredFields()) { |
| // only process static final fields. |
| int modifiers = f.getModifiers(); |
| if (Modifier.isStatic(modifiers)) { |
| Class<?> type = f.getType(); |
| if (type.isArray() && type.getComponentType() == int.class) { |
| // if the object is an int[] we put it in the styleable map |
| styleableValueToNameMap.put( |
| new IntArrayWrapper((int[]) f.get(null)), |
| f.getName()); |
| } else if (type == int.class) { |
| Integer value = (Integer) f.get(null); |
| genericValueToNameMap.put(value, Pair.of(resType, f.getName())); |
| fullMap.put(f.getName(), value); |
| } else { |
| assert false; |
| } |
| } |
| } |
| } |
| } |
| |
| return true; |
| } catch (IllegalArgumentException e) { |
| } catch (IllegalAccessException e) { |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the class name of the R class, based on the project's manifest's package. |
| * |
| * @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest. |
| */ |
| private String getRClassName(IProject project) { |
| IFile manifestFile = ProjectHelper.getManifest(project); |
| if (manifestFile != null && manifestFile.isSynchronized(IResource.DEPTH_ZERO)) { |
| ManifestData data = AndroidManifestHelper.parseForData(manifestFile); |
| if (data != null) { |
| String javaPackage = data.getPackage(); |
| return javaPackage + ".R"; //$NON-NLS-1$ |
| } |
| } |
| return null; |
| } |
| |
| } |