• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AaptQuickFix()74     public AaptQuickFix() {
75     }
76 
77     /** Returns the error message from aapt that signals missing resources */
getTargetMarkerErrorMessage()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 */
getUnboundErrorMessage()83     private static String getUnboundErrorMessage() {
84         return "Error parsing XML: unbound prefix";
85     }
86 
87     // ---- Implements IMarkerResolution2 ----
88 
89     @Override
hasResolutions(IMarker marker)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
getResolutions(IMarker marker)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
canAssist(IQuickAssistInvocationContext invocationContext)145     public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
146         return true;
147     }
148 
149     @Override
canFix(Annotation annotation)150     public boolean canFix(Annotation annotation) {
151         return true;
152     }
153 
154     @Override
computeQuickAssistProposals( IQuickAssistInvocationContext invocationContext)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
getErrorMessage()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 
CreateNamespaceFix(IFile file)218         public CreateNamespaceFix(IFile file) {
219             mFile = file;
220         }
221 
perform(IDocument doc)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 
perform(IFile file)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 
perform(IStructuredModel model)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
apply(IDocument document)272         public void apply(IDocument document) {
273             perform(document);
274         }
275 
276         @Override
getAdditionalProposalInfo()277         public String getAdditionalProposalInfo() {
278             return "Adds an Android namespace declaratiopn to the root element.";
279         }
280 
281         @Override
getContextInformation()282         public IContextInformation getContextInformation() {
283             return null;
284         }
285 
286         @Override
getDisplayString()287         public String getDisplayString() {
288             return "Insert namespace binding";
289         }
290 
291         @Override
getImage()292         public Image getImage() {
293             return AdtPlugin.getAndroidLogo();
294         }
295 
296         @Override
getSelection(IDocument doc)297         public Point getSelection(IDocument doc) {
298             return null;
299         }
300 
301 
302         // ---- Implements MarkerResolution2 ----
303 
304         @Override
getLabel()305         public String getLabel() {
306             return getDisplayString();
307         }
308 
309         @Override
run(IMarker marker)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
getDescription()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 
CreateResourceProposal(IProject project, String resource)340         CreateResourceProposal(IProject project, String resource) {
341             super();
342             mProject = project;
343             mResource = resource;
344         }
345 
perform()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
apply(IDocument document)384         public void apply(IDocument document) {
385             perform();
386         }
387 
388         @Override
getAdditionalProposalInfo()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
getContextInformation()395         public IContextInformation getContextInformation() {
396             return null;
397         }
398 
399         @Override
getDisplayString()400         public String getDisplayString() {
401             return String.format("Create resource %1$s", mResource);
402         }
403 
404         @Override
getImage()405         public Image getImage() {
406             return AdtPlugin.getAndroidLogo();
407         }
408 
409         @Override
getSelection(IDocument document)410         public Point getSelection(IDocument document) {
411             return null;
412         }
413 
414         // ---- Implements MarkerResolution2 ----
415 
416         @Override
getLabel()417         public String getLabel() {
418             return getDisplayString();
419         }
420 
421         @Override
run(IMarker marker)422         public void run(IMarker marker) {
423             perform();
424         }
425 
426         @Override
getDescription()427         public String getDescription() {
428             return getAdditionalProposalInfo();
429         }
430     }
431 }
432