Home | History | Annotate | Download | only in manifest
      1 /*
      2 * Copyright (C) 2012 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.motorola.studio.android.model.manifest;
     18 
     19 import java.io.IOException;
     20 import java.io.StringWriter;
     21 import java.util.LinkedList;
     22 import java.util.List;
     23 import java.util.Map;
     24 
     25 import javax.xml.parsers.DocumentBuilder;
     26 import javax.xml.parsers.DocumentBuilderFactory;
     27 import javax.xml.parsers.ParserConfigurationException;
     28 
     29 import org.apache.xml.serialize.OutputFormat;
     30 import org.apache.xml.serialize.XMLSerializer;
     31 import org.eclipse.core.runtime.IStatus;
     32 import org.eclipse.jface.text.IDocument;
     33 import org.w3c.dom.Attr;
     34 import org.w3c.dom.Document;
     35 import org.w3c.dom.NamedNodeMap;
     36 import org.w3c.dom.Node;
     37 
     38 import com.motorola.studio.android.common.exception.AndroidException;
     39 import com.motorola.studio.android.common.log.StudioLogger;
     40 import com.motorola.studio.android.common.utilities.AndroidStatus;
     41 import com.motorola.studio.android.common.utilities.i18n.UtilitiesNLS;
     42 import com.motorola.studio.android.model.manifest.dom.AbstractBuildingBlockNode;
     43 import com.motorola.studio.android.model.manifest.dom.ActionNode;
     44 import com.motorola.studio.android.model.manifest.dom.ActivityNode;
     45 import com.motorola.studio.android.model.manifest.dom.AndroidManifestNode;
     46 import com.motorola.studio.android.model.manifest.dom.AndroidManifestNode.NodeType;
     47 import com.motorola.studio.android.model.manifest.dom.ApplicationNode;
     48 import com.motorola.studio.android.model.manifest.dom.CommentNode;
     49 import com.motorola.studio.android.model.manifest.dom.IntentFilterNode;
     50 import com.motorola.studio.android.model.manifest.dom.ManifestNode;
     51 import com.motorola.studio.android.model.manifest.parser.AndroidManifestParser;
     52 
     53 /**
     54  * Class that represents an AndroidManifest.xml file
     55  */
     56 @SuppressWarnings("deprecation")
     57 public class AndroidManifestFile extends AndroidManifestParser
     58 {
     59     /**
     60      * Adds an AndroidManifestNode to the file
     61      *
     62      * @param node The node to be added
     63      */
     64     public void addNode(AndroidManifestNode node)
     65     {
     66         if ((node != null) && !rootNodes.contains(node))
     67         {
     68             rootNodes.add(node);
     69         }
     70     }
     71 
     72     /**
     73      * Removes an AndroidManifestNode from the file
     74      *
     75      * @param node The node to be removed
     76      */
     77     public void removeNode(AndroidManifestNode node)
     78     {
     79         if ((node != null) && !rootNodes.contains(node))
     80         {
     81             rootNodes.add(node);
     82         }
     83     }
     84 
     85     /**
     86      * Retrieves an array containing all nodes present on xml root.
     87      * If the file is valid, there will be only one node, the <manifest> node
     88      *
     89      * @return an array containing all nodes present on xml root.
     90      */
     91     public AndroidManifestNode[] getNodes()
     92     {
     93         AndroidManifestNode[] nodes = new AndroidManifestNode[rootNodes.size()];
     94         nodes = rootNodes.toArray(nodes);
     95 
     96         return nodes;
     97     }
     98 
     99     /**
    100      * Retrieves the <manifest> node
    101      *
    102      * @return the <manifest> node
    103      */
    104     public ManifestNode getManifestNode()
    105     {
    106         ManifestNode manifestNode = null;
    107 
    108         for (AndroidManifestNode node : rootNodes)
    109         {
    110             if (node.getNodeType() == NodeType.Manifest)
    111             {
    112                 manifestNode = (ManifestNode) node;
    113                 break;
    114             }
    115         }
    116 
    117         return manifestNode;
    118     }
    119 
    120     /**
    121      * Retrieves the <application> node from the manifest file
    122      *
    123      * @return The <application> node of the manifest file.
    124      */
    125     public ApplicationNode getApplicationNode()
    126     {
    127         // Retrieve <manifest> node and return application node
    128         return getManifestNode().getApplicationNode();
    129 
    130     }
    131 
    132     /**
    133      * Retrieves a building block node, which can be of the following types:
    134      * NodeType.Activity
    135      * NodeType.Provider
    136      * NodeType.Receiver
    137      * NodeType.Service
    138      *
    139      * @param type The NodeType.
    140      * @param androidName The android:name property value. Should be the fully qualified name of the building block class.
    141      * For example, for the Activity class "Test" located in the package "com.motorola", the androidName parameter should be "com.motorola.Teste"
    142      *
    143      * @return A AbstractBuildingBlockNode that represents the building block. If no matching node is found or
    144      * the type passed is invalid, null is returned.
    145      */
    146     public AbstractBuildingBlockNode getBuildingBlockNode(NodeType type, String androidName)
    147     {
    148         // Result
    149         AbstractBuildingBlockNode resultNode = null;
    150 
    151         // Candidate list of nodes to iterate through
    152         List<AbstractBuildingBlockNode> candidateList = new LinkedList<AbstractBuildingBlockNode>();
    153 
    154         // Retrieve the Manifest node to check the default package
    155         ManifestNode manifestNode = getManifestNode();
    156         String manifestPackage = manifestNode.getNodeProperties().get(PROP_PACKAGE);
    157 
    158         // Compare the qualified name from the parameter with the manifest package. If equal, we can use the class name for comparison purposes.
    159         String androidNamePackage = androidName.substring(0, androidName.lastIndexOf('.'));
    160         String shortAndroidName = new String();
    161 
    162         if (manifestPackage.equals(androidNamePackage))
    163         {
    164             shortAndroidName = androidName.substring(androidName.lastIndexOf('.'));
    165         }
    166 
    167         // Retrieve the application node
    168         ApplicationNode applicationNode = getApplicationNode();
    169 
    170         // Check the building block type
    171         switch (type)
    172         {
    173             case Activity:
    174                 candidateList.addAll(applicationNode.getActivityNodes());
    175                 break;
    176             case Provider:
    177                 candidateList.addAll(applicationNode.getProviderNodes());
    178                 break;
    179             case Receiver:
    180                 candidateList.addAll(applicationNode.getReceiverNodes());
    181                 break;
    182             case Service:
    183                 candidateList.addAll(applicationNode.getServiceNodes());
    184                 break;
    185             default:
    186                 break;
    187         }
    188 
    189         // Search the candidate list for the target node
    190         for (AbstractBuildingBlockNode node : candidateList)
    191         {
    192             // In the case that shortAndroidName is not null or empty, we check if it's like that in the manifest first
    193             if ((shortAndroidName != null) && (shortAndroidName.length() > 0))
    194             {
    195                 if (node.getNodeProperties().get(PROP_NAME).equals(shortAndroidName))
    196                 {
    197                     resultNode = node;
    198                     break;
    199                 }
    200             }
    201 
    202             if (node.getNodeProperties().get(PROP_NAME).equals(androidName))
    203             {
    204                 // We found the node!
    205                 resultNode = node;
    206                 break;
    207             }
    208         }
    209 
    210         return resultNode;
    211     }
    212 
    213     /**
    214      * Retrieves an IDocument object containing the xml content for the file
    215      *
    216      * @return an IDocument object containing the xml content for the file
    217      */
    218     public IDocument getContent() throws AndroidException
    219     {
    220         IDocument document = null;
    221         DocumentBuilder documentBuilder = null;
    222 
    223         try
    224         {
    225             documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    226         }
    227         catch (ParserConfigurationException e)
    228         {
    229             StudioLogger.error(AndroidManifestFile.class,
    230                     UtilitiesNLS.EXC_AndroidManifestFile_ErrorCreatingTheDocumentBuilder, e);
    231             throw new AndroidException(
    232                     UtilitiesNLS.EXC_AndroidManifestFile_ErrorCreatingTheDocumentBuilder);
    233         }
    234 
    235         Document xmlDocument = documentBuilder.newDocument();
    236 
    237         for (AndroidManifestNode node : rootNodes)
    238         {
    239             addNode(xmlDocument, null, node);
    240         }
    241 
    242         document = new org.eclipse.jface.text.Document(getXmlContent(xmlDocument));
    243 
    244         return document;
    245     }
    246 
    247     /**
    248      * Recursive function to build a XML file from AndroidManifestNode objects
    249      *
    250      * @param xmlDocument The XML Document
    251      * @param xmlParentNode The XML parent node
    252      * @param nodeToAdd The AndroidManifestNode to be added
    253      */
    254     private void addNode(Document xmlDocument, Node xmlParentNode, AndroidManifestNode nodeToAdd)
    255     {
    256         Node xmlNode;
    257 
    258         if (nodeToAdd instanceof CommentNode)
    259         {
    260             CommentNode commentNode = (CommentNode) nodeToAdd;
    261             xmlNode = xmlDocument.createComment(commentNode.getComment());
    262         }
    263         else
    264         {
    265             xmlNode = xmlDocument.createElement(nodeToAdd.getNodeName());
    266             Map<String, String> attributes = nodeToAdd.getNodeProperties();
    267             Map<String, String> unknownAttributes = nodeToAdd.getNodeUnknownProperties();
    268             AndroidManifestNode[] children = nodeToAdd.getChildren();
    269             AndroidManifestNode[] unknown = nodeToAdd.getUnkownChildren();
    270 
    271             // Adds valid attributes
    272             if ((attributes != null) && (attributes.size() > 0))
    273             {
    274                 NamedNodeMap xmlAttributes = xmlNode.getAttributes();
    275 
    276                 for (String attrName : attributes.keySet())
    277                 {
    278                     Attr attr = xmlDocument.createAttribute(attrName);
    279                     attr.setValue(attributes.get(attrName));
    280                     xmlAttributes.setNamedItem(attr);
    281                 }
    282             }
    283 
    284             // Adds invalid attributes
    285             if ((unknownAttributes != null) && (unknownAttributes.size() > 0))
    286             {
    287                 NamedNodeMap xmlAttributes = xmlNode.getAttributes();
    288 
    289                 for (String attrName : unknownAttributes.keySet())
    290                 {
    291                     Attr attr = xmlDocument.createAttribute(attrName);
    292                     attr.setNodeValue(unknownAttributes.get(attrName));
    293                     xmlAttributes.setNamedItem(attr);
    294                 }
    295             }
    296 
    297             // Adds known child nodes
    298             for (AndroidManifestNode child : children)
    299             {
    300                 addNode(xmlDocument, xmlNode, child);
    301             }
    302 
    303             // Adds unknown child nodes
    304             for (AndroidManifestNode child : unknown)
    305             {
    306                 addNode(xmlDocument, xmlNode, child);
    307             }
    308         }
    309 
    310         if (xmlParentNode == null)
    311         {
    312             xmlDocument.appendChild(xmlNode);
    313         }
    314         else
    315         {
    316             xmlParentNode.appendChild(xmlNode);
    317         }
    318     }
    319 
    320     /**
    321      * Creates the XML content from a XML Document
    322      *
    323      * @param xmlDocument The XML Document
    324      * @return a String object containing the XML content
    325      */
    326     private String getXmlContent(Document xmlDocument) throws AndroidException
    327     {
    328         // Despite Xerces is deprecated, its formatted xml source output works
    329         // better than W3C xml output classes
    330         OutputFormat outputFormat = new OutputFormat();
    331         XMLSerializer xmlSerializer = new XMLSerializer();
    332         StringWriter writer = new StringWriter();
    333         String content = null;
    334 
    335         outputFormat.setEncoding("UTF-8");
    336         outputFormat.setLineSeparator(System.getProperty("line.separator"));
    337         outputFormat.setIndenting(true);
    338 
    339         xmlSerializer.setOutputCharStream(writer);
    340         xmlSerializer.setOutputFormat(outputFormat);
    341 
    342         try
    343         {
    344             xmlSerializer.serialize(xmlDocument);
    345             content = writer.toString();
    346         }
    347         catch (IOException e)
    348         {
    349             StudioLogger.error(AndroidManifestFile.class,
    350                     UtilitiesNLS.EXC_AndroidManifestFile_ErrorFormattingTheXMLOutput, e);
    351             throw new AndroidException(
    352                     UtilitiesNLS.EXC_AndroidManifestFile_ErrorFormattingTheXMLOutput);
    353         }
    354         finally
    355         {
    356             if (writer != null)
    357             {
    358                 try
    359                 {
    360                     writer.close();
    361                 }
    362                 catch (IOException e)
    363                 {
    364                     //Do nothing.
    365                 }
    366             }
    367         }
    368 
    369         return content;
    370     }
    371 
    372     /**
    373      * Gets all file problems: Errors and Warnings
    374      *
    375      * @return all file problems
    376      */
    377     public IStatus[] getProblems()
    378     {
    379         ManifestNode manifestNode = getManifestNode();
    380         IStatus[] errors;
    381 
    382         if (manifestNode == null)
    383         {
    384             errors =
    385                     new IStatus[]
    386                     {
    387                         new AndroidStatus(
    388                                 IStatus.ERROR,
    389                                 UtilitiesNLS.ERR_AndroidManifestFile_TheFileAndroidManifestXmlIsMalFormed)
    390                     };
    391         }
    392         else
    393         {
    394             errors = getManifestNode().getRecursiveNodeErrors();
    395         }
    396 
    397         return errors;
    398     }
    399 
    400     /**
    401      * Gets all file errors
    402      *
    403      * @return all file errors
    404      */
    405     public IStatus[] getErrors()
    406     {
    407         List<IStatus> errors = new LinkedList<IStatus>();
    408 
    409         for (IStatus status : getProblems())
    410         {
    411             if (status.getSeverity() == IStatus.ERROR)
    412             {
    413                 errors.add(status);
    414             }
    415         }
    416 
    417         return errors.toArray(new IStatus[0]);
    418     }
    419 
    420     /**
    421      * Checks if the file has errors in the model
    422      *
    423      * @return true if the file has errors in the model and false otherwise
    424      */
    425     public boolean hasErrors()
    426     {
    427         boolean hasErrors = false;
    428 
    429         for (IStatus status : getProblems())
    430         {
    431             if (status.getSeverity() == IStatus.ERROR)
    432             {
    433                 hasErrors = true;
    434                 break;
    435             }
    436         }
    437 
    438         return hasErrors;
    439     }
    440 
    441     public AndroidManifestNode getNode(NodeType nodeType)
    442     {
    443         AndroidManifestNode requiredNode = null;
    444         AndroidManifestNode[] manifestChildren = null;
    445         for (AndroidManifestNode node : rootNodes)
    446         {
    447             if (node instanceof ManifestNode)
    448             {
    449                 manifestChildren = ((ManifestNode) node).getChildren();
    450                 break;
    451             }
    452         }
    453 
    454         if ((manifestChildren != null) && (manifestChildren.length > 0))
    455         {
    456             for (AndroidManifestNode manifestChild : manifestChildren)
    457             {
    458                 if (manifestChild.getNodeType().equals(nodeType))
    459                 {
    460                     requiredNode = manifestChild;
    461                     break;
    462                 }
    463             }
    464         }
    465         return requiredNode;
    466     }
    467 
    468     /**
    469      * This method sets the main activity of and android project be the class identified by {@code className}.
    470      *
    471      * @param className The name of the class to be set as the main activity.
    472      * @param isMainActivity If true, the activity will be set as main activity. If false, the activity will no longer be a main activity.
    473      * @return True if the activity exist, is declared on the manifest and was successfully set as the main activity. False otherwise.
    474      * */
    475     public boolean setAsMainActivity(String className, boolean isMainActivity)
    476     {
    477         boolean result = false;
    478 
    479         List<ActivityNode> activityNodes = getApplicationNode().getActivityNodes();
    480 
    481         for (ActivityNode activityNode : activityNodes)
    482         {
    483             if (activityNode.getName().equals(className))
    484             {
    485                 result = activityNode.setAsMainActivity(isMainActivity);
    486                 break;
    487             }
    488         }
    489 
    490         return result;
    491     }
    492 
    493     /**
    494      * Convenience method that returns the main activity of the application.
    495      * The main activity is the one declared with the intent filter
    496      *    <action android:name="android.intent.action.MAIN"/>
    497      * If more than one main activity is declared, then the first one declared is returned.
    498      * This behavior follows the android behavior to choose the main activity.
    499      * */
    500     public ActivityNode getMainActivity()
    501     {
    502         ActivityNode mainActivity = null;
    503         ApplicationNode appNode = getApplicationNode();
    504         List<ActivityNode> activities = appNode.getActivityNodes();
    505 
    506         for (ActivityNode activity : activities)
    507         {
    508             for (IntentFilterNode intent : activity.getIntentFilterNodes())
    509             {
    510                 for (ActionNode actionNode : intent.getActionNodes())
    511                 {
    512                     if (actionNode.getNodeProperties().get("android:name")
    513                             .equals("android.intent.action.MAIN"))
    514                     {
    515                         mainActivity = activity;
    516                         break;
    517                     }
    518                 }
    519                 if (mainActivity != null)
    520                 {
    521                     break;
    522                 }
    523             }
    524             if (mainActivity != null)
    525             {
    526                 break;
    527             }
    528         }
    529 
    530         return mainActivity;
    531     }
    532 
    533 }
    534