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