diff options
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.java | 431 |
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(); + } + } +} |