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