blob: ab5ae407013227a90dbccb47fc6f3d9d7d91d43b [file] [log] [blame]
/*
* 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;
}
}