aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java431
1 files changed, 431 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
new file mode 100644
index 000000000..3db380832
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2011 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.adt.internal.build;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.XMLNS_ANDROID;
+import static com.android.SdkConstants.XMLNS_URI;
+
+import com.android.ide.common.resources.ResourceUrl;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.resources.ResourceType;
+import com.android.utils.Pair;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.IMarkerResolution;
+import org.eclipse.ui.IMarkerResolution2;
+import org.eclipse.ui.IMarkerResolutionGenerator2;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+/**
+ * Shared handler for both quick assist processors (Control key handler) and quick fix
+ * marker resolution (Problem view handling), since there is a lot of overlap between
+ * these two UI handlers.
+ */
+@SuppressWarnings("restriction") // XML model
+public class AaptQuickFix implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
+
+ public AaptQuickFix() {
+ }
+
+ /** Returns the error message from aapt that signals missing resources */
+ private static String getTargetMarkerErrorMessage() {
+ return "No resource found that matches the given name";
+ }
+
+ /** Returns the error message from aapt that signals a missing namespace declaration */
+ private static String getUnboundErrorMessage() {
+ return "Error parsing XML: unbound prefix";
+ }
+
+ // ---- Implements IMarkerResolution2 ----
+
+ @Override
+ public boolean hasResolutions(IMarker marker) {
+ String message = null;
+ try {
+ message = (String) marker.getAttribute(IMarker.MESSAGE);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return message != null
+ && (message.contains(getTargetMarkerErrorMessage())
+ || message.contains(getUnboundErrorMessage()));
+ }
+
+ @Override
+ public IMarkerResolution[] getResolutions(IMarker marker) {
+ IResource markerResource = marker.getResource();
+ IProject project = markerResource.getProject();
+ try {
+ String message = (String) marker.getAttribute(IMarker.MESSAGE);
+ if (message.contains(getUnboundErrorMessage()) && markerResource instanceof IFile) {
+ return new IMarkerResolution[] {
+ new CreateNamespaceFix((IFile) markerResource)
+ };
+ }
+ } catch (CoreException e1) {
+ AdtPlugin.log(e1, null);
+ }
+
+ int start = marker.getAttribute(IMarker.CHAR_START, 0);
+ int end = marker.getAttribute(IMarker.CHAR_END, 0);
+ if (end > start) {
+ int length = end - start;
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ try {
+ provider.connect(markerResource);
+ IDocument document = provider.getDocument(markerResource);
+ String resource = document.get(start, length);
+ if (ResourceHelper.canCreateResource(resource)) {
+ return new IMarkerResolution[] {
+ new CreateResourceProposal(project, resource)
+ };
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't find range information for %1$s", markerResource);
+ } finally {
+ provider.disconnect(markerResource);
+ }
+ }
+
+ return null;
+ }
+
+ // ---- Implements IQuickAssistProcessor ----
+
+ @Override
+ public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+ return true;
+ }
+
+ @Override
+ public boolean canFix(Annotation annotation) {
+ return true;
+ }
+
+ @Override
+ public ICompletionProposal[] computeQuickAssistProposals(
+ IQuickAssistInvocationContext invocationContext) {
+
+ // We have to find the corresponding project/file (so we can look up the aapt
+ // error markers). Unfortunately, an IQuickAssistProcessor only gets
+ // access to an ISourceViewer which has no hooks back to the surrounding
+ // editor.
+ //
+ // However, the IQuickAssistProcessor will only be used interactively by a file
+ // being edited, so we can cheat like the hyperlink detector and simply
+ // look up the currently active file in the IDE. To be on the safe side,
+ // we'll make sure that that editor has the same sourceViewer such that
+ // we are indeed looking at the right file:
+ ISourceViewer sourceViewer = invocationContext.getSourceViewer();
+ AndroidXmlEditor editor = AndroidXmlEditor.fromTextViewer(sourceViewer);
+ if (editor != null) {
+ IFile file = editor.getInputFile();
+ if (file == null) {
+ return null;
+ }
+ IDocument document = sourceViewer.getDocument();
+ List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_AAPT_COMPILE,
+ file, document, invocationContext.getOffset());
+ try {
+ for (IMarker marker : markers) {
+ String message = marker.getAttribute(IMarker.MESSAGE, ""); //$NON-NLS-1$
+ if (message.contains(getTargetMarkerErrorMessage())) {
+ int start = marker.getAttribute(IMarker.CHAR_START, 0);
+ int end = marker.getAttribute(IMarker.CHAR_END, 0);
+ int length = end - start;
+ String resource = document.get(start, length);
+ // Can only offer create value for non-framework value
+ // resources
+ if (ResourceHelper.canCreateResource(resource)) {
+ IProject project = editor.getProject();
+ return new ICompletionProposal[] {
+ new CreateResourceProposal(project, resource)
+ };
+ }
+ } else if (message.contains(getUnboundErrorMessage())) {
+ return new ICompletionProposal[] {
+ new CreateNamespaceFix(null)
+ };
+ }
+ }
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getErrorMessage() {
+ return null;
+ }
+
+ /** Quick fix to insert namespace binding when missing */
+ private final static class CreateNamespaceFix
+ implements ICompletionProposal, IMarkerResolution2 {
+ private IFile mFile;
+
+ public CreateNamespaceFix(IFile file) {
+ mFile = file;
+ }
+
+ private IndexedRegion perform(IDocument doc) {
+ IModelManager manager = StructuredModelManager.getModelManager();
+ IStructuredModel model = manager.getExistingModelForEdit(doc);
+ if (model != null) {
+ try {
+ perform(model);
+ } finally {
+ model.releaseFromEdit();
+ }
+ }
+
+ return null;
+ }
+
+ private IndexedRegion perform(IFile file) {
+ IModelManager manager = StructuredModelManager.getModelManager();
+ IStructuredModel model;
+ try {
+ model = manager.getModelForEdit(file);
+ if (model != null) {
+ try {
+ perform(model);
+ } finally {
+ model.releaseFromEdit();
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't look up XML model");
+ }
+
+ return null;
+ }
+
+ private IndexedRegion perform(IStructuredModel model) {
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ Document document = domModel.getDocument();
+ Element element = document.getDocumentElement();
+ Attr attr = document.createAttributeNS(XMLNS_URI, XMLNS_ANDROID);
+ attr.setValue(ANDROID_URI);
+ element.getAttributes().setNamedItemNS(attr);
+ return (IndexedRegion) attr;
+ }
+
+ return null;
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ @Override
+ public void apply(IDocument document) {
+ perform(document);
+ }
+
+ @Override
+ public String getAdditionalProposalInfo() {
+ return "Adds an Android namespace declaratiopn to the root element.";
+ }
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+
+ @Override
+ public String getDisplayString() {
+ return "Insert namespace binding";
+ }
+
+ @Override
+ public Image getImage() {
+ return AdtPlugin.getAndroidLogo();
+ }
+
+ @Override
+ public Point getSelection(IDocument doc) {
+ return null;
+ }
+
+
+ // ---- Implements MarkerResolution2 ----
+
+ @Override
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ @Override
+ public void run(IMarker marker) {
+ try {
+ AdtPlugin.openFile(mFile, null);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "Can't open file %1$s", mFile.getName());
+ }
+
+ IndexedRegion indexedRegion = perform(mFile);
+ if (indexedRegion != null) {
+ try {
+ IRegion region =
+ new Region(indexedRegion.getStartOffset(), indexedRegion.getLength());
+ AdtPlugin.openFile(mFile, region);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "Can't open file %1$s", mFile.getName());
+ }
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+ }
+
+ private static class CreateResourceProposal
+ implements ICompletionProposal, IMarkerResolution2 {
+ private final IProject mProject;
+ private final String mResource;
+
+ CreateResourceProposal(IProject project, String resource) {
+ super();
+ mProject = project;
+ mResource = resource;
+ }
+
+ private void perform() {
+ ResourceUrl resource = ResourceUrl.parse(mResource);
+ if (resource == null) {
+ return;
+ }
+ ResourceType type = resource.type;
+ String name = resource.name;
+ assert !resource.framework;
+ String value = ""; //$NON-NLS-1$
+
+ // Try to pick a reasonable first guess. The new value will be highlighted and
+ // selected for editing, but if we have an initial value then the new file
+ // won't show an error.
+ switch (type) {
+ case STRING: value = "TODO"; break; //$NON-NLS-1$
+ case DIMEN: value = "1dp"; break; //$NON-NLS-1$
+ case BOOL: value = "true"; break; //$NON-NLS-1$
+ case COLOR: value = "#000000"; break; //$NON-NLS-1$
+ case INTEGER: value = "1"; break; //$NON-NLS-1$
+ case ARRAY: value = "<item>1</item>"; break; //$NON-NLS-1$
+ }
+
+ Pair<IFile, IRegion> location =
+ ResourceHelper.createResource(mProject, type, name, value);
+ if (location != null) {
+ IFile file = location.getFirst();
+ IRegion region = location.getSecond();
+ try {
+ AdtPlugin.openFile(file, region);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "Can't open file %1$s", file.getName());
+ }
+ }
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ @Override
+ public void apply(IDocument document) {
+ perform();
+ }
+
+ @Override
+ public String getAdditionalProposalInfo() {
+ return "Creates an XML file entry for the given missing resource "
+ + "and opens it in the editor.";
+ }
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+
+ @Override
+ public String getDisplayString() {
+ return String.format("Create resource %1$s", mResource);
+ }
+
+ @Override
+ public Image getImage() {
+ return AdtPlugin.getAndroidLogo();
+ }
+
+ @Override
+ public Point getSelection(IDocument document) {
+ return null;
+ }
+
+ // ---- Implements MarkerResolution2 ----
+
+ @Override
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ @Override
+ public void run(IMarker marker) {
+ perform();
+ }
+
+ @Override
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+ }
+}