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