diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java new file mode 100644 index 000000000..ab5ae4070 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java @@ -0,0 +1,313 @@ +/* + * 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; + } + +} |