Home | History | Annotate | Download | only in build
      1 /*
      2  * Copyright (C) 2011 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.build;
     18 
     19 import static com.android.SdkConstants.ANDROID_URI;
     20 import static com.android.SdkConstants.XMLNS_ANDROID;
     21 import static com.android.SdkConstants.XMLNS_URI;
     22 
     23 import com.android.ide.common.resources.ResourceUrl;
     24 import com.android.ide.eclipse.adt.AdtConstants;
     25 import com.android.ide.eclipse.adt.AdtPlugin;
     26 import com.android.ide.eclipse.adt.AdtUtils;
     27 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     28 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
     29 import com.android.resources.ResourceType;
     30 import com.android.utils.Pair;
     31 
     32 import org.eclipse.core.resources.IFile;
     33 import org.eclipse.core.resources.IMarker;
     34 import org.eclipse.core.resources.IProject;
     35 import org.eclipse.core.resources.IResource;
     36 import org.eclipse.core.runtime.CoreException;
     37 import org.eclipse.jface.text.BadLocationException;
     38 import org.eclipse.jface.text.IDocument;
     39 import org.eclipse.jface.text.IRegion;
     40 import org.eclipse.jface.text.Region;
     41 import org.eclipse.jface.text.contentassist.ICompletionProposal;
     42 import org.eclipse.jface.text.contentassist.IContextInformation;
     43 import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
     44 import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
     45 import org.eclipse.jface.text.source.Annotation;
     46 import org.eclipse.jface.text.source.ISourceViewer;
     47 import org.eclipse.swt.graphics.Image;
     48 import org.eclipse.swt.graphics.Point;
     49 import org.eclipse.ui.IMarkerResolution;
     50 import org.eclipse.ui.IMarkerResolution2;
     51 import org.eclipse.ui.IMarkerResolutionGenerator2;
     52 import org.eclipse.ui.PartInitException;
     53 import org.eclipse.ui.editors.text.TextFileDocumentProvider;
     54 import org.eclipse.ui.texteditor.IDocumentProvider;
     55 import org.eclipse.wst.sse.core.StructuredModelManager;
     56 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
     57 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     58 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
     59 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
     60 import org.w3c.dom.Attr;
     61 import org.w3c.dom.Document;
     62 import org.w3c.dom.Element;
     63 
     64 import java.util.List;
     65 
     66 /**
     67  * Shared handler for both quick assist processors (Control key handler) and quick fix
     68  * marker resolution (Problem view handling), since there is a lot of overlap between
     69  * these two UI handlers.
     70  */
     71 @SuppressWarnings("restriction") // XML model
     72 public class AaptQuickFix implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
     73 
     74     public AaptQuickFix() {
     75     }
     76 
     77     /** Returns the error message from aapt that signals missing resources */
     78     private static String getTargetMarkerErrorMessage() {
     79         return "No resource found that matches the given name";
     80     }
     81 
     82     /** Returns the error message from aapt that signals a missing namespace declaration */
     83     private static String getUnboundErrorMessage() {
     84         return "Error parsing XML: unbound prefix";
     85     }
     86 
     87     // ---- Implements IMarkerResolution2 ----
     88 
     89     @Override
     90     public boolean hasResolutions(IMarker marker) {
     91         String message = null;
     92         try {
     93             message = (String) marker.getAttribute(IMarker.MESSAGE);
     94         } catch (CoreException e) {
     95             AdtPlugin.log(e, null);
     96         }
     97 
     98         return message != null
     99                 && (message.contains(getTargetMarkerErrorMessage())
    100                         || message.contains(getUnboundErrorMessage()));
    101     }
    102 
    103     @Override
    104     public IMarkerResolution[] getResolutions(IMarker marker) {
    105         IResource markerResource = marker.getResource();
    106         IProject project = markerResource.getProject();
    107         try {
    108             String message = (String) marker.getAttribute(IMarker.MESSAGE);
    109             if (message.contains(getUnboundErrorMessage()) && markerResource instanceof IFile) {
    110                 return new IMarkerResolution[] {
    111                         new CreateNamespaceFix((IFile) markerResource)
    112                     };
    113             }
    114         } catch (CoreException e1) {
    115             AdtPlugin.log(e1, null);
    116         }
    117 
    118         int start = marker.getAttribute(IMarker.CHAR_START, 0);
    119         int end = marker.getAttribute(IMarker.CHAR_END, 0);
    120         if (end > start) {
    121             int length = end - start;
    122             IDocumentProvider provider = new TextFileDocumentProvider();
    123             try {
    124                 provider.connect(markerResource);
    125                 IDocument document = provider.getDocument(markerResource);
    126                 String resource = document.get(start, length);
    127                 if (ResourceHelper.canCreateResource(resource)) {
    128                     return new IMarkerResolution[] {
    129                         new CreateResourceProposal(project, resource)
    130                     };
    131                 }
    132             } catch (Exception e) {
    133                 AdtPlugin.log(e, "Can't find range information for %1$s", markerResource);
    134             } finally {
    135                 provider.disconnect(markerResource);
    136             }
    137         }
    138 
    139         return null;
    140     }
    141 
    142     // ---- Implements IQuickAssistProcessor ----
    143 
    144     @Override
    145     public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
    146         return true;
    147     }
    148 
    149     @Override
    150     public boolean canFix(Annotation annotation) {
    151         return true;
    152     }
    153 
    154     @Override
    155     public ICompletionProposal[] computeQuickAssistProposals(
    156             IQuickAssistInvocationContext invocationContext) {
    157 
    158         // We have to find the corresponding project/file (so we can look up the aapt
    159         // error markers). Unfortunately, an IQuickAssistProcessor only gets
    160         // access to an ISourceViewer which has no hooks back to the surrounding
    161         // editor.
    162         //
    163         // However, the IQuickAssistProcessor will only be used interactively by a file
    164         // being edited, so we can cheat like the hyperlink detector and simply
    165         // look up the currently active file in the IDE. To be on the safe side,
    166         // we'll make sure that that editor has the same sourceViewer such that
    167         // we are indeed looking at the right file:
    168         ISourceViewer sourceViewer = invocationContext.getSourceViewer();
    169         AndroidXmlEditor editor = AndroidXmlEditor.fromTextViewer(sourceViewer);
    170         if (editor != null) {
    171             IFile file = editor.getInputFile();
    172             if (file == null) {
    173                 return null;
    174             }
    175             IDocument document = sourceViewer.getDocument();
    176             List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_AAPT_COMPILE,
    177                     file, document, invocationContext.getOffset());
    178             try {
    179                 for (IMarker marker : markers) {
    180                     String message = marker.getAttribute(IMarker.MESSAGE, ""); //$NON-NLS-1$
    181                     if (message.contains(getTargetMarkerErrorMessage())) {
    182                         int start = marker.getAttribute(IMarker.CHAR_START, 0);
    183                         int end = marker.getAttribute(IMarker.CHAR_END, 0);
    184                         int length = end - start;
    185                         String resource = document.get(start, length);
    186                         // Can only offer create value for non-framework value
    187                         // resources
    188                         if (ResourceHelper.canCreateResource(resource)) {
    189                             IProject project = editor.getProject();
    190                             return new ICompletionProposal[] {
    191                                 new CreateResourceProposal(project, resource)
    192                             };
    193                         }
    194                     } else if (message.contains(getUnboundErrorMessage())) {
    195                         return new ICompletionProposal[] {
    196                             new CreateNamespaceFix(null)
    197                         };
    198                     }
    199                 }
    200             } catch (BadLocationException e) {
    201                 AdtPlugin.log(e, null);
    202             }
    203         }
    204 
    205         return null;
    206     }
    207 
    208     @Override
    209     public String getErrorMessage() {
    210         return null;
    211     }
    212 
    213     /** Quick fix to insert namespace binding when missing */
    214     private final static class CreateNamespaceFix
    215             implements ICompletionProposal, IMarkerResolution2 {
    216         private IFile mFile;
    217 
    218         public CreateNamespaceFix(IFile file) {
    219             mFile = file;
    220         }
    221 
    222         private IndexedRegion perform(IDocument doc) {
    223             IModelManager manager = StructuredModelManager.getModelManager();
    224             IStructuredModel model = manager.getExistingModelForEdit(doc);
    225             if (model != null) {
    226                 try {
    227                     perform(model);
    228                 } finally {
    229                     model.releaseFromEdit();
    230                 }
    231             }
    232 
    233             return null;
    234         }
    235 
    236         private IndexedRegion perform(IFile file) {
    237             IModelManager manager = StructuredModelManager.getModelManager();
    238             IStructuredModel model;
    239             try {
    240                 model = manager.getModelForEdit(file);
    241                 if (model != null) {
    242                     try {
    243                         perform(model);
    244                     } finally {
    245                         model.releaseFromEdit();
    246                     }
    247                 }
    248             } catch (Exception e) {
    249                 AdtPlugin.log(e, "Can't look up XML model");
    250             }
    251 
    252             return null;
    253         }
    254 
    255         private IndexedRegion perform(IStructuredModel model) {
    256             if (model instanceof IDOMModel) {
    257                 IDOMModel domModel = (IDOMModel) model;
    258                 Document document = domModel.getDocument();
    259                 Element element = document.getDocumentElement();
    260                 Attr attr = document.createAttributeNS(XMLNS_URI, XMLNS_ANDROID);
    261                 attr.setValue(ANDROID_URI);
    262                 element.getAttributes().setNamedItemNS(attr);
    263                 return (IndexedRegion) attr;
    264             }
    265 
    266             return null;
    267         }
    268 
    269         // ---- Implements ICompletionProposal ----
    270 
    271         @Override
    272         public void apply(IDocument document) {
    273             perform(document);
    274         }
    275 
    276         @Override
    277         public String getAdditionalProposalInfo() {
    278             return "Adds an Android namespace declaratiopn to the root element.";
    279         }
    280 
    281         @Override
    282         public IContextInformation getContextInformation() {
    283             return null;
    284         }
    285 
    286         @Override
    287         public String getDisplayString() {
    288             return "Insert namespace binding";
    289         }
    290 
    291         @Override
    292         public Image getImage() {
    293             return AdtPlugin.getAndroidLogo();
    294         }
    295 
    296         @Override
    297         public Point getSelection(IDocument doc) {
    298             return null;
    299         }
    300 
    301 
    302         // ---- Implements MarkerResolution2 ----
    303 
    304         @Override
    305         public String getLabel() {
    306             return getDisplayString();
    307         }
    308 
    309         @Override
    310         public void run(IMarker marker) {
    311             try {
    312                 AdtPlugin.openFile(mFile, null);
    313             } catch (PartInitException e) {
    314                 AdtPlugin.log(e, "Can't open file %1$s", mFile.getName());
    315             }
    316 
    317             IndexedRegion indexedRegion = perform(mFile);
    318             if (indexedRegion != null) {
    319                 try {
    320                     IRegion region =
    321                         new Region(indexedRegion.getStartOffset(), indexedRegion.getLength());
    322                     AdtPlugin.openFile(mFile, region);
    323                 } catch (PartInitException e) {
    324                     AdtPlugin.log(e, "Can't open file %1$s", mFile.getName());
    325                 }
    326             }
    327         }
    328 
    329         @Override
    330         public String getDescription() {
    331             return getAdditionalProposalInfo();
    332         }
    333     }
    334 
    335     private static class CreateResourceProposal
    336             implements ICompletionProposal, IMarkerResolution2 {
    337         private final IProject mProject;
    338         private final String mResource;
    339 
    340         CreateResourceProposal(IProject project, String resource) {
    341             super();
    342             mProject = project;
    343             mResource = resource;
    344         }
    345 
    346         private void perform() {
    347             ResourceUrl resource = ResourceUrl.parse(mResource);
    348             if (resource == null) {
    349                 return;
    350             }
    351             ResourceType type = resource.type;
    352             String name = resource.name;
    353             assert !resource.framework;
    354             String value = ""; //$NON-NLS-1$
    355 
    356             // Try to pick a reasonable first guess. The new value will be highlighted and
    357             // selected for editing, but if we have an initial value then the new file
    358             // won't show an error.
    359             switch (type) {
    360                 case STRING: value = "TODO"; break; //$NON-NLS-1$
    361                 case DIMEN: value = "1dp"; break; //$NON-NLS-1$
    362                 case BOOL: value = "true"; break; //$NON-NLS-1$
    363                 case COLOR: value = "#000000"; break; //$NON-NLS-1$
    364                 case INTEGER: value = "1"; break; //$NON-NLS-1$
    365                 case ARRAY: value = "<item>1</item>"; break; //$NON-NLS-1$
    366             }
    367 
    368             Pair<IFile, IRegion> location =
    369                 ResourceHelper.createResource(mProject, type, name, value);
    370             if (location != null) {
    371                 IFile file = location.getFirst();
    372                 IRegion region = location.getSecond();
    373                 try {
    374                     AdtPlugin.openFile(file, region);
    375                 } catch (PartInitException e) {
    376                     AdtPlugin.log(e, "Can't open file %1$s", file.getName());
    377                 }
    378             }
    379         }
    380 
    381         // ---- Implements ICompletionProposal ----
    382 
    383         @Override
    384         public void apply(IDocument document) {
    385             perform();
    386         }
    387 
    388         @Override
    389         public String getAdditionalProposalInfo() {
    390             return "Creates an XML file entry for the given missing resource "
    391                     + "and opens it in the editor.";
    392         }
    393 
    394         @Override
    395         public IContextInformation getContextInformation() {
    396             return null;
    397         }
    398 
    399         @Override
    400         public String getDisplayString() {
    401             return String.format("Create resource %1$s", mResource);
    402         }
    403 
    404         @Override
    405         public Image getImage() {
    406             return AdtPlugin.getAndroidLogo();
    407         }
    408 
    409         @Override
    410         public Point getSelection(IDocument document) {
    411             return null;
    412         }
    413 
    414         // ---- Implements MarkerResolution2 ----
    415 
    416         @Override
    417         public String getLabel() {
    418             return getDisplayString();
    419         }
    420 
    421         @Override
    422         public void run(IMarker marker) {
    423             perform();
    424         }
    425 
    426         @Override
    427         public String getDescription() {
    428             return getAdditionalProposalInfo();
    429         }
    430     }
    431 }
    432