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 com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AdtUtils; 21 22 import org.eclipse.core.resources.IMarker; 23 import org.eclipse.core.resources.IResource; 24 import org.eclipse.core.runtime.CoreException; 25 import org.eclipse.jdt.core.IBuffer; 26 import org.eclipse.jdt.core.ICompilationUnit; 27 import org.eclipse.jdt.core.compiler.IProblem; 28 import org.eclipse.jdt.core.dom.ASTNode; 29 import org.eclipse.jdt.core.dom.QualifiedName; 30 import org.eclipse.jdt.ui.text.java.IInvocationContext; 31 import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; 32 import org.eclipse.jdt.ui.text.java.IProblemLocation; 33 import org.eclipse.jdt.ui.text.java.IQuickFixProcessor; 34 import org.eclipse.jface.text.IDocument; 35 import org.eclipse.jface.text.contentassist.IContextInformation; 36 import org.eclipse.swt.graphics.Image; 37 import org.eclipse.swt.graphics.Point; 38 import org.eclipse.swt.widgets.Shell; 39 import org.eclipse.ui.editors.text.TextFileDocumentProvider; 40 import org.eclipse.ui.texteditor.IDocumentProvider; 41 42 import java.util.List; 43 44 /** 45 * A quickfix processor which looks for "case expressions must be constant 46 * expressions" errors, and if they apply to fields in a class named R, it 47 * assumes this is code related to library projects that are no longer final and 48 * will need to be rewritten to use if-else chains instead. 49 */ 50 public class ConvertSwitchQuickFixProcessor implements IQuickFixProcessor { 51 /** Constructs a new {@link ConvertSwitchQuickFixProcessor} */ 52 public ConvertSwitchQuickFixProcessor() { 53 } 54 55 @Override 56 public boolean hasCorrections(ICompilationUnit cu, int problemId) { 57 return problemId == IProblem.NonConstantExpression; 58 } 59 60 @Override 61 public IJavaCompletionProposal[] getCorrections(IInvocationContext context, 62 IProblemLocation[] location) throws CoreException { 63 if (location == null || location.length == 0) { 64 return null; 65 } 66 ASTNode coveringNode = context.getCoveringNode(); 67 if (coveringNode == null) { 68 return null; 69 } 70 71 // Look up the fully qualified name of the non-constant expression, if any, and 72 // make sure it's R-something. 73 if (coveringNode.getNodeType() == ASTNode.SIMPLE_NAME) { 74 coveringNode = coveringNode.getParent(); 75 if (coveringNode == null) { 76 return null; 77 } 78 } 79 if (coveringNode.getNodeType() != ASTNode.QUALIFIED_NAME) { 80 return null; 81 } 82 QualifiedName name = (QualifiedName) coveringNode; 83 if (!name.getFullyQualifiedName().startsWith("R.")) { //$NON-NLS-1$ 84 return null; 85 } 86 87 IProblemLocation error = location[0]; 88 int errorStart = error.getOffset(); 89 int errorLength = error.getLength(); 90 int caret = context.getSelectionOffset(); 91 92 // Even though the hasCorrections() method above will return false for everything 93 // other than non-constant expression errors, it turns out this getCorrections() 94 // method will ALSO be called on lines where there is no such error. In particular, 95 // if you have an invalid cast expression like this: 96 // Button button = findViewById(R.id.textView); 97 // then this method will be called, and the expression will pass all of the above 98 // checks. However, we -don't- want to show a migrate code suggestion in that case! 99 // Therefore, we'll need to check if we're *actually* on a line with the given 100 // problem. 101 // 102 // Unfortunately, we don't get passed the problemId again, and there's no access 103 // to it. So instead we'll need to look up the markers on the line, and see 104 // if we actually have a constant expression warning. This is not pretty!! 105 106 boolean foundError = false; 107 ICompilationUnit compilationUnit = context.getCompilationUnit(); 108 IResource file = compilationUnit.getResource(); 109 if (file != null) { 110 IDocumentProvider provider = new TextFileDocumentProvider(); 111 try { 112 provider.connect(file); 113 IDocument document = provider.getDocument(file); 114 if (document != null) { 115 List<IMarker> markers = AdtUtils.findMarkersOnLine(IMarker.PROBLEM, 116 file, document, errorStart); 117 for (IMarker marker : markers) { 118 String message = marker.getAttribute(IMarker.MESSAGE, ""); 119 // There are no other attributes in the marker we can use to identify 120 // the exact error, so we'll need to resort to the actual message 121 // text even though that would not work if the messages had been 122 // localized... This can also break if the error messages change. Yuck. 123 if (message.contains("constant expressions")) { //$NON-NLS-1$ 124 foundError = true; 125 } 126 } 127 } 128 } catch (Exception e) { 129 AdtPlugin.log(e, "Can't validate error message in %1$s", file.getName()); 130 } finally { 131 provider.disconnect(file); 132 } 133 } 134 if (!foundError) { 135 // Not a constant-expression warning, so do nothing 136 return null; 137 } 138 139 IBuffer buffer = compilationUnit.getBuffer(); 140 boolean sameLine = false; 141 // See if the caret is on the same line as the error 142 if (caret <= errorStart) { 143 // Search backwards to beginning of line 144 for (int i = errorStart; i >= 0; i--) { 145 if (i <= caret) { 146 sameLine = true; 147 break; 148 } 149 char c = buffer.getChar(i); 150 if (c == '\n') { 151 break; 152 } 153 } 154 } else { 155 // Search forwards to the end of the line 156 for (int i = errorStart + errorLength, n = buffer.getLength(); i < n; i++) { 157 if (i >= caret) { 158 sameLine = true; 159 break; 160 } 161 char c = buffer.getChar(i); 162 if (c == '\n') { 163 break; 164 } 165 } 166 } 167 168 if (sameLine) { 169 String expression = buffer.getText(errorStart, errorLength); 170 return new IJavaCompletionProposal[] { 171 new MigrateProposal(expression) 172 }; 173 } 174 175 return null; 176 } 177 178 /** Proposal for the quick fix which displays an explanation message to the user */ 179 private class MigrateProposal implements IJavaCompletionProposal { 180 private String mExpression; 181 182 private MigrateProposal(String expression) { 183 mExpression = expression; 184 } 185 186 @Override 187 public void apply(IDocument document) { 188 Shell shell = AdtPlugin.getShell(); 189 ConvertSwitchDialog dialog = new ConvertSwitchDialog(shell, mExpression); 190 dialog.open(); 191 } 192 193 @Override 194 public Point getSelection(IDocument document) { 195 return null; 196 } 197 198 @Override 199 public String getAdditionalProposalInfo() { 200 return "As of ADT 14, resource fields cannot be used as switch cases. Invoke this " + 201 "fix to get more information."; 202 } 203 204 @Override 205 public String getDisplayString() { 206 return "Migrate Android Code"; 207 } 208 209 @Override 210 public Image getImage() { 211 return AdtPlugin.getAndroidLogo(); 212 } 213 214 @Override 215 public IContextInformation getContextInformation() { 216 return null; 217 } 218 219 @Override 220 public int getRelevance() { 221 return 50; 222 } 223 } 224 } 225