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-2006 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.CatchTree;
044: import com.sun.source.tree.ExpressionTree;
045: import com.sun.source.tree.IdentifierTree;
046: import com.sun.source.tree.MethodInvocationTree;
047: import com.sun.source.tree.MethodTree;
048: import com.sun.source.tree.NewClassTree;
049: import com.sun.source.tree.StatementTree;
050: import com.sun.source.tree.ThrowTree;
051: import com.sun.source.tree.Tree;
052: import com.sun.source.tree.Tree.Kind;
053: import com.sun.source.tree.TryTree;
054: import com.sun.source.util.TreePath;
055: import java.io.IOException;
056: import java.util.ArrayList;
057: import java.util.Collections;
058: import java.util.EnumSet;
059: import java.util.HashSet;
060: import java.util.Iterator;
061: import java.util.LinkedList;
062: import java.util.List;
063: import java.util.Set;
064: import javax.lang.model.element.Element;
065: import javax.lang.model.element.ElementKind;
066: import javax.lang.model.element.ExecutableElement;
067: import javax.lang.model.element.TypeElement;
068: import javax.lang.model.element.VariableElement;
069: import javax.lang.model.type.ExecutableType;
070: import javax.lang.model.type.TypeMirror;
071: import javax.lang.model.type.TypeKind;
072: import org.netbeans.api.java.source.Task;
073: import org.netbeans.api.java.source.CompilationInfo;
074: import org.netbeans.api.java.source.ElementHandle;
075: import org.netbeans.api.java.source.JavaSource;
076: import org.netbeans.api.java.source.JavaSource.Phase;
077: import org.netbeans.api.java.source.TreePathHandle;
078: import org.netbeans.api.java.source.TypeMirrorHandle;
079: import org.netbeans.api.java.source.WorkingCopy;
080: import org.netbeans.modules.editor.java.Utilities;
081: import org.netbeans.modules.java.editor.overridden.AnnotationType;
082: import org.netbeans.modules.java.editor.overridden.ElementDescription;
083: import org.netbeans.modules.java.editor.overridden.IsOverriddenAnnotationHandler;
084: import org.netbeans.modules.java.hints.spi.ErrorRule;
085: import org.netbeans.spi.editor.hints.ChangeInfo;
086: import org.netbeans.spi.editor.hints.Fix;
087: import org.openide.util.NbBundle;
088:
089: /**
090: *
091: * @author Jan Lahoda
092: */
093: public final class UncaughtException implements ErrorRule<Void> {
094:
095: /**
096: * Creates a new instance of UncaughtExceptionCreator
097: */
098: public UncaughtException() {
099: }
100:
101: private List<? extends TypeMirror> findUncaughtExceptions(
102: CompilationInfo info, TreePath path,
103: List<? extends TypeMirror> exceptions) {
104: List<TypeMirror> result = new ArrayList<TypeMirror>();
105:
106: result.addAll(exceptions);
107:
108: Tree lastTree = null;
109:
110: while (path != null) {
111: TypeMirror tm = info.getTrees().getTypeMirror(path);
112:
113: if (tm != null && tm.getKind() == TypeKind.EXECUTABLE) {
114: for (TypeMirror mirr : ((ExecutableType) tm)
115: .getThrownTypes()) {
116: for (Iterator<TypeMirror> it = result.iterator(); it
117: .hasNext();)
118: if (info.getTypes().isSameType(it.next(), mirr))
119: it.remove();
120: }
121: break;
122: }
123:
124: Tree currentTree = path.getLeaf();
125:
126: if (currentTree.getKind() == Kind.TRY) {
127: TryTree tt = (TryTree) currentTree;
128:
129: if (tt.getBlock() == lastTree) {
130: for (CatchTree c : tt.getCatches()) {
131: TreePath catchPath = new TreePath(new TreePath(
132: path, c), c.getParameter());
133: VariableElement variable = (VariableElement) info
134: .getTrees().getElement(catchPath);
135:
136: result.remove(variable.asType());
137: }
138: }
139: }
140:
141: lastTree = path.getLeaf();
142: path = path.getParentPath();
143: }
144:
145: return result;
146: }
147:
148: public Set<String> getCodes() {
149: return Collections
150: .singleton("compiler.err.unreported.exception.need.to.catch.or.throw");
151: }
152:
153: @SuppressWarnings("fallthrough")
154: public List<Fix> run(CompilationInfo info, String diagnosticKey,
155: int offset, TreePath treePath, Data<Void> data) {
156: List<Fix> result = new ArrayList<Fix>();
157: TreePath path = info.getTreeUtilities().pathFor(offset + 1);
158: List<? extends TypeMirror> uncaught = null;
159: boolean disableSurroundWithTryCatch = false;
160: Element el;
161:
162: OUTTER: while (path != null) {
163: Tree leaf = path.getLeaf();
164:
165: switch (leaf.getKind()) {
166: case METHOD_INVOCATION:
167: //check for super/this constructor call (and disable surround with try-catch):
168: MethodInvocationTree mit = (MethodInvocationTree) leaf;
169:
170: if (mit.getMethodSelect().getKind() == Kind.IDENTIFIER) {
171: String ident = ((IdentifierTree) mit
172: .getMethodSelect()).getName().toString();
173:
174: if ("super".equals(ident) || "this".equals(ident)) {
175: Element element = info.getTrees().getElement(
176: path);
177:
178: disableSurroundWithTryCatch = element != null
179: && element.getKind() == ElementKind.CONSTRUCTOR;
180: }
181: }
182: case NEW_CLASS:
183: el = info.getTrees().getElement(path);
184:
185: //IZ 95535 -- dont't offer surround with T-C for fields
186: if (!isInsideMethod(path))
187: disableSurroundWithTryCatch = true;
188:
189: if (isThisParameter(path)) {
190: disableSurroundWithTryCatch = el != null
191: && (el.getKind() == ElementKind.CONSTRUCTOR || el
192: .getKind() == ElementKind.METHOD);
193: }
194:
195: if (el != null
196: && EXECUTABLE_ELEMENTS.contains(el.getKind())) {
197: TypeMirror uncaughtException;
198: if (leaf.getKind() == Kind.NEW_CLASS)
199: uncaughtException = info
200: .getTrees()
201: .getTypeMirror(
202: new TreePath(
203: path,
204: ((NewClassTree) leaf)
205: .getIdentifier()));
206: else
207: uncaughtException = info
208: .getTrees()
209: .getTypeMirror(
210: new TreePath(
211: path,
212: ((MethodInvocationTree) leaf)
213: .getMethodSelect()));
214:
215: if (uncaughtException != null
216: && uncaughtException.getKind() == TypeKind.EXECUTABLE)
217: uncaught = ((ExecutableType) uncaughtException)
218: .getThrownTypes();
219: else
220: uncaught = ((ExecutableElement) el)
221: .getThrownTypes();
222: }
223: path = path.getParentPath();
224: break OUTTER;
225: case THROW:
226: TypeMirror uncaughtException = info.getTrees()
227: .getTypeMirror(
228: new TreePath(path, ((ThrowTree) leaf)
229: .getExpression()));
230: uncaught = Collections.singletonList(uncaughtException);
231: break OUTTER;
232: }
233:
234: path = path.getParentPath();
235: }
236:
237: if (uncaught != null) {
238: uncaught = findUncaughtExceptions(info, path, uncaught);
239:
240: TreePath pathRec = path;
241:
242: while (pathRec != null
243: && pathRec.getLeaf().getKind() != Kind.METHOD) {
244: pathRec = pathRec.getParentPath();
245: }
246:
247: ExecutableElement method = pathRec != null ? (ExecutableElement) info
248: .getTrees().getElement(pathRec)
249: : null;
250:
251: if (method != null) {
252: //if the method header is inside a guarded block, do nothing:
253: if (!org.netbeans.modules.java.hints.errors.Utilities
254: .isMethodHeaderInsideGuardedBlock(info,
255: (MethodTree) pathRec.getLeaf())) {
256: List<ElementDescription> eds = new LinkedList<ElementDescription>();
257: AnnotationType at = IsOverriddenAnnotationHandler
258: .detectOverrides(info, (TypeElement) method
259: .getEnclosingElement(), method, eds);
260: List<TypeMirror> declaredThrows = null;
261:
262: if (at != null) {
263: declaredThrows = new LinkedList<TypeMirror>();
264:
265: for (ElementDescription ed : eds) {
266: ExecutableElement ee = (ExecutableElement) ed
267: .getHandle().resolve(info);
268: List<TypeMirror> this DeclaredThrows = new LinkedList<TypeMirror>(
269: ee.getThrownTypes());
270:
271: for (Iterator<TypeMirror> dt = declaredThrows
272: .iterator(); dt.hasNext();) {
273: for (Iterator<TypeMirror> tdt = this DeclaredThrows
274: .iterator(); tdt.hasNext();) {
275: TypeMirror dtNext = dt.next();
276: TypeMirror tdtNext = tdt.next();
277:
278: if (info.getTypes().isSubtype(
279: tdtNext, dtNext)) {
280: tdt.remove();
281: continue;
282: }
283:
284: if (info.getTypes().isSubtype(
285: dtNext, tdtNext)) {
286: dt.remove();
287: continue;
288: }
289:
290: tdt.remove();
291: dt.remove();
292: }
293: }
294:
295: declaredThrows.addAll(this DeclaredThrows);
296: }
297: }
298:
299: for (TypeMirror tm : uncaught) {
300: if (declaredThrows != null) {
301: boolean found = false;
302:
303: for (TypeMirror decl : declaredThrows) {
304: if (info.getTypes().isSubtype(tm, decl)) {
305: found = true;
306: break;
307: }
308: }
309:
310: if (!found) {
311: continue;
312: }
313: }
314:
315: if (tm.getKind() != TypeKind.ERROR) {
316: result.add(new AddThrowsClauseHintImpl(info
317: .getJavaSource(), Utilities
318: .getTypeName(tm, true).toString(),
319: TypeMirrorHandle.create(tm),
320: ElementHandle.create(method)));
321: }
322: }
323: }
324: }
325:
326: if (!uncaught.isEmpty() && !disableSurroundWithTryCatch) {
327: List<TypeMirrorHandle> thandles = new ArrayList<TypeMirrorHandle>();
328: List<String> fqns = new ArrayList<String>();
329:
330: for (TypeMirror tm : uncaught) {
331: if (tm.getKind() != TypeKind.ERROR) {
332: thandles.add(TypeMirrorHandle.create(tm));
333: fqns.add(Utilities.getTypeName(tm, true)
334: .toString());
335: }
336: }
337:
338: if (ErrorFixesFakeHint
339: .enabled(ErrorFixesFakeHint.FixKind.SURROUND_WITH_TRY_CATCH)) {
340: result.add(new OrigSurroundWithTryCatchFix(info
341: .getJavaSource(), thandles, TreePathHandle
342: .create(path, info)));
343: result.add(new MagicSurroundWithTryCatchFix(info
344: .getJavaSource(), thandles, offset,
345: ElementHandle.create(method), fqns));
346: }
347: }
348: }
349:
350: return result;
351: }
352:
353: /**
354: * Detects if we are parameter of this() or super() call
355: * @return true if yes
356: */
357: private boolean isThisParameter(TreePath path) {
358: //anonymous class must not be on the path to top
359: while (path.getLeaf().getKind() != Kind.CLASS
360: && path.getLeaf().getKind() != Kind.COMPILATION_UNIT) {
361: if (path.getParentPath().getLeaf().getKind() == Kind.METHOD_INVOCATION) {
362: MethodInvocationTree mi = (MethodInvocationTree) path
363: .getParentPath().getLeaf();
364: if (mi.getMethodSelect().getKind() == Kind.IDENTIFIER) {
365: String id = ((IdentifierTree) mi.getMethodSelect())
366: .getName().toString();
367: if ("super".equals(id) || "this".equals(id))
368: return true;
369: }
370: }
371: path = path.getParentPath();
372: }
373: return false;
374: }
375:
376: private static boolean isInsideMethod(TreePath tp) {
377: while (tp != null) {
378: if (tp.getLeaf().getKind() == Kind.METHOD)
379: return true;
380:
381: tp = tp.getParentPath();
382: }
383:
384: return false;
385: }
386:
387: public void cancel() {
388: //XXX: not done yet
389: }
390:
391: public String getId() {
392: return UncaughtException.class.getName();
393: }
394:
395: public String getDisplayName() {
396: return NbBundle.getMessage(UncaughtException.class,
397: "DN_AddThrowsClauseAndSurround");
398: }
399:
400: public String getDescription() {
401: return NbBundle.getMessage(UncaughtException.class,
402: "DESC_AddThrowsClauseAndSurround");
403: }
404:
405: private static final Set<ElementKind> EXECUTABLE_ELEMENTS = EnumSet
406: .of(ElementKind.CONSTRUCTOR, ElementKind.METHOD);
407:
408: private static final class AddThrowsClauseHintImpl implements Fix {
409:
410: private JavaSource js;
411: private String fqn;
412: private TypeMirrorHandle thandle;
413: private ElementHandle<ExecutableElement> method;
414:
415: public AddThrowsClauseHintImpl(JavaSource js, String fqn,
416: TypeMirrorHandle thandle,
417: ElementHandle<ExecutableElement> method) {
418: this .js = js;
419: this .fqn = fqn;
420: this .thandle = thandle;
421: this .method = method;
422: }
423:
424: public String getText() {
425: return NbBundle.getMessage(UncaughtException.class,
426: "FIX_AddThrowsClause", new Object[] { String
427: .valueOf(fqn) });
428: }
429:
430: public ChangeInfo implement() throws IOException {
431: js.runModificationTask(new Task<WorkingCopy>() {
432: public void run(WorkingCopy wc) throws Exception {
433: wc.toPhase(Phase.RESOLVED);
434: Tree tree = wc.getTrees().getTree(
435: method.resolve(wc));
436:
437: assert tree != null;
438: assert tree.getKind() == Kind.METHOD;
439:
440: MethodTree nue = wc.getTreeMaker().addMethodThrows(
441: (MethodTree) tree,
442: (ExpressionTree) wc.getTreeMaker().Type(
443: thandle.resolve(wc)));
444:
445: wc.rewrite(tree, nue);
446: }
447: }).commit();
448: return null;
449: }
450:
451: @Override
452: public boolean equals(Object obj) {
453: if (obj == null) {
454: return false;
455: }
456: if (getClass() != obj.getClass()) {
457: return false;
458: }
459: final AddThrowsClauseHintImpl other = (AddThrowsClauseHintImpl) obj;
460: if (this .js != other.js
461: && (this .js == null || !this .js.equals(other.js))) {
462: return false;
463: }
464: if (this .fqn == null || !this .fqn.equals(other.fqn)) {
465: return false;
466: }
467: if (this .method != other.method
468: && (this .method == null || !this .method
469: .equals(other.method))) {
470: return false;
471: }
472: return true;
473: }
474:
475: @Override
476: public int hashCode() {
477: int hash = 7;
478: hash = 13 * hash
479: + (this .fqn != null ? this .fqn.hashCode() : 0);
480: hash = 13
481: * hash
482: + (this .method != null ? this .method.hashCode() : 0);
483: return hash;
484: }
485:
486: }
487:
488: static final Set<Kind> STATEMENT_KINDS;
489:
490: static {
491: Set<Kind> kinds = new HashSet<Kind>();
492:
493: for (Kind k : Kind.values()) {
494: Class c = k.asInterface();
495:
496: if (c != null && StatementTree.class.isAssignableFrom(c)) {
497: kinds.add(k);
498: }
499: }
500:
501: STATEMENT_KINDS = Collections.unmodifiableSet(EnumSet
502: .copyOf(kinds));
503: }
504:
505: }
|