diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java new file mode 100644 index 0000000..d0f8d7b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.editors.manifest; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidXPathFactory; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.manifest.pages.ApplicationPage; +import com.android.ide.eclipse.editors.manifest.pages.InstrumentationPage; +import com.android.ide.eclipse.editors.manifest.pages.OverviewPage; +import com.android.ide.eclipse.editors.manifest.pages.PermissionPage; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.FileEditorInput; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import java.util.List; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; + +/** + * Multi-page form editor for AndroidManifest.xml. + */ +public final class ManifestEditor extends AndroidEditor { + private final static String EMPTY = ""; //$NON-NLS-1$ + + + /** Root node of the UI element hierarchy */ + private UiElementNode mUiManifestNode; + /** The Application Page tab */ + private ApplicationPage mAppPage; + /** The Overview Manifest Page tab */ + private OverviewPage mOverviewPage; + /** The Permission Page tab */ + private PermissionPage mPermissionPage; + /** The Instrumentation Page tab */ + private InstrumentationPage mInstrumentationPage; + + private IFileListener mMarkerMonitor; + + + /** + * Creates the form editor for AndroidManifest.xml. + */ + public ManifestEditor() { + super(); + } + + @Override + public void dispose() { + super.dispose(); + + ResourceMonitor.getMonitor().removeFileListener(mMarkerMonitor); + } + + /** + * Return the root node of the UI element hierarchy, which here + * is the "manifest" node. + */ + @Override + public UiElementNode getUiRootNode() { + return mUiManifestNode; + } + + /** + * Returns the Manifest descriptors for the file being edited. + */ + public AndroidManifestDescriptors getManifestDescriptors() { + AndroidTargetData data = getTargetData(); + if (data != null) { + return data.getManifestDescriptors(); + } + + return null; + } + + // ---- Base Class Overrides ---- + + /** + * Returns whether the "save as" operation is supported by this editor. + * <p/> + * Save-As is a valid operation for the ManifestEditor since it acts on a + * single source file. + * + * @see IEditorPart + */ + @Override + public boolean isSaveAsAllowed() { + return true; + } + + /** + * Creates the various form pages. + */ + @Override + protected void createFormPages() { + try { + addPage(mOverviewPage = new OverviewPage(this)); + addPage(mAppPage = new ApplicationPage(this)); + addPage(mPermissionPage = new PermissionPage(this)); + addPage(mInstrumentationPage = new InstrumentationPage(this)); + } catch (PartInitException e) { + AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ + } + } + + /* (non-java doc) + * Change the tab/title name to include the project name. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + IFile inputFile = getInputFile(); + if (inputFile != null) { + startMonitoringMarkers(); + setPartName(String.format("%1$s Manifest", inputFile.getProject().getName())); + } + } + + /** + * Processes the new XML Model, which XML root node is given. + * + * @param xml_doc The XML document, if available, or null if none exists. + */ + @Override + protected void xmlModelChanged(Document xml_doc) { + // create the ui root node on demand. + initUiRootNode(false /*force*/); + + loadFromXml(xml_doc); + + super.xmlModelChanged(xml_doc); + } + + private void loadFromXml(Document xmlDoc) { + mUiManifestNode.setXmlDocument(xmlDoc); + if (xmlDoc != null) { + ElementDescriptor manifest_desc = mUiManifestNode.getDescriptor(); + try { + XPath xpath = AndroidXPathFactory.newXPath(); + Node node = (Node) xpath.evaluate("/" + manifest_desc.getXmlName(), //$NON-NLS-1$ + xmlDoc, + XPathConstants.NODE); + assert node != null && node.getNodeName().equals(manifest_desc.getXmlName()); + + // Refresh the manifest UI node and all its descendants + mUiManifestNode.loadFromXmlNode(node); + } catch (XPathExpressionException e) { + AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ + manifest_desc.getXmlName()); + } + } + } + + private void onDescriptorsChanged(UiElementNode oldManifestNode) { + mUiManifestNode.reloadFromXmlNode(oldManifestNode.getXmlNode()); + + if (mOverviewPage != null) { + mOverviewPage.refreshUiApplicationNode(); + } + + if (mAppPage != null) { + mAppPage.refreshUiApplicationNode(); + } + + if (mPermissionPage != null) { + mPermissionPage.refreshUiNode(); + } + + if (mInstrumentationPage != null) { + mInstrumentationPage.refreshUiNode(); + } + } + + /** + * Reads and processes the current markers and adds a listener for marker changes. + */ + private void startMonitoringMarkers() { + final IFile inputFile = getInputFile(); + if (inputFile != null) { + updateFromExistingMarkers(inputFile); + + mMarkerMonitor = new IFileListener() { + public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { + if (file.equals(inputFile)) { + processMarkerChanges(markerDeltas); + } + } + }; + + ResourceMonitor.getMonitor().addFileListener(mMarkerMonitor, IResourceDelta.CHANGED); + } + } + + /** + * Processes the markers of the specified {@link IFile} and updates the error status of + * {@link UiElementNode}s and {@link UiAttributeNode}s. + * @param inputFile the file being edited. + */ + private void updateFromExistingMarkers(IFile inputFile) { + try { + // get the markers for the file + IMarker[] markers = inputFile.findMarkers(AndroidConstants.MARKER_ANDROID, true, + IResource.DEPTH_ZERO); + + AndroidManifestDescriptors desc = getManifestDescriptors(); + if (desc != null) { + ElementDescriptor appElement = desc.getApplicationElement(); + + if (appElement != null) { + UiElementNode app_ui_node = mUiManifestNode.findUiChildNode( + appElement.getXmlName()); + List<UiElementNode> children = app_ui_node.getUiChildren(); + + for (IMarker marker : markers) { + processMarker(marker, children, IResourceDelta.ADDED); + } + } + } + + } catch (CoreException e) { + // findMarkers can throw an exception, in which case, we'll do nothing. + } + } + + /** + * Processes a {@link IMarker} change. + * @param markerDeltas the list of {@link IMarkerDelta} + */ + private void processMarkerChanges(IMarkerDelta[] markerDeltas) { + AndroidManifestDescriptors descriptors = getManifestDescriptors(); + if (descriptors != null && descriptors.getApplicationElement() != null) { + UiElementNode app_ui_node = mUiManifestNode.findUiChildNode( + descriptors.getApplicationElement().getXmlName()); + List<UiElementNode> children = app_ui_node.getUiChildren(); + + for (IMarkerDelta markerDelta : markerDeltas) { + processMarker(markerDelta.getMarker(), children, markerDelta.getKind()); + } + } + } + + /** + * Processes a new/old/updated marker. + * @param marker The marker being added/removed/changed + * @param nodeList the list of activity/service/provider/receiver nodes. + * @param kind the change kind. Can be {@link IResourceDelta#ADDED}, + * {@link IResourceDelta#REMOVED}, or {@link IResourceDelta#CHANGED} + */ + private void processMarker(IMarker marker, List<UiElementNode> nodeList, int kind) { + // get the data from the marker + String nodeType = marker.getAttribute(AndroidConstants.MARKER_ATTR_TYPE, EMPTY); + if (nodeType == EMPTY) { + return; + } + + String className = marker.getAttribute(AndroidConstants.MARKER_ATTR_CLASS, EMPTY); + if (className == EMPTY) { + return; + } + + for (UiElementNode ui_node : nodeList) { + if (ui_node.getDescriptor().getXmlName().equals(nodeType)) { + for (UiAttributeNode attr : ui_node.getUiAttributes()) { + if (attr.getDescriptor().getXmlLocalName().equals( + AndroidManifestDescriptors.ANDROID_NAME_ATTR)) { + if (attr.getCurrentValue().equals(className)) { + if (kind == IResourceDelta.REMOVED) { + attr.setHasError(false); + } else { + attr.setHasError(true); + } + return; + } + } + } + } + } + } + + /** + * Creates the initial UI Root Node, including the known mandatory elements. + * @param force if true, a new UiManifestNode is recreated even if it already exists. + */ + @Override + protected void initUiRootNode(boolean force) { + // The manifest UI node is always created, even if there's no corresponding XML node. + if (mUiManifestNode != null && force == false) { + return; + } + + + AndroidManifestDescriptors manifestDescriptor = getManifestDescriptors(); + + if (manifestDescriptor != null) { + // save the old manifest node if it exists + UiElementNode oldManifestNode = mUiManifestNode; + + ElementDescriptor manifestElement = manifestDescriptor.getManifestElement(); + mUiManifestNode = manifestElement.createUiNode(); + mUiManifestNode.setEditor(this); + + // Similarly, always create the /manifest/application and /manifest/uses-sdk nodes + ElementDescriptor appElement = manifestDescriptor.getApplicationElement(); + boolean present = false; + for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { + if (ui_node.getDescriptor() == appElement) { + present = true; + break; + } + } + if (!present) { + mUiManifestNode.appendNewUiChild(appElement); + } + + appElement = manifestDescriptor.getUsesSdkElement(); + present = false; + for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { + if (ui_node.getDescriptor() == appElement) { + present = true; + break; + } + } + if (!present) { + mUiManifestNode.appendNewUiChild(appElement); + } + + if (oldManifestNode != null) { + onDescriptorsChanged(oldManifestNode); + } + } else { + // create a dummy descriptor/uinode until we have real descriptors + ElementDescriptor desc = new ElementDescriptor("manifest", //$NON-NLS-1$ + "temporary descriptors due to missing decriptors", //$NON-NLS-1$ + null /*tooltip*/, null /*sdk_url*/, null /*attributes*/, + null /*children*/, false /*mandatory*/); + mUiManifestNode = desc.createUiNode(); + mUiManifestNode.setEditor(this); + } + } + + /** + * Returns the {@link IFile} being edited, or <code>null</code> if it couldn't be computed. + */ + private IFile getInputFile() { + IEditorInput input = getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + return fileInput.getFile(); + } + + return null; + } +} |