001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.java.hints.errors;
042:
043: import com.sun.source.tree.AnnotationTree;
044: import com.sun.source.tree.AssignmentTree;
045: import com.sun.source.tree.ClassTree;
046: import com.sun.source.tree.ExpressionTree;
047: import com.sun.source.tree.LiteralTree;
048: import com.sun.source.tree.MethodTree;
049: import com.sun.source.tree.ModifiersTree;
050: import com.sun.source.tree.NewArrayTree;
051: import com.sun.source.tree.Tree;
052: import com.sun.source.tree.Tree.Kind;
053: import com.sun.source.tree.VariableTree;
054: import com.sun.source.util.TreePath;
055: import java.io.IOException;
056: import java.util.ArrayList;
057: import java.util.Arrays;
058: import java.util.Collections;
059: import java.util.EnumSet;
060: import java.util.HashMap;
061: import java.util.HashSet;
062: import java.util.Iterator;
063: import java.util.List;
064: import java.util.Map;
065: import java.util.Set;
066: import java.util.logging.Level;
067: import java.util.logging.Logger;
068: import javax.lang.model.element.Element;
069: import javax.lang.model.element.TypeElement;
070: import org.netbeans.api.java.queries.SourceLevelQuery;
071: import org.netbeans.api.java.source.Task;
072: import org.netbeans.api.java.source.CompilationInfo;
073: import org.netbeans.api.java.source.JavaSource;
074: import org.netbeans.api.java.source.JavaSource.Phase;
075: import org.netbeans.api.java.source.TreePathHandle;
076: import org.netbeans.api.java.source.WorkingCopy;
077: import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
078: import org.netbeans.modules.java.hints.spi.ErrorRule;
079: import org.netbeans.modules.java.hints.spi.ErrorRule.Data;
080: import org.netbeans.spi.editor.hints.ChangeInfo;
081: import org.netbeans.spi.editor.hints.Fix;
082: import org.netbeans.spi.editor.hints.Fix;
083: import org.openide.filesystems.FileObject;
084: import org.openide.modules.SpecificationVersion;
085: import org.openide.util.Exceptions;
086: import org.openide.util.NbBundle;
087:
088: /**
089: *
090: * @author Jan Lahoda
091: */
092: public class SuppressWarningsFixer implements ErrorRule<Void> {
093:
094: /** Creates a new instance of SuppressWarningsFixer */
095: public SuppressWarningsFixer() {
096: }
097:
098: private static final Map<String, String> KEY2SUPRESS_KEY;
099:
100: static {
101: Map<String, String> map = new HashMap<String, String>();
102:
103: String uncheckedKey = "unchecked";
104:
105: map.put("compiler.warn.prob.found.req", uncheckedKey); // NOI18N
106: map.put("compiler.warn.unchecked.cast.to.type", uncheckedKey); // NOI18N
107: map.put("compiler.warn.unchecked.assign", uncheckedKey); // NOI18N
108: map.put("compiler.warn.unchecked.assign.to.var", uncheckedKey); // NOI18N
109: map.put("compiler.warn.unchecked.call.mbr.of.raw.type",
110: uncheckedKey); // NOI18N
111: map.put("compiler.warn.unchecked.meth.invocation.applied",
112: uncheckedKey); // NOI18N
113: map.put("compiler.warn.unchecked.generic.array.creation",
114: uncheckedKey); // NOI18N
115:
116: String fallThroughKey = "fallthrough"; // NOI18N
117:
118: map.put("compiler.warn.possible.fall-through.into.case",
119: fallThroughKey); // NOI18N
120:
121: String deprecationKey = "deprecation"; // NOI18N
122:
123: map.put("compiler.warn.has.been.deprecated", deprecationKey); // NOI18N
124:
125: KEY2SUPRESS_KEY = Collections.unmodifiableMap(map);
126: }
127:
128: public Set<String> getCodes() {
129: return KEY2SUPRESS_KEY.keySet();
130: }
131:
132: private boolean isSuppressWarningsSupported(CompilationInfo info) {
133: //cannot suppress if there is no SuppressWarnings annotation in the platform:
134: if (info.getElements().getTypeElement(
135: "java.lang.SuppressWarnings") == null)
136: return false;
137:
138: String sourceVersion = SourceLevelQuery.getSourceLevel(info
139: .getFileObject());
140:
141: if (sourceVersion == null) {
142: return true;
143: }
144:
145: try {
146: SpecificationVersion version = new SpecificationVersion(
147: sourceVersion);
148: SpecificationVersion supp = new SpecificationVersion("1.5");
149:
150: return version.compareTo(supp) >= 0;
151: } catch (NumberFormatException e) {
152: return true;
153: }
154: }
155:
156: public List<Fix> run(CompilationInfo compilationInfo,
157: String diagnosticKey, int offset, TreePath treePath,
158: Data<Void> data) {
159: if (!isSuppressWarningsSupported(compilationInfo)) {
160: return null;
161: }
162:
163: String suppressKey = KEY2SUPRESS_KEY.get(diagnosticKey);
164:
165: final Set<Kind> DECLARATION = EnumSet.of(Kind.CLASS,
166: Kind.METHOD, Kind.VARIABLE);
167:
168: while (treePath.getLeaf().getKind() != Kind.COMPILATION_UNIT
169: && !DECLARATION.contains(treePath.getLeaf().getKind())) {
170: treePath = treePath.getParentPath();
171: }
172:
173: if (suppressKey != null
174: && treePath.getLeaf().getKind() != Kind.COMPILATION_UNIT) {
175: return Collections.singletonList((Fix) new FixImpl(
176: TreePathHandle.create(treePath, compilationInfo),
177: compilationInfo.getFileObject(), suppressKey));
178: }
179:
180: return Collections.<Fix> emptyList();
181: }
182:
183: public void cancel() {
184: }
185:
186: public String getId() {
187: return "SuppressWarningsFixer"; // NOI18N
188: }
189:
190: public String getDisplayName() {
191: return NbBundle.getMessage(SuppressWarningsFixer.class,
192: "LBL_Suppress_Waning"); // NOI18N
193: }
194:
195: public String getDescription() {
196: return NbBundle.getMessage(SuppressWarningsFixer.class,
197: "LBL_Suppress_Waning"); // NOI18N
198: }
199:
200: public static final class FixImpl implements Fix {
201:
202: private String keys[];
203: private TreePathHandle handle;
204: private FileObject file;
205:
206: // public FixImpl(String key, TreePathHandle handle, FileObject file) {
207: // this.key = key;
208: // this.handle = handle;
209: // this.file = file;
210: // }
211:
212: public FixImpl(TreePathHandle handle, FileObject file,
213: String... keys) {
214: this .keys = keys;
215: this .handle = handle;
216: this .file = file;
217: }
218:
219: public String getText() {
220: StringBuilder keyNames = new StringBuilder();
221: for (int i = 0; i < keys.length; i++) {
222: String string = keys[i];
223: keyNames.append(string);
224: if (i < keys.length - 1) {
225: keyNames.append(", "); // NOI18N
226: }
227: }
228:
229: return NbBundle.getMessage(SuppressWarningsFixer.class,
230: "LBL_FIX_Suppress_Waning", keyNames.toString()); // NOI18N
231: }
232:
233: private static final Set<Kind> DECLARATION = EnumSet.of(
234: Kind.CLASS, Kind.METHOD, Kind.VARIABLE);
235:
236: public ChangeInfo implement() throws IOException {
237: JavaSource js = JavaSource.forFileObject(file);
238:
239: js.runModificationTask(new Task<WorkingCopy>() {
240: public void run(WorkingCopy copy) throws IOException {
241: copy.toPhase(Phase.RESOLVED); //XXX: performance
242: TreePath path = handle.resolve(copy);
243:
244: while (path.getLeaf().getKind() != Kind.COMPILATION_UNIT
245: && !DECLARATION.contains(path.getLeaf()
246: .getKind())) {
247: path = path.getParentPath();
248: }
249:
250: if (path.getLeaf().getKind() == Kind.COMPILATION_UNIT) {
251: return;
252: }
253:
254: Tree top = path.getLeaf();
255: ModifiersTree modifiers = null;
256:
257: switch (top.getKind()) {
258: case CLASS:
259: modifiers = ((ClassTree) top).getModifiers();
260: break;
261: case METHOD:
262: modifiers = ((MethodTree) top).getModifiers();
263: break;
264: case VARIABLE:
265: modifiers = ((VariableTree) top).getModifiers();
266: break;
267: default:
268: assert false : "Unhandled Tree.Kind"; // NOI18N
269: }
270:
271: if (modifiers == null) {
272: return;
273: }
274:
275: TypeElement el = copy.getElements().getTypeElement(
276: "java.lang.SuppressWarnings"); // NOI18N
277:
278: if (el == null) {
279: return;
280: }
281:
282: //check for already existing SuppressWarnings annotation:
283: for (AnnotationTree at : modifiers.getAnnotations()) {
284: TreePath tp = new TreePath(new TreePath(path,
285: at), at.getAnnotationType());
286: Element e = copy.getTrees().getElement(tp);
287:
288: if (el.equals(e)) {
289: //found SuppressWarnings:
290: List<? extends ExpressionTree> arguments = at
291: .getArguments();
292:
293: if (arguments.isEmpty()
294: || arguments.size() > 1) {
295: Logger
296: .getLogger(
297: SuppressWarningsFixer.class
298: .getName())
299: .log(
300: Level.INFO,
301: "SupressWarnings annotation has incorrect number of arguments - {0}.",
302: arguments.size()); // NOI18N
303: return;
304: }
305:
306: ExpressionTree et = at.getArguments()
307: .get(0);
308:
309: if (et.getKind() != Kind.ASSIGNMENT) {
310: Logger
311: .getLogger(
312: SuppressWarningsFixer.class
313: .getName())
314: .log(
315: Level.INFO,
316: "SupressWarnings annotation's argument is not an assignment - {0}.",
317: et.getKind()); // NOI18N
318: return;
319: }
320:
321: AssignmentTree assignment = (AssignmentTree) et;
322: List<? extends ExpressionTree> currentValues = null;
323:
324: if (assignment.getExpression().getKind() == Kind.NEW_ARRAY) {
325: currentValues = ((NewArrayTree) assignment
326: .getExpression())
327: .getInitializers();
328: } else {
329: currentValues = Collections
330: .singletonList(assignment
331: .getExpression());
332: }
333:
334: assert currentValues != null;
335:
336: List<ExpressionTree> values = new ArrayList<ExpressionTree>(
337: currentValues);
338:
339: for (String key : keys) {
340: values.add(copy.getTreeMaker().Literal(
341: key));
342: }
343:
344: copy
345: .rewrite(
346: assignment.getExpression(),
347: copy
348: .getTreeMaker()
349: .NewArray(
350: null,
351: Collections
352: .<ExpressionTree> emptyList(),
353: values));
354: return;
355: }
356: }
357:
358: List<AnnotationTree> annotations = new ArrayList<AnnotationTree>(
359: modifiers.getAnnotations());
360:
361: if (keys.length > 1) {
362: List<LiteralTree> keyLiterals = new ArrayList<LiteralTree>(
363: keys.length);
364: for (String key : keys) {
365: keyLiterals.add(copy.getTreeMaker()
366: .Literal(key));
367: }
368: annotations
369: .add(copy
370: .getTreeMaker()
371: .Annotation(
372: copy.getTreeMaker()
373: .QualIdent(el),
374: Collections
375: .singletonList(copy
376: .getTreeMaker()
377: .NewArray(
378: null,
379: Collections
380: .<ExpressionTree> emptyList(),
381: keyLiterals))));
382: } else {
383: annotations.add(copy.getTreeMaker().Annotation(
384: copy.getTreeMaker().QualIdent(el),
385: Collections.singletonList(copy
386: .getTreeMaker()
387: .Literal(keys[0]))));
388: }
389: ModifiersTree nueMods = copy.getTreeMaker()
390: .Modifiers(modifiers, annotations);
391:
392: copy.rewrite(modifiers, nueMods);
393: }
394: }).commit();
395:
396: return null;
397: }
398: }
399: }
|