| /* |
| * 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(); |
| } |
| } |
| } |