Home | History | Annotate | Download | only in manifest
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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.ide.eclipse.adt.internal.editors.manifest;
     18 
     19 import com.android.ide.eclipse.adt.AdtConstants;
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     22 import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
     23 import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors;
     24 import com.android.ide.eclipse.adt.internal.editors.manifest.pages.ApplicationPage;
     25 import com.android.ide.eclipse.adt.internal.editors.manifest.pages.InstrumentationPage;
     26 import com.android.ide.eclipse.adt.internal.editors.manifest.pages.OverviewPage;
     27 import com.android.ide.eclipse.adt.internal.editors.manifest.pages.PermissionPage;
     28 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
     29 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     30 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
     31 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
     32 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     33 import com.android.sdklib.xml.AndroidXPathFactory;
     34 
     35 import org.eclipse.core.resources.IFile;
     36 import org.eclipse.core.resources.IMarker;
     37 import org.eclipse.core.resources.IMarkerDelta;
     38 import org.eclipse.core.resources.IResource;
     39 import org.eclipse.core.resources.IResourceDelta;
     40 import org.eclipse.core.runtime.CoreException;
     41 import org.eclipse.ui.IEditorInput;
     42 import org.eclipse.ui.IEditorPart;
     43 import org.eclipse.ui.PartInitException;
     44 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     45 import org.w3c.dom.Document;
     46 import org.w3c.dom.Node;
     47 
     48 import java.util.List;
     49 
     50 import javax.xml.xpath.XPath;
     51 import javax.xml.xpath.XPathConstants;
     52 import javax.xml.xpath.XPathExpressionException;
     53 
     54 /**
     55  * Multi-page form editor for AndroidManifest.xml.
     56  */
     57 @SuppressWarnings("restriction")
     58 public final class ManifestEditor extends AndroidXmlEditor {
     59 
     60     public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".manifest.ManifestEditor"; //$NON-NLS-1$
     61 
     62     private final static String EMPTY = ""; //$NON-NLS-1$
     63 
     64     /** Root node of the UI element hierarchy */
     65     private UiElementNode mUiManifestNode;
     66     /** The Application Page tab */
     67     private ApplicationPage mAppPage;
     68     /** The Overview Manifest Page tab */
     69     private OverviewPage mOverviewPage;
     70     /** The Permission Page tab */
     71     private PermissionPage mPermissionPage;
     72     /** The Instrumentation Page tab */
     73     private InstrumentationPage mInstrumentationPage;
     74 
     75     private IFileListener mMarkerMonitor;
     76 
     77 
     78     /**
     79      * Creates the form editor for AndroidManifest.xml.
     80      */
     81     public ManifestEditor() {
     82         super();
     83     }
     84 
     85     @Override
     86     public void dispose() {
     87         super.dispose();
     88 
     89         GlobalProjectMonitor.getMonitor().removeFileListener(mMarkerMonitor);
     90     }
     91 
     92     /**
     93      * Return the root node of the UI element hierarchy, which here
     94      * is the "manifest" node.
     95      */
     96     @Override
     97     public UiElementNode getUiRootNode() {
     98         return mUiManifestNode;
     99     }
    100 
    101     /**
    102      * Returns the Manifest descriptors for the file being edited.
    103      */
    104     public AndroidManifestDescriptors getManifestDescriptors() {
    105         AndroidTargetData data = getTargetData();
    106         if (data != null) {
    107             return data.getManifestDescriptors();
    108         }
    109 
    110         return null;
    111     }
    112 
    113     // ---- Base Class Overrides ----
    114 
    115     /**
    116      * Returns whether the "save as" operation is supported by this editor.
    117      * <p/>
    118      * Save-As is a valid operation for the ManifestEditor since it acts on a
    119      * single source file.
    120      *
    121      * @see IEditorPart
    122      */
    123     @Override
    124     public boolean isSaveAsAllowed() {
    125         return true;
    126     }
    127 
    128     /**
    129      * Creates the various form pages.
    130      */
    131     @Override
    132     protected void createFormPages() {
    133         try {
    134             addPage(mOverviewPage = new OverviewPage(this));
    135             addPage(mAppPage = new ApplicationPage(this));
    136             addPage(mPermissionPage = new PermissionPage(this));
    137             addPage(mInstrumentationPage = new InstrumentationPage(this));
    138         } catch (PartInitException e) {
    139             AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$
    140         }
    141     }
    142 
    143     /* (non-java doc)
    144      * Change the tab/title name to include the project name.
    145      */
    146     @Override
    147     protected void setInput(IEditorInput input) {
    148         super.setInput(input);
    149         IFile inputFile = getInputFile();
    150         if (inputFile != null) {
    151             startMonitoringMarkers();
    152             setPartName(String.format("%1$s Manifest", inputFile.getProject().getName()));
    153         }
    154     }
    155 
    156     /**
    157      * Processes the new XML Model, which XML root node is given.
    158      *
    159      * @param xml_doc The XML document, if available, or null if none exists.
    160      */
    161     @Override
    162     protected void xmlModelChanged(Document xml_doc) {
    163         if (mIgnoreXmlUpdate) {
    164             return;
    165         }
    166 
    167         // create the ui root node on demand.
    168         initUiRootNode(false /*force*/);
    169 
    170         loadFromXml(xml_doc);
    171 
    172         super.xmlModelChanged(xml_doc);
    173     }
    174 
    175     private void loadFromXml(Document xmlDoc) {
    176         mUiManifestNode.setXmlDocument(xmlDoc);
    177         Node node = getManifestXmlNode(xmlDoc);
    178 
    179         if (node != null) {
    180             // Refresh the manifest UI node and all its descendants
    181             mUiManifestNode.loadFromXmlNode(node);
    182         }
    183     }
    184 
    185     private Node getManifestXmlNode(Document xmlDoc) {
    186         if (xmlDoc != null) {
    187             ElementDescriptor manifest_desc = mUiManifestNode.getDescriptor();
    188             try {
    189                 XPath xpath = AndroidXPathFactory.newXPath();
    190                 Node node = (Node) xpath.evaluate("/" + manifest_desc.getXmlName(),  //$NON-NLS-1$
    191                         xmlDoc,
    192                         XPathConstants.NODE);
    193                 assert node != null && node.getNodeName().equals(manifest_desc.getXmlName());
    194 
    195                 return node;
    196             } catch (XPathExpressionException e) {
    197                 AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$
    198                         manifest_desc.getXmlName());
    199             }
    200         }
    201 
    202         return null;
    203     }
    204 
    205     private void onDescriptorsChanged() {
    206         IStructuredModel model = getModelForRead();
    207         if (model != null) {
    208             try {
    209                 Node node = getManifestXmlNode(getXmlDocument(model));
    210                 mUiManifestNode.reloadFromXmlNode(node);
    211             } finally {
    212                 model.releaseFromRead();
    213             }
    214         }
    215 
    216         if (mOverviewPage != null) {
    217             mOverviewPage.refreshUiApplicationNode();
    218         }
    219 
    220         if (mAppPage != null) {
    221             mAppPage.refreshUiApplicationNode();
    222         }
    223 
    224         if (mPermissionPage != null) {
    225             mPermissionPage.refreshUiNode();
    226         }
    227 
    228         if (mInstrumentationPage != null) {
    229             mInstrumentationPage.refreshUiNode();
    230         }
    231     }
    232 
    233     /**
    234      * Reads and processes the current markers and adds a listener for marker changes.
    235      */
    236     private void startMonitoringMarkers() {
    237         final IFile inputFile = getInputFile();
    238         if (inputFile != null) {
    239             updateFromExistingMarkers(inputFile);
    240 
    241             mMarkerMonitor = new IFileListener() {
    242                 public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
    243                     if (file.equals(inputFile)) {
    244                         processMarkerChanges(markerDeltas);
    245                     }
    246                 }
    247             };
    248 
    249             GlobalProjectMonitor.getMonitor().addFileListener(
    250                     mMarkerMonitor, IResourceDelta.CHANGED);
    251         }
    252     }
    253 
    254     /**
    255      * Processes the markers of the specified {@link IFile} and updates the error status of
    256      * {@link UiElementNode}s and {@link UiAttributeNode}s.
    257      * @param inputFile the file being edited.
    258      */
    259     private void updateFromExistingMarkers(IFile inputFile) {
    260         try {
    261             // get the markers for the file
    262             IMarker[] markers = inputFile.findMarkers(
    263                     AdtConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO);
    264 
    265             AndroidManifestDescriptors desc = getManifestDescriptors();
    266             if (desc != null) {
    267                 ElementDescriptor appElement = desc.getApplicationElement();
    268 
    269                 if (appElement != null && mUiManifestNode != null) {
    270                     UiElementNode appUiNode = mUiManifestNode.findUiChildNode(
    271                             appElement.getXmlName());
    272                     List<UiElementNode> children = appUiNode.getUiChildren();
    273 
    274                     for (IMarker marker : markers) {
    275                         processMarker(marker, children, IResourceDelta.ADDED);
    276                     }
    277                 }
    278             }
    279 
    280         } catch (CoreException e) {
    281             // findMarkers can throw an exception, in which case, we'll do nothing.
    282         }
    283     }
    284 
    285     /**
    286      * Processes a {@link IMarker} change.
    287      * @param markerDeltas the list of {@link IMarkerDelta}
    288      */
    289     private void processMarkerChanges(IMarkerDelta[] markerDeltas) {
    290         AndroidManifestDescriptors descriptors = getManifestDescriptors();
    291         if (descriptors != null && descriptors.getApplicationElement() != null) {
    292             UiElementNode app_ui_node = mUiManifestNode.findUiChildNode(
    293                     descriptors.getApplicationElement().getXmlName());
    294             List<UiElementNode> children = app_ui_node.getUiChildren();
    295 
    296             for (IMarkerDelta markerDelta : markerDeltas) {
    297                 processMarker(markerDelta.getMarker(), children, markerDelta.getKind());
    298             }
    299         }
    300     }
    301 
    302     /**
    303      * Processes a new/old/updated marker.
    304      * @param marker The marker being added/removed/changed
    305      * @param nodeList the list of activity/service/provider/receiver nodes.
    306      * @param kind the change kind. Can be {@link IResourceDelta#ADDED},
    307      * {@link IResourceDelta#REMOVED}, or {@link IResourceDelta#CHANGED}
    308      */
    309     private void processMarker(IMarker marker, List<UiElementNode> nodeList, int kind) {
    310         // get the data from the marker
    311         String nodeType = marker.getAttribute(AdtConstants.MARKER_ATTR_TYPE, EMPTY);
    312         if (nodeType == EMPTY) {
    313             return;
    314         }
    315 
    316         String className = marker.getAttribute(AdtConstants.MARKER_ATTR_CLASS, EMPTY);
    317         if (className == EMPTY) {
    318             return;
    319         }
    320 
    321         for (UiElementNode ui_node : nodeList) {
    322             if (ui_node.getDescriptor().getXmlName().equals(nodeType)) {
    323                 for (UiAttributeNode attr : ui_node.getAllUiAttributes()) {
    324                     if (attr.getDescriptor().getXmlLocalName().equals(
    325                             AndroidManifestDescriptors.ANDROID_NAME_ATTR)) {
    326                         if (attr.getCurrentValue().equals(className)) {
    327                             if (kind == IResourceDelta.REMOVED) {
    328                                 attr.setHasError(false);
    329                             } else {
    330                                 attr.setHasError(true);
    331                             }
    332                             return;
    333                         }
    334                     }
    335                 }
    336             }
    337         }
    338     }
    339 
    340     /**
    341      * Creates the initial UI Root Node, including the known mandatory elements.
    342      * @param force if true, a new UiManifestNode is recreated even if it already exists.
    343      */
    344     @Override
    345     protected void initUiRootNode(boolean force) {
    346         // The manifest UI node is always created, even if there's no corresponding XML node.
    347         if (mUiManifestNode != null && force == false) {
    348             return;
    349         }
    350 
    351         AndroidManifestDescriptors manifestDescriptor = getManifestDescriptors();
    352 
    353         if (manifestDescriptor != null) {
    354             ElementDescriptor manifestElement = manifestDescriptor.getManifestElement();
    355             mUiManifestNode = manifestElement.createUiNode();
    356             mUiManifestNode.setEditor(this);
    357 
    358             // Similarly, always create the /manifest/uses-sdk followed by /manifest/application
    359             // (order of the elements now matters)
    360             ElementDescriptor element = manifestDescriptor.getUsesSdkElement();
    361             boolean present = false;
    362             for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) {
    363                 if (ui_node.getDescriptor() == element) {
    364                     present = true;
    365                     break;
    366                 }
    367             }
    368             if (!present) {
    369                 mUiManifestNode.appendNewUiChild(element);
    370             }
    371 
    372             element = manifestDescriptor.getApplicationElement();
    373             present = false;
    374             for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) {
    375                 if (ui_node.getDescriptor() == element) {
    376                     present = true;
    377                     break;
    378                 }
    379             }
    380             if (!present) {
    381                 mUiManifestNode.appendNewUiChild(element);
    382             }
    383 
    384             onDescriptorsChanged();
    385         } else {
    386             // create a dummy descriptor/uinode until we have real descriptors
    387             ElementDescriptor desc = new ElementDescriptor("manifest", //$NON-NLS-1$
    388                     "temporary descriptors due to missing decriptors", //$NON-NLS-1$
    389                     null /*tooltip*/, null /*sdk_url*/, null /*attributes*/,
    390                     null /*children*/, false /*mandatory*/);
    391             mUiManifestNode = desc.createUiNode();
    392             mUiManifestNode.setEditor(this);
    393         }
    394     }
    395 }
    396