Home | History | Annotate | Download | only in xml
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
      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.sdklib.xml;
     18 
     19 import com.android.io.IAbstractFile;
     20 import com.android.io.IAbstractFolder;
     21 import com.android.io.StreamException;
     22 import com.android.resources.Keyboard;
     23 import com.android.resources.Navigation;
     24 import com.android.resources.TouchScreen;
     25 import com.android.sdklib.SdkConstants;
     26 import com.android.sdklib.xml.ManifestData.Activity;
     27 import com.android.sdklib.xml.ManifestData.Instrumentation;
     28 import com.android.sdklib.xml.ManifestData.SupportsScreens;
     29 import com.android.sdklib.xml.ManifestData.UsesConfiguration;
     30 import com.android.sdklib.xml.ManifestData.UsesFeature;
     31 import com.android.sdklib.xml.ManifestData.UsesLibrary;
     32 
     33 import org.xml.sax.Attributes;
     34 import org.xml.sax.ErrorHandler;
     35 import org.xml.sax.InputSource;
     36 import org.xml.sax.Locator;
     37 import org.xml.sax.SAXException;
     38 import org.xml.sax.SAXParseException;
     39 import org.xml.sax.helpers.DefaultHandler;
     40 
     41 import java.io.FileNotFoundException;
     42 import java.io.IOException;
     43 import java.io.InputStream;
     44 import java.util.Locale;
     45 
     46 import javax.xml.parsers.ParserConfigurationException;
     47 import javax.xml.parsers.SAXParser;
     48 import javax.xml.parsers.SAXParserFactory;
     49 
     50 public class AndroidManifestParser {
     51 
     52     private final static int LEVEL_TOP = 0;
     53     private final static int LEVEL_INSIDE_MANIFEST = 1;
     54     private final static int LEVEL_INSIDE_APPLICATION = 2;
     55     private final static int LEVEL_INSIDE_APP_COMPONENT = 3;
     56     private final static int LEVEL_INSIDE_INTENT_FILTER = 4;
     57 
     58     private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$
     59     private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$
     60 
     61     public interface ManifestErrorHandler extends ErrorHandler {
     62         /**
     63          * Handles a parsing error and an optional line number.
     64          */
     65         void handleError(Exception exception, int lineNumber);
     66 
     67         /**
     68          * Checks that a class is valid and can be used in the Android Manifest.
     69          * <p/>
     70          * Errors are put as {@code org.eclipse.core.resources.IMarker} on the manifest file.
     71          *
     72          * @param locator
     73          * @param className the fully qualified name of the class to test.
     74          * @param superClassName the fully qualified name of the class it is supposed to extend.
     75          * @param testVisibility if <code>true</code>, the method will check the visibility of
     76          * the class or of its constructors.
     77          */
     78         void checkClass(Locator locator, String className, String superClassName,
     79                 boolean testVisibility);
     80     }
     81 
     82     /**
     83      * XML error & data handler used when parsing the AndroidManifest.xml file.
     84      * <p/>
     85      * During parsing this will fill up the {@link ManifestData} object given to the constructor
     86      * and call out errors to the given {@link ManifestErrorHandler}.
     87      */
     88     private static class ManifestHandler extends DefaultHandler {
     89 
     90         //--- temporary data/flags used during parsing
     91         private final ManifestData mManifestData;
     92         private final ManifestErrorHandler mErrorHandler;
     93         private int mCurrentLevel = 0;
     94         private int mValidLevel = 0;
     95         private Activity mCurrentActivity = null;
     96         private Locator mLocator;
     97 
     98         /**
     99          * Creates a new {@link ManifestHandler}.
    100          *
    101          * @param manifestFile The manifest file being parsed. Can be null.
    102          * @param manifestData Class containing the manifest info obtained during the parsing.
    103          * @param errorHandler An optional error handler.
    104          */
    105         ManifestHandler(IAbstractFile manifestFile, ManifestData manifestData,
    106                 ManifestErrorHandler errorHandler) {
    107             super();
    108             mManifestData = manifestData;
    109             mErrorHandler = errorHandler;
    110         }
    111 
    112         /* (non-Javadoc)
    113          * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
    114          */
    115         @Override
    116         public void setDocumentLocator(Locator locator) {
    117             mLocator = locator;
    118             super.setDocumentLocator(locator);
    119         }
    120 
    121         /* (non-Javadoc)
    122          * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
    123          * java.lang.String, org.xml.sax.Attributes)
    124          */
    125         @Override
    126         public void startElement(String uri, String localName, String name, Attributes attributes)
    127                 throws SAXException {
    128             try {
    129                 if (mManifestData == null) {
    130                     return;
    131                 }
    132 
    133                 // if we're at a valid level
    134                 if (mValidLevel == mCurrentLevel) {
    135                     String value;
    136                     switch (mValidLevel) {
    137                         case LEVEL_TOP:
    138                             if (AndroidManifest.NODE_MANIFEST.equals(localName)) {
    139                                 // lets get the package name.
    140                                 mManifestData.mPackage = getAttributeValue(attributes,
    141                                         AndroidManifest.ATTRIBUTE_PACKAGE,
    142                                         false /* hasNamespace */);
    143 
    144                                 // and the versionCode
    145                                 String tmp = getAttributeValue(attributes,
    146                                         AndroidManifest.ATTRIBUTE_VERSIONCODE, true);
    147                                 if (tmp != null) {
    148                                     try {
    149                                         mManifestData.mVersionCode = Integer.valueOf(tmp);
    150                                     } catch (NumberFormatException e) {
    151                                         // keep null in the field.
    152                                     }
    153                                 }
    154                                 mValidLevel++;
    155                             }
    156                             break;
    157                         case LEVEL_INSIDE_MANIFEST:
    158                             if (AndroidManifest.NODE_APPLICATION.equals(localName)) {
    159                                 value = getAttributeValue(attributes,
    160                                         AndroidManifest.ATTRIBUTE_PROCESS,
    161                                         true /* hasNamespace */);
    162                                 if (value != null) {
    163                                     mManifestData.addProcessName(value);
    164                                 }
    165 
    166                                 value = getAttributeValue(attributes,
    167                                         AndroidManifest.ATTRIBUTE_DEBUGGABLE,
    168                                         true /* hasNamespace*/);
    169                                 if (value != null) {
    170                                     mManifestData.mDebuggable = Boolean.parseBoolean(value);
    171                                 }
    172 
    173                                 mValidLevel++;
    174                             } else if (AndroidManifest.NODE_USES_SDK.equals(localName)) {
    175                                 mManifestData.setMinSdkVersionString(getAttributeValue(attributes,
    176                                         AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION,
    177                                         true /* hasNamespace */));
    178                                 mManifestData.setTargetSdkVersionString(getAttributeValue(attributes,
    179                                         AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION,
    180                                         true /* hasNamespace */));
    181                             } else if (AndroidManifest.NODE_INSTRUMENTATION.equals(localName)) {
    182                                 processInstrumentationNode(attributes);
    183 
    184                             } else if (AndroidManifest.NODE_SUPPORTS_SCREENS.equals(localName)) {
    185                                 processSupportsScreensNode(attributes);
    186 
    187                             } else if (AndroidManifest.NODE_USES_CONFIGURATION.equals(localName)) {
    188                                 processUsesConfiguration(attributes);
    189 
    190                             } else if (AndroidManifest.NODE_USES_FEATURE.equals(localName)) {
    191                                 UsesFeature feature = new UsesFeature();
    192 
    193                                 // get the name
    194                                 value = getAttributeValue(attributes,
    195                                         AndroidManifest.ATTRIBUTE_NAME,
    196                                         true /* hasNamespace */);
    197                                 if (value != null) {
    198                                     feature.mName = value;
    199                                 }
    200 
    201                                 // read the required attribute
    202                                 value = getAttributeValue(attributes,
    203                                         AndroidManifest.ATTRIBUTE_REQUIRED,
    204                                         true /*hasNamespace*/);
    205                                 if (value != null) {
    206                                     Boolean b = Boolean.valueOf(value);
    207                                     if (b != null) {
    208                                         feature.mRequired = b;
    209                                     }
    210                                 }
    211 
    212                                 // read the gl es attribute
    213                                 value = getAttributeValue(attributes,
    214                                         AndroidManifest.ATTRIBUTE_GLESVERSION,
    215                                         true /*hasNamespace*/);
    216                                 if (value != null) {
    217                                     try {
    218                                         int version = Integer.decode(value);
    219                                         feature.mGlEsVersion = version;
    220                                     } catch (NumberFormatException e) {
    221                                         // ignore
    222                                     }
    223 
    224                                 }
    225 
    226                                 mManifestData.mFeatures.add(feature);
    227                             }
    228                             break;
    229                         case LEVEL_INSIDE_APPLICATION:
    230                             if (AndroidManifest.NODE_ACTIVITY.equals(localName)) {
    231                                 processActivityNode(attributes);
    232                                 mValidLevel++;
    233                             } else if (AndroidManifest.NODE_SERVICE.equals(localName)) {
    234                                 processNode(attributes, SdkConstants.CLASS_SERVICE);
    235                                 mValidLevel++;
    236                             } else if (AndroidManifest.NODE_RECEIVER.equals(localName)) {
    237                                 processNode(attributes, SdkConstants.CLASS_BROADCASTRECEIVER);
    238                                 mValidLevel++;
    239                             } else if (AndroidManifest.NODE_PROVIDER.equals(localName)) {
    240                                 processNode(attributes, SdkConstants.CLASS_CONTENTPROVIDER);
    241                                 mValidLevel++;
    242                             } else if (AndroidManifest.NODE_USES_LIBRARY.equals(localName)) {
    243                                 value = getAttributeValue(attributes,
    244                                         AndroidManifest.ATTRIBUTE_NAME,
    245                                         true /* hasNamespace */);
    246                                 if (value != null) {
    247                                     UsesLibrary library = new UsesLibrary();
    248                                     library.mName = value;
    249 
    250                                     // read the required attribute
    251                                     value = getAttributeValue(attributes,
    252                                             AndroidManifest.ATTRIBUTE_REQUIRED,
    253                                             true /*hasNamespace*/);
    254                                     if (value != null) {
    255                                         Boolean b = Boolean.valueOf(value);
    256                                         if (b != null) {
    257                                             library.mRequired = b;
    258                                         }
    259                                     }
    260 
    261                                     mManifestData.mLibraries.add(library);
    262                                 }
    263                             }
    264                             break;
    265                         case LEVEL_INSIDE_APP_COMPONENT:
    266                             // only process this level if we are in an activity
    267                             if (mCurrentActivity != null &&
    268                                     AndroidManifest.NODE_INTENT.equals(localName)) {
    269                                 mCurrentActivity.resetIntentFilter();
    270                                 mValidLevel++;
    271                             }
    272                             break;
    273                         case LEVEL_INSIDE_INTENT_FILTER:
    274                             if (mCurrentActivity != null) {
    275                                 if (AndroidManifest.NODE_ACTION.equals(localName)) {
    276                                     // get the name attribute
    277                                     String action = getAttributeValue(attributes,
    278                                             AndroidManifest.ATTRIBUTE_NAME,
    279                                             true /* hasNamespace */);
    280                                     if (action != null) {
    281                                         mCurrentActivity.setHasAction(true);
    282                                         mCurrentActivity.setHasMainAction(
    283                                                 ACTION_MAIN.equals(action));
    284                                     }
    285                                 } else if (AndroidManifest.NODE_CATEGORY.equals(localName)) {
    286                                     String category = getAttributeValue(attributes,
    287                                             AndroidManifest.ATTRIBUTE_NAME,
    288                                             true /* hasNamespace */);
    289                                     if (CATEGORY_LAUNCHER.equals(category)) {
    290                                         mCurrentActivity.setHasLauncherCategory(true);
    291                                     }
    292                                 }
    293 
    294                                 // no need to increase mValidLevel as we don't process anything
    295                                 // below this level.
    296                             }
    297                             break;
    298                     }
    299                 }
    300 
    301                 mCurrentLevel++;
    302             } finally {
    303                 super.startElement(uri, localName, name, attributes);
    304             }
    305         }
    306 
    307         /* (non-Javadoc)
    308          * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
    309          * java.lang.String)
    310          */
    311         @Override
    312         public void endElement(String uri, String localName, String name) throws SAXException {
    313             try {
    314                 if (mManifestData == null) {
    315                     return;
    316                 }
    317 
    318                 // decrement the levels.
    319                 if (mValidLevel == mCurrentLevel) {
    320                     mValidLevel--;
    321                 }
    322                 mCurrentLevel--;
    323 
    324                 // if we're at a valid level
    325                 // process the end of the element
    326                 if (mValidLevel == mCurrentLevel) {
    327                     switch (mValidLevel) {
    328                         case LEVEL_INSIDE_APPLICATION:
    329                             mCurrentActivity = null;
    330                             break;
    331                         case LEVEL_INSIDE_APP_COMPONENT:
    332                             // if we found both a main action and a launcher category, this is our
    333                             // launcher activity!
    334                             if (mManifestData.mLauncherActivity == null &&
    335                                     mCurrentActivity != null &&
    336                                     mCurrentActivity.isHomeActivity() &&
    337                                     mCurrentActivity.isExported()) {
    338                                 mManifestData.mLauncherActivity = mCurrentActivity;
    339                             }
    340                             break;
    341                         default:
    342                             break;
    343                     }
    344 
    345                 }
    346             } finally {
    347                 super.endElement(uri, localName, name);
    348             }
    349         }
    350 
    351         /* (non-Javadoc)
    352          * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
    353          */
    354         @Override
    355         public void error(SAXParseException e) {
    356             if (mErrorHandler != null) {
    357                 mErrorHandler.handleError(e, e.getLineNumber());
    358             }
    359         }
    360 
    361         /* (non-Javadoc)
    362          * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
    363          */
    364         @Override
    365         public void fatalError(SAXParseException e) {
    366             if (mErrorHandler != null) {
    367                 mErrorHandler.handleError(e, e.getLineNumber());
    368             }
    369         }
    370 
    371         /* (non-Javadoc)
    372          * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
    373          */
    374         @Override
    375         public void warning(SAXParseException e) throws SAXException {
    376             if (mErrorHandler != null) {
    377                 mErrorHandler.warning(e);
    378             }
    379         }
    380 
    381         /**
    382          * Processes the activity node.
    383          * @param attributes the attributes for the activity node.
    384          */
    385         private void processActivityNode(Attributes attributes) {
    386             // lets get the activity name, and add it to the list
    387             String activityName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME,
    388                     true /* hasNamespace */);
    389             if (activityName != null) {
    390                 activityName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage,
    391                         activityName);
    392 
    393                 // get the exported flag.
    394                 String exportedStr = getAttributeValue(attributes,
    395                         AndroidManifest.ATTRIBUTE_EXPORTED, true);
    396                 boolean exported = exportedStr == null ||
    397                         exportedStr.toLowerCase(Locale.US).equals("true"); //$NON-NLS-1$
    398                 mCurrentActivity = new Activity(activityName, exported);
    399                 mManifestData.mActivities.add(mCurrentActivity);
    400 
    401                 if (mErrorHandler != null) {
    402                     mErrorHandler.checkClass(mLocator, activityName, SdkConstants.CLASS_ACTIVITY,
    403                             true /* testVisibility */);
    404                 }
    405             } else {
    406                 // no activity found! Aapt will output an error,
    407                 // so we don't have to do anything
    408                 mCurrentActivity = null;
    409             }
    410 
    411             String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS,
    412                     true /* hasNamespace */);
    413             if (processName != null) {
    414                 mManifestData.addProcessName(processName);
    415             }
    416         }
    417 
    418         /**
    419          * Processes the service/receiver/provider nodes.
    420          * @param attributes the attributes for the activity node.
    421          * @param superClassName the fully qualified name of the super class that this
    422          * node is representing
    423          */
    424         private void processNode(Attributes attributes, String superClassName) {
    425             // lets get the class name, and check it if required.
    426             String serviceName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_NAME,
    427                     true /* hasNamespace */);
    428             if (serviceName != null) {
    429                 serviceName = AndroidManifest.combinePackageAndClassName(mManifestData.mPackage,
    430                         serviceName);
    431 
    432                 if (mErrorHandler != null) {
    433                     mErrorHandler.checkClass(mLocator, serviceName, superClassName,
    434                             false /* testVisibility */);
    435                 }
    436             }
    437 
    438             String processName = getAttributeValue(attributes, AndroidManifest.ATTRIBUTE_PROCESS,
    439                     true /* hasNamespace */);
    440             if (processName != null) {
    441                 mManifestData.addProcessName(processName);
    442             }
    443         }
    444 
    445         /**
    446          * Processes the instrumentation node.
    447          * @param attributes the attributes for the instrumentation node.
    448          */
    449         private void processInstrumentationNode(Attributes attributes) {
    450             // lets get the class name, and check it if required.
    451             String instrumentationName = getAttributeValue(attributes,
    452                     AndroidManifest.ATTRIBUTE_NAME,
    453                     true /* hasNamespace */);
    454             if (instrumentationName != null) {
    455                 String instrClassName = AndroidManifest.combinePackageAndClassName(
    456                         mManifestData.mPackage, instrumentationName);
    457                 String targetPackage = getAttributeValue(attributes,
    458                         AndroidManifest.ATTRIBUTE_TARGET_PACKAGE,
    459                         true /* hasNamespace */);
    460                 mManifestData.mInstrumentations.add(
    461                         new Instrumentation(instrClassName, targetPackage));
    462                 if (mErrorHandler != null) {
    463                     mErrorHandler.checkClass(mLocator, instrClassName,
    464                             SdkConstants.CLASS_INSTRUMENTATION, true /* testVisibility */);
    465                 }
    466             }
    467         }
    468 
    469         /**
    470          * Processes the supports-screens node.
    471          * @param attributes the attributes for the supports-screens node.
    472          */
    473         private void processSupportsScreensNode(Attributes attributes) {
    474             mManifestData.mSupportsScreensFromManifest = new SupportsScreens();
    475 
    476             mManifestData.mSupportsScreensFromManifest.setResizeable(getAttributeBooleanValue(
    477                     attributes, AndroidManifest.ATTRIBUTE_RESIZEABLE, true /*hasNamespace*/));
    478 
    479             mManifestData.mSupportsScreensFromManifest.setAnyDensity(getAttributeBooleanValue(
    480                     attributes, AndroidManifest.ATTRIBUTE_ANYDENSITY, true /*hasNamespace*/));
    481 
    482             mManifestData.mSupportsScreensFromManifest.setSmallScreens(getAttributeBooleanValue(
    483                     attributes, AndroidManifest.ATTRIBUTE_SMALLSCREENS, true /*hasNamespace*/));
    484 
    485             mManifestData.mSupportsScreensFromManifest.setNormalScreens(getAttributeBooleanValue(
    486                     attributes, AndroidManifest.ATTRIBUTE_NORMALSCREENS, true /*hasNamespace*/));
    487 
    488             mManifestData.mSupportsScreensFromManifest.setLargeScreens(getAttributeBooleanValue(
    489                     attributes, AndroidManifest.ATTRIBUTE_LARGESCREENS, true /*hasNamespace*/));
    490         }
    491 
    492         /**
    493          * Processes the supports-screens node.
    494          * @param attributes the attributes for the supports-screens node.
    495          */
    496         private void processUsesConfiguration(Attributes attributes) {
    497             mManifestData.mUsesConfiguration = new UsesConfiguration();
    498 
    499             mManifestData.mUsesConfiguration.mReqFiveWayNav = getAttributeBooleanValue(
    500                     attributes,
    501                     AndroidManifest.ATTRIBUTE_REQ_5WAYNAV, true /*hasNamespace*/);
    502             mManifestData.mUsesConfiguration.mReqNavigation = Navigation.getEnum(
    503                     getAttributeValue(attributes,
    504                             AndroidManifest.ATTRIBUTE_REQ_NAVIGATION, true /*hasNamespace*/));
    505             mManifestData.mUsesConfiguration.mReqHardKeyboard = getAttributeBooleanValue(
    506                     attributes,
    507                     AndroidManifest.ATTRIBUTE_REQ_HARDKEYBOARD, true /*hasNamespace*/);
    508             mManifestData.mUsesConfiguration.mReqKeyboardType = Keyboard.getEnum(
    509                     getAttributeValue(attributes,
    510                             AndroidManifest.ATTRIBUTE_REQ_KEYBOARDTYPE, true /*hasNamespace*/));
    511             mManifestData.mUsesConfiguration.mReqTouchScreen = TouchScreen.getEnum(
    512                     getAttributeValue(attributes,
    513                             AndroidManifest.ATTRIBUTE_REQ_TOUCHSCREEN, true /*hasNamespace*/));
    514         }
    515 
    516         /**
    517          * Searches through the attributes list for a particular one and returns its value.
    518          * @param attributes the attribute list to search through
    519          * @param attributeName the name of the attribute to look for.
    520          * @param hasNamespace Indicates whether the attribute has an android namespace.
    521          * @return a String with the value or null if the attribute was not found.
    522          * @see SdkConstants#NS_RESOURCES
    523          */
    524         private String getAttributeValue(Attributes attributes, String attributeName,
    525                 boolean hasNamespace) {
    526             int count = attributes.getLength();
    527             for (int i = 0 ; i < count ; i++) {
    528                 if (attributeName.equals(attributes.getLocalName(i)) &&
    529                         ((hasNamespace &&
    530                                 SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) ||
    531                                 (hasNamespace == false && attributes.getURI(i).length() == 0))) {
    532                     return attributes.getValue(i);
    533                 }
    534             }
    535 
    536             return null;
    537         }
    538 
    539         /**
    540          * Searches through the attributes list for a particular one and returns its value as a
    541          * Boolean. If the attribute is not present, this will return null.
    542          * @param attributes the attribute list to search through
    543          * @param attributeName the name of the attribute to look for.
    544          * @param hasNamespace Indicates whether the attribute has an android namespace.
    545          * @return a String with the value or null if the attribute was not found.
    546          * @see SdkConstants#NS_RESOURCES
    547          */
    548         private Boolean getAttributeBooleanValue(Attributes attributes, String attributeName,
    549                 boolean hasNamespace) {
    550             int count = attributes.getLength();
    551             for (int i = 0 ; i < count ; i++) {
    552                 if (attributeName.equals(attributes.getLocalName(i)) &&
    553                         ((hasNamespace &&
    554                                 SdkConstants.NS_RESOURCES.equals(attributes.getURI(i))) ||
    555                                 (hasNamespace == false && attributes.getURI(i).length() == 0))) {
    556                     String attr = attributes.getValue(i);
    557                     if (attr != null) {
    558                         return Boolean.valueOf(attr);
    559                     } else {
    560                         return null;
    561                     }
    562                 }
    563             }
    564 
    565             return null;
    566         }
    567 
    568     }
    569 
    570     private final static SAXParserFactory sParserFactory;
    571 
    572     static {
    573         sParserFactory = SAXParserFactory.newInstance();
    574         sParserFactory.setNamespaceAware(true);
    575     }
    576 
    577     /**
    578      * Parses the Android Manifest, and returns a {@link ManifestData} object containing the
    579      * result of the parsing.
    580      *
    581      * @param manifestFile the {@link IAbstractFile} representing the manifest file.
    582      * @param gatherData indicates whether the parsing will extract data from the manifest. If false
    583      * the method will always return null.
    584      * @param errorHandler an optional errorHandler.
    585      * @return A class containing the manifest info obtained during the parsing, or null on error.
    586      *
    587      * @throws StreamException
    588      * @throws IOException
    589      * @throws SAXException
    590      * @throws ParserConfigurationException
    591      */
    592     public static ManifestData parse(
    593             IAbstractFile manifestFile,
    594             boolean gatherData,
    595             ManifestErrorHandler errorHandler)
    596                 throws SAXException, IOException, StreamException, ParserConfigurationException {
    597         if (manifestFile != null) {
    598             SAXParser parser = sParserFactory.newSAXParser();
    599 
    600             ManifestData data = null;
    601             if (gatherData) {
    602                 data = new ManifestData();
    603             }
    604 
    605             ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
    606                     data, errorHandler);
    607             parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
    608 
    609             return data;
    610         }
    611 
    612         return null;
    613     }
    614 
    615     /**
    616      * Parses the Android Manifest, and returns an object containing the result of the parsing.
    617      *
    618      * <p/>
    619      * This is the equivalent of calling <pre>parse(manifestFile, true, null)</pre>
    620      *
    621      * @param manifestFile the manifest file to parse.
    622      *
    623      * @throws ParserConfigurationException
    624      * @throws StreamException
    625      * @throws IOException
    626      * @throws SAXException
    627      */
    628     public static ManifestData parse(IAbstractFile manifestFile)
    629             throws SAXException, IOException, StreamException, ParserConfigurationException {
    630         return parse(manifestFile, true, null);
    631     }
    632 
    633     public static ManifestData parse(IAbstractFolder projectFolder)
    634             throws SAXException, IOException, StreamException, ParserConfigurationException {
    635         IAbstractFile manifestFile = AndroidManifest.getManifest(projectFolder);
    636         if (manifestFile == null) {
    637             throw new FileNotFoundException();
    638         }
    639 
    640         return parse(manifestFile, true, null);
    641     }
    642 
    643     /**
    644      * Parses the Android Manifest from an {@link InputStream}, and returns a {@link ManifestData}
    645      * object containing the result of the parsing.
    646      *
    647      * @param manifestFileStream the {@link InputStream} representing the manifest file.
    648      * @return A class containing the manifest info obtained during the parsing or null on error.
    649      *
    650      * @throws StreamException
    651      * @throws IOException
    652      * @throws SAXException
    653      * @throws ParserConfigurationException
    654      */
    655     public static ManifestData parse(InputStream manifestFileStream)
    656             throws SAXException, IOException, StreamException, ParserConfigurationException {
    657         if (manifestFileStream != null) {
    658             SAXParser parser = sParserFactory.newSAXParser();
    659 
    660             ManifestData data = new ManifestData();
    661 
    662             ManifestHandler manifestHandler = new ManifestHandler(null, data, null);
    663             parser.parse(new InputSource(manifestFileStream), manifestHandler);
    664 
    665             return data;
    666         }
    667 
    668         return null;
    669     }
    670 }
    671