From 3106768f313486f395f4094db73f78bb722289dd Mon Sep 17 00:00:00 2001 From: Xavier Ducrohet Date: Fri, 29 May 2009 15:01:18 -0700 Subject: [PATCH] Make CompiledResourcesMonitor#getRClassName more error proof. --- .../project/AndroidManifestParser.java | 229 +++++++++--------- .../manager/CompiledResourcesMonitor.java | 35 +-- 2 files changed, 134 insertions(+), 130 deletions(-) diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestParser.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestParser.java index 72ff51dcd..3deea23cc 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestParser.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestParser.java @@ -75,28 +75,28 @@ public class AndroidManifestParser { private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ - + public final static int INVALID_MIN_SDK = -1; - + /** * Instrumentation info obtained from manifest */ public static class Instrumentation { private final String mName; private final String mTargetPackage; - + Instrumentation(String name, String targetPackage) { mName = name; mTargetPackage = targetPackage; } - + /** * Returns the fully qualified instrumentation class name */ public String getName() { return mName; } - + /** * Returns the Android app package that is the target of this instrumentation */ @@ -104,7 +104,7 @@ public class AndroidManifestParser { return mTargetPackage; } } - + /** * Activity info obtained from the manifest. */ @@ -114,32 +114,32 @@ public class AndroidManifestParser { private boolean mHasAction = false; private boolean mHasMainAction = false; private boolean mHasLauncherCategory = false; - + public Activity(String name, boolean exported) { mName = name; mIsExported = exported; } - + public String getName() { return mName; } - + public boolean isExported() { return mIsExported; } - + public boolean hasAction() { return mHasAction; } - + public boolean isHomeActivity() { return mHasMainAction && mHasLauncherCategory; } - + void setHasAction(boolean hasAction) { mHasAction = hasAction; } - + /** If the activity doesn't yet have a filter set for the launcher, this resets both * flags. This is to handle multiple intent-filters where one could have the valid * action, and another one of the valid category. @@ -149,16 +149,16 @@ public class AndroidManifestParser { mHasMainAction = mHasLauncherCategory = false; } } - + void setHasMainAction(boolean hasMainAction) { mHasMainAction = hasMainAction; } - + void setHasLauncherCategory(boolean hasLauncherCategory) { mHasLauncherCategory = hasLauncherCategory; } } - + /** * XML error & data handler used when parsing the AndroidManifest.xml file. *

@@ -166,9 +166,9 @@ public class AndroidManifestParser { * to collect data from the manifest. */ private static class ManifestHandler extends XmlErrorHandler { - + //--- data read from the parsing - + /** Application package */ private String mPackage; /** List of all activities */ @@ -196,10 +196,10 @@ public class AndroidManifestParser { private int mValidLevel = 0; private Activity mCurrentActivity = null; private Locator mLocator; - + /** * Creates a new {@link ManifestHandler}, which is also an {@link XmlErrorHandler}. - * + * * @param manifestFile The manifest file being parsed. Can be null. * @param errorListener An optional error listener. * @param gatherData True if data should be gathered. @@ -221,24 +221,24 @@ public class AndroidManifestParser { String getPackage() { return mPackage; } - - /** + + /** * Returns the list of activities found in the manifest. * @return An array of fully qualified class names, or empty if no activity were found. */ Activity[] getActivities() { return mActivities.toArray(new Activity[mActivities.size()]); } - + /** * Returns the name of one activity found in the manifest, that is configured to show - * up in the HOME screen. - * @return the fully qualified name of a HOME activity or null if none were found. + * up in the HOME screen. + * @return the fully qualified name of a HOME activity or null if none were found. */ Activity getLauncherActivity() { return mLauncherActivity; } - + /** * Returns the list of process names declared by the manifest. */ @@ -246,42 +246,42 @@ public class AndroidManifestParser { if (mProcesses != null) { return mProcesses.toArray(new String[mProcesses.size()]); } - + return new String[0]; } - + /** * Returns the debuggable attribute value or null if it is not set. */ Boolean getDebuggable() { return mDebuggable; } - + /** * Returns the minSdkVersion attribute, or - * {@link AndroidManifestParser#INVALID_MIN_SDK} if it's not set. + * {@link AndroidManifestParser#INVALID_MIN_SDK} if it's not set. */ int getApiLevelRequirement() { return mApiLevelRequirement; } - - /** + + /** * Returns the list of instrumentations found in the manifest. - * @return An array of {@link Instrumentation}, or empty if no instrumentations were + * @return An array of {@link Instrumentation}, or empty if no instrumentations were * found. */ Instrumentation[] getInstrumentations() { return mInstrumentations.toArray(new Instrumentation[mInstrumentations.size()]); } - - /** + + /** * Returns the list of libraries in use found in the manifest. * @return An array of library names, or empty if no libraries were found. */ String[] getUsesLibraries() { return mLibraries.toArray(new String[mLibraries.size()]); } - + /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator) */ @@ -290,7 +290,7 @@ public class AndroidManifestParser { mLocator = locator; super.setDocumentLocator(locator); } - + /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, * java.lang.String, org.xml.sax.Attributes) @@ -322,18 +322,18 @@ public class AndroidManifestParser { if (value != null) { addProcessName(value); } - + value = getAttributeValue(attributes, ATTRIBUTE_DEBUGGABLE, true /* hasNamespace*/); if (value != null) { mDebuggable = Boolean.parseBoolean(value); } - + mValidLevel++; } else if (NODE_USES_SDK.equals(localName)) { value = getAttributeValue(attributes, ATTRIBUTE_MIN_SDK_VERSION, true /* hasNamespace */); - + if (value != null) { try { mApiLevelRequirement = Integer.parseInt(value); @@ -343,7 +343,7 @@ public class AndroidManifestParser { } } else if (NODE_INSTRUMENTATION.equals(localName)) { processInstrumentationNode(attributes); - } + } break; case LEVEL_ACTIVITY: if (NODE_ACTIVITY.equals(localName)) { @@ -364,7 +364,7 @@ public class AndroidManifestParser { if (value != null) { mLibraries.add(value); } - } + } break; case LEVEL_INTENT_FILTER: // only process this level if we are in an activity @@ -391,7 +391,7 @@ public class AndroidManifestParser { mCurrentActivity.setHasLauncherCategory(true); } } - + // no need to increase mValidLevel as we don't process anything // below this level. } @@ -415,13 +415,13 @@ public class AndroidManifestParser { if (mGatherData == false) { return; } - + // decrement the levels. if (mValidLevel == mCurrentLevel) { mValidLevel--; } mCurrentLevel--; - + // if we're at a valid level // process the end of the element if (mValidLevel == mCurrentLevel) { @@ -442,13 +442,13 @@ public class AndroidManifestParser { default: break; } - + } } finally { super.endElement(uri, localName, name); } } - + /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) */ @@ -478,7 +478,7 @@ public class AndroidManifestParser { super.warning(e); } } - + /** * Processes the activity node. * @param attributes the attributes for the activity node. @@ -489,14 +489,14 @@ public class AndroidManifestParser { true /* hasNamespace */); if (activityName != null) { activityName = combinePackageAndClassName(mPackage, activityName); - + // get the exported flag. String exportedStr = getAttributeValue(attributes, ATTRIBUTE_EXPORTED, true); boolean exported = exportedStr == null || exportedStr.toLowerCase().equals("true"); // $NON-NLS-1$ mCurrentActivity = new Activity(activityName, exported); mActivities.add(mCurrentActivity); - + if (mMarkErrors) { checkClass(activityName, AndroidConstants.CLASS_ACTIVITY, true /* testVisibility */); @@ -506,7 +506,7 @@ public class AndroidManifestParser { // so we don't have to do anything mCurrentActivity = null; } - + String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS, true /* hasNamespace */); if (processName != null) { @@ -526,19 +526,19 @@ public class AndroidManifestParser { true /* hasNamespace */); if (serviceName != null) { serviceName = combinePackageAndClassName(mPackage, serviceName); - + if (mMarkErrors) { checkClass(serviceName, superClassName, false /* testVisibility */); } } - + String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS, true /* hasNamespace */); if (processName != null) { addProcessName(processName); } } - + /** * Processes the instrumentation nodes. * @param attributes the attributes for the activity node. @@ -563,7 +563,7 @@ public class AndroidManifestParser { /** * Checks that a class is valid and can be used in the Android Manifest. *

- * Errors are put as {@link IMarker} on the manifest file. + * Errors are put as {@link IMarker} on the manifest file. * @param className the fully qualified name of the class to test. * @param superClassName the fully qualified name of the class it is supposed to extend. * @param testVisibility if true, the method will check the visibility of @@ -579,12 +579,12 @@ public class AndroidManifestParser { if (result != BaseProjectHelper.TEST_CLASS_OK) { // get the line number int line = mLocator.getLineNumber(); - + // mark the file IMarker marker = BaseProjectHelper.addMarker(getFile(), AndroidConstants.MARKER_ANDROID, result, line, IMarker.SEVERITY_ERROR); - + // add custom attributes to be used by the manifest editor. if (marker != null) { try { @@ -594,7 +594,7 @@ public class AndroidManifestParser { } catch (CoreException e) { } } - } + } } /** @@ -616,21 +616,21 @@ public class AndroidManifestParser { return attributes.getValue(i); } } - + return null; } - + private void addProcessName(String processName) { if (mProcesses == null) { mProcesses = new TreeSet(); } - + mProcesses.add(processName); } } private static SAXParserFactory sParserFactory; - + private final String mJavaPackage; private final Activity[] mActivities; private final Activity mLauncherActivity; @@ -644,14 +644,14 @@ public class AndroidManifestParser { sParserFactory = SAXParserFactory.newInstance(); sParserFactory.setNamespaceAware(true); } - + /** * Parses the Android Manifest, and returns an object containing the result of the parsing. *

* This method is useful to parse a specific {@link IFile} in a Java project. *

* If you only want to gather data, consider {@link #parseForData(IFile)} instead. - * + * * @param javaProject The java project. * @param manifestFile the {@link IFile} representing the manifest file. * @param errorListener @@ -670,41 +670,42 @@ public class AndroidManifestParser { boolean markErrors) throws CoreException { try { - SAXParser parser = sParserFactory.newSAXParser(); + if (manifestFile != null) { + SAXParser parser = sParserFactory.newSAXParser(); - ManifestHandler manifestHandler = new ManifestHandler(manifestFile, - errorListener, gatherData, javaProject, markErrors); - parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); - - // get the result from the handler - - return new AndroidManifestParser(manifestHandler.getPackage(), - manifestHandler.getActivities(), - manifestHandler.getLauncherActivity(), - manifestHandler.getProcesses(), - manifestHandler.getDebuggable(), - manifestHandler.getApiLevelRequirement(), - manifestHandler.getInstrumentations(), - manifestHandler.getUsesLibraries()); + ManifestHandler manifestHandler = new ManifestHandler(manifestFile, + errorListener, gatherData, javaProject, markErrors); + parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); + + // get the result from the handler + return new AndroidManifestParser(manifestHandler.getPackage(), + manifestHandler.getActivities(), + manifestHandler.getLauncherActivity(), + manifestHandler.getProcesses(), + manifestHandler.getDebuggable(), + manifestHandler.getApiLevelRequirement(), + manifestHandler.getInstrumentations(), + manifestHandler.getUsesLibraries()); + } } catch (ParserConfigurationException e) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "Bad parser configuration for %s: %s", manifestFile.getFullPath(), e.getMessage()); } catch (SAXException e) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "Parser exception for %s: %s", manifestFile.getFullPath(), e.getMessage()); } catch (IOException e) { // Don't log a console error when failing to read a non-existing file if (!(e instanceof FileNotFoundException)) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "I/O error for %s: %s", manifestFile.getFullPath(), e.getMessage()); } - } + } return null; } @@ -716,7 +717,7 @@ public class AndroidManifestParser { * parsing a file that is not part of an Eclipse Java project. *

* It assumes errors cannot be marked on the file and that data gathering is enabled. - * + * * @param manifestFile the manifest file to parse. * @return an {@link AndroidManifestParser} or null if the parsing failed. * @throws CoreException @@ -733,11 +734,11 @@ public class AndroidManifestParser { null, //javaProject false //markErrors ); - + parser.parse(new InputSource(new FileReader(manifestFile)), manifestHandler); - + // get the result from the handler - + return new AndroidManifestParser(manifestHandler.getPackage(), manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), @@ -747,25 +748,25 @@ public class AndroidManifestParser { manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries()); } catch (ParserConfigurationException e) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "Bad parser configuration for %s: %s", manifestFile.getAbsolutePath(), e.getMessage()); } catch (SAXException e) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "Parser exception for %s: %s", manifestFile.getAbsolutePath(), e.getMessage()); } catch (IOException e) { // Don't log a console error when failing to read a non-existing file if (!(e instanceof FileNotFoundException)) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "I/O error for %s: %s", manifestFile.getAbsolutePath(), e.getMessage()); } } - + return null; } @@ -788,9 +789,9 @@ public class AndroidManifestParser { boolean gatherData, boolean markErrors) throws CoreException { - + IFile manifestFile = getManifest(javaProject.getProject()); - + try { SAXParser parser = sParserFactory.newSAXParser(); @@ -799,25 +800,25 @@ public class AndroidManifestParser { errorListener, gatherData, javaProject, markErrors); parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); - + // get the result from the handler return new AndroidManifestParser(manifestHandler.getPackage(), manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), manifestHandler.getProcesses(), manifestHandler.getDebuggable(), - manifestHandler.getApiLevelRequirement(), + manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries()); } } catch (ParserConfigurationException e) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "Bad parser configuration for %s", manifestFile.getFullPath()); } catch (SAXException e) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "Parser exception for %s", manifestFile.getFullPath()); } catch (IOException e) { - AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), + AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(), "I/O error for %s", manifestFile.getFullPath()); - } - + } + return null; } @@ -849,7 +850,7 @@ public class AndroidManifestParser { /** * Parses the manifest file, and collects data. - * + * * @param osManifestFilePath The OS path of the manifest file to parse. * @return an {@link AndroidManifestParser} or null if the parsing failed. */ @@ -871,7 +872,7 @@ public class AndroidManifestParser { return mJavaPackage; } - /** + /** * Returns the list of activities found in the manifest. * @return An array of {@link Activity}, or empty if no activity were found. */ @@ -881,35 +882,35 @@ public class AndroidManifestParser { /** * Returns the name of one activity found in the manifest, that is configured to show - * up in the HOME screen. - * @return The {@link Activity} representing a HOME activity or null if none were found. + * up in the HOME screen. + * @return The {@link Activity} representing a HOME activity or null if none were found. */ public Activity getLauncherActivity() { return mLauncherActivity; } - + /** * Returns the list of process names declared by the manifest. */ public String[] getProcesses() { return mProcesses; } - + /** * Returns the debuggable attribute value or null if it is not set. */ public Boolean getDebuggable() { return mDebuggable; } - + /** * Returns the minSdkVersion attribute, or {@link #INVALID_MIN_SDK} - * if it's not set. + * if it's not set. */ public int getApiLevelRequirement() { return mApiLevelRequirement; } - + /** * Returns the list of instrumentations found in the manifest. * @return An array of {@link Instrumentation}, or empty if no instrumentations were found. @@ -917,7 +918,7 @@ public class AndroidManifestParser { public Instrumentation[] getInstrumentations() { return mInstrumentations; } - + /** * Returns the list of libraries in use found in the manifest. * @return An array of library names, or empty if no uses-library declarations were found. @@ -926,7 +927,7 @@ public class AndroidManifestParser { return mLibraries; } - + /** * Private constructor to enforce using * {@link #parse(IJavaProject, XmlErrorListener, boolean, boolean)}, @@ -977,7 +978,7 @@ public class AndroidManifestParser { * Combines a java package, with a class value from the manifest to make a fully qualified * class name * @param javaPackage the java package from the manifest. - * @param className the class name from the manifest. + * @param className the class name from the manifest. * @return the fully qualified class name. */ public static String combinePackageAndClassName(String javaPackage, String className) { @@ -1010,8 +1011,8 @@ public class AndroidManifestParser { * Given a fully qualified activity name (e.g. com.foo.test.MyClass) and given a project * package base name (e.g. com.foo), returns the relative activity name that would be used * the "name" attribute of an "activity" element. - * - * @param fullActivityName a fully qualified activity class name, e.g. "com.foo.test.MyClass" + * + * @param fullActivityName a fully qualified activity class name, e.g. "com.foo.test.MyClass" * @param packageName The project base package name, e.g. "com.foo" * @return The relative activity name if it can be computed or the original fullActivityName. */ diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java index 93c27b70a..df716b268 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java @@ -26,6 +26,7 @@ import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IP 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.IStatus; @@ -42,7 +43,7 @@ import java.util.Map; 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. @@ -62,12 +63,12 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi /* (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 */ public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { @@ -111,7 +112,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi // pass } } - + private void loadAndParseRClass(IProject project) { try { // first check there's a ProjectResources to store the content @@ -129,13 +130,13 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi return; } - // create a temporary class loader to load it. + // create a temporary class loader to load it. 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> resourceValueMap = @@ -144,7 +145,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi new HashMap(); Map styleableValueToNameMap = new HashMap(); - + // parse the class if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap, resourceValueMap)) { @@ -180,7 +181,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi Map fullMap = new HashMap(); resourceValueMap.put(resType, fullMap); - + for (Field f : inner.getDeclaredFields()) { // only process static final fields. int modifiers = f.getModifiers(); @@ -191,7 +192,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi styleableValueToNameMap.put(new IntArrayWrapper((int[]) f.get(null)), f.getName()); } else if (type == int.class) { - Integer value = (Integer) f.get(null); + Integer value = (Integer) f.get(null); genericValueToNameMap.put(value, new String[] { f.getName(), resType }); fullMap.put(f.getName(), value); } else { @@ -210,16 +211,18 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi /** * 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) { try { IFile manifestFile = AndroidManifestParser.getManifest(project); - AndroidManifestParser data = AndroidManifestParser.parseForData(manifestFile); - if (data != null) { - String javaPackage = data.getPackage(); - return javaPackage + ".R"; //$NON-NLS-1$ + if (manifestFile != null && manifestFile.isSynchronized(IResource.DEPTH_ZERO)) { + AndroidManifestParser data = AndroidManifestParser.parseForData(manifestFile); + if (data != null) { + String javaPackage = data.getPackage(); + return javaPackage + ".R"; //$NON-NLS-1$ + } } } catch (CoreException e) { // This will typically happen either because the manifest file is not present @@ -232,5 +235,5 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi } return null; } - + }