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:
042: package org.netbeans.modules.java.hints.errors;
043:
044: import com.sun.source.tree.BlockTree;
045: import com.sun.source.tree.CatchTree;
046: import com.sun.source.tree.ClassTree;
047: import com.sun.source.tree.ExpressionTree;
048: import com.sun.source.tree.ImportTree;
049: import com.sun.source.tree.MemberSelectTree;
050: import com.sun.source.tree.MethodTree;
051: import com.sun.source.tree.Scope;
052: import com.sun.source.tree.StatementTree;
053: import com.sun.source.tree.Tree;
054: import com.sun.source.tree.Tree.Kind;
055: import com.sun.source.tree.TryTree;
056: import com.sun.source.tree.VariableTree;
057: import com.sun.source.util.TreePath;
058: import com.sun.source.util.TreePathScanner;
059: import com.sun.source.util.Trees;
060: import java.io.IOException;
061: import java.util.ArrayList;
062: import java.util.Arrays;
063: import java.util.Collections;
064: import java.util.EnumSet;
065: import java.util.HashSet;
066: import java.util.Iterator;
067: import java.util.LinkedList;
068: import java.util.List;
069: import java.util.Set;
070: import javax.lang.model.element.Element;
071: import javax.lang.model.element.ElementKind;
072: import javax.lang.model.element.ExecutableElement;
073: import javax.lang.model.element.Modifier;
074: import javax.lang.model.element.TypeElement;
075: import javax.lang.model.type.TypeMirror;
076: import org.netbeans.api.java.source.ElementUtilities;
077: import org.netbeans.api.java.source.Task;
078: import org.netbeans.api.java.source.CompilationInfo;
079: import org.netbeans.api.java.source.ElementHandle;
080: import org.netbeans.api.java.source.ElementUtilities.ElementAcceptor;
081: import org.netbeans.api.java.source.JavaSource;
082: import org.netbeans.api.java.source.JavaSource.Phase;
083: import org.netbeans.api.java.source.TreeMaker;
084: import org.netbeans.api.java.source.TypeMirrorHandle;
085: import org.netbeans.api.java.source.WorkingCopy;
086: import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
087: import org.netbeans.spi.editor.hints.ChangeInfo;
088: import org.netbeans.spi.editor.hints.Fix;
089: import org.openide.util.NbBundle;
090:
091: /**
092: *
093: * @author Jan Lahoda
094: */
095: final class MagicSurroundWithTryCatchFix implements Fix {
096:
097: private JavaSource js;
098: private List<TypeMirrorHandle> thandles;
099: private int offset;
100: private ElementHandle<ExecutableElement> method;
101: List<String> fqns;
102:
103: public MagicSurroundWithTryCatchFix(JavaSource js,
104: List<TypeMirrorHandle> thandles, int offset,
105: ElementHandle<ExecutableElement> method, List<String> fqns) {
106: this .js = js;
107: this .thandles = thandles;
108: this .offset = offset;
109: this .method = method;
110: this .fqns = fqns;
111: }
112:
113: public String getText() {
114: return NbBundle.getMessage(MagicSurroundWithTryCatchFix.class,
115: "LBL_SurroundBlockWithTryCatch");
116: }
117:
118: private static final String[] STREAM_ALIKE_CLASSES = new String[] {
119: "java.io.InputStream", "java.io.OutputStream",
120: "java.io.Reader", "java.io.Writer", };
121:
122: private boolean isStreamAlike(CompilationInfo info, TypeMirror type) {
123: for (String fqn : STREAM_ALIKE_CLASSES) {
124: Element inputStream = info.getElements()
125: .getTypeElement(fqn);
126:
127: if (info.getTypes()
128: .isAssignable(type, inputStream.asType()))
129: return true;
130: }
131:
132: return false;
133: }
134:
135: public ChangeInfo implement() throws IOException {
136: js.runModificationTask(new Task<WorkingCopy>() {
137: public void run(WorkingCopy wc) throws Exception {
138: wc.toPhase(Phase.RESOLVED);
139: TreePath currentPath = wc.getTreeUtilities().pathFor(
140: offset + 1);
141:
142: //find statement:
143: while (currentPath != null
144: && !UncaughtException.STATEMENT_KINDS
145: .contains(currentPath.getLeaf()
146: .getKind()))
147: currentPath = currentPath.getParentPath();
148:
149: //TODO: test for final??
150: TreePath statement = currentPath;
151: boolean streamAlike = false;
152:
153: if (statement.getLeaf().getKind() == Kind.VARIABLE) {
154: //special case variable declarations which intializers create streams or readers/writers:
155: Element curType = wc.getTrees().getElement(
156: statement);
157:
158: streamAlike = isStreamAlike(wc, curType.asType());
159: }
160:
161: //find try block containing this statement, if exists:
162: TreePath catchTree = currentPath;
163:
164: while (catchTree != null
165: && catchTree.getLeaf().getKind() != Kind.TRY
166: && catchTree.getLeaf().getKind() != Kind.CLASS
167: && catchTree.getLeaf().getKind() != Kind.CATCH)
168: catchTree = catchTree.getParentPath();
169:
170: boolean createNewTryCatch = false;
171:
172: if (catchTree.getLeaf().getKind() == Kind.TRY) {
173: TryTree tt = (TryTree) catchTree.getLeaf();
174: //#104085: if the statement to be wrapped is inside a finally block of the try-catch,
175: //do not attempt to extend existing catches...:
176: for (Tree t : currentPath) {
177: if (tt.getFinallyBlock() == t) {
178: createNewTryCatch = true;
179: break;
180: }
181: }
182:
183: if (!createNewTryCatch) {
184: //only add catches for uncatched exceptions:
185: new TransformerImpl(wc, thandles, streamAlike,
186: statement).scan(catchTree, null);
187: }
188: } else {
189: createNewTryCatch = true;
190: }
191:
192: if (createNewTryCatch) {
193: //find block containing this statement, if exists:
194: TreePath blockTree = currentPath;
195:
196: while (blockTree != null
197: && blockTree.getLeaf().getKind() != Kind.BLOCK)
198: blockTree = blockTree.getParentPath();
199:
200: new TransformerImpl(wc, thandles, streamAlike,
201: statement).scan(blockTree, null);
202: }
203: }
204: }).commit();
205:
206: return null;
207: }
208:
209: static boolean DISABLE_JAVA_UTIL_LOGGER = false;
210:
211: private final class TransformerImpl extends
212: TreePathScanner<Void, Void> {
213:
214: private WorkingCopy info;
215: private List<TypeMirrorHandle> thandles;
216: private boolean streamAlike;
217: private TreePath statement;
218: private TreeMaker make;
219:
220: public TransformerImpl(WorkingCopy info,
221: List<TypeMirrorHandle> thandles, boolean streamAlike,
222: TreePath statement) {
223: this .info = info;
224: this .thandles = thandles;
225: this .streamAlike = streamAlike;
226: this .statement = statement;
227: this .make = info.getTreeMaker();
228: }
229:
230: public @Override
231: Void visitTry(TryTree tt, Void p) {
232: List<CatchTree> catches = createCatches(info, make,
233: thandles, statement);
234:
235: catches.addAll(tt.getCatches());
236:
237: if (!streamAlike) {
238: info.rewrite(tt, make.Try(tt.getBlock(), catches, tt
239: .getFinallyBlock()));
240: } else {
241: VariableTree originalDeclaration = (VariableTree) statement
242: .getLeaf();
243: VariableTree declaration = make.Variable(make
244: .Modifiers(EnumSet.noneOf(Modifier.class)),
245: originalDeclaration.getName(),
246: originalDeclaration.getType(), make
247: .Literal(null));
248: StatementTree assignment = make
249: .ExpressionStatement(make.Assignment(make
250: .Identifier(originalDeclaration
251: .getName()),
252: originalDeclaration.getInitializer()));
253: List<StatementTree> finallyStatements = new ArrayList<StatementTree>(
254: tt.getFinallyBlock() != null ? tt
255: .getFinallyBlock().getStatements()
256: : Collections
257: .<StatementTree> emptyList());
258:
259: finallyStatements
260: .add(createFinallyCloseBlockStatement(originalDeclaration));
261:
262: BlockTree finallyTree = make.Block(finallyStatements,
263: false);
264:
265: info.rewrite(originalDeclaration, assignment);
266:
267: TryTree nueTry = make.Try(tt.getBlock(), catches,
268: finallyTree);
269:
270: TreePath currentBlockCandidate = statement;
271:
272: while (currentBlockCandidate.getLeaf() != tt) {
273: currentBlockCandidate = currentBlockCandidate
274: .getParentPath();
275: }
276:
277: if (currentBlockCandidate.getLeaf().getKind() == Kind.BLOCK) {
278: BlockTree originalTree = (BlockTree) currentBlockCandidate
279: .getLeaf();
280: List<StatementTree> statements = new ArrayList<StatementTree>(
281: originalTree.getStatements());
282: int index = statements.indexOf(tt);
283:
284: statements.remove(index);
285: statements.add(index, nueTry);
286: statements.add(index, declaration);
287: info.rewrite(originalTree, make.Block(statements,
288: false));
289: } else {
290: BlockTree nueBlock = make.Block(Arrays.asList(
291: declaration, nueTry), false);
292:
293: info.rewrite(tt, nueBlock);
294: }
295: }
296:
297: return null;
298: }
299:
300: private StatementTree createFinallyCloseBlockStatement(
301: VariableTree origDeclaration) {
302: Trees trees = info.getTrees();
303: TypeMirror tm = trees.getTypeMirror(statement);
304: ElementUtilities elUtils = info.getElementUtilities();
305: Iterable iterable = elUtils.getMembers(tm,
306: new ElementAcceptor() {
307: public boolean accept(Element e, TypeMirror type) {
308: return e.getKind() == ElementKind.METHOD
309: && "close".equals(e.getSimpleName()
310: .toString()); // NOI18N
311: }
312: });
313: boolean throwsIO = false;
314: for (Iterator iter = iterable.iterator(); iter.hasNext();) {
315: ExecutableElement elem = (ExecutableElement) iter
316: .next();
317: if (!elem.getParameters().isEmpty()) {
318: continue;
319: } else {
320: for (TypeMirror typeMirror : elem.getThrownTypes()) {
321: if ("java.io.IOException".equals(typeMirror
322: .toString())) { // NOI18N
323: throwsIO = true;
324: break;
325: }
326: }
327: }
328: }
329:
330: CharSequence name = origDeclaration.getName();
331: StatementTree close = make.ExpressionStatement(make
332: .MethodInvocation(Collections
333: .<ExpressionTree> emptyList(), make
334: .MemberSelect(make.Identifier(name),
335: "close"), Collections
336: .<ExpressionTree> emptyList()));
337: StatementTree result = close;
338: if (throwsIO) {
339: result = make.Try(make.Block(Collections
340: .singletonList(close), false),
341: Collections.singletonList(createCatch(info,
342: make, statement, inferName(info,
343: statement), info.getElements()
344: .getTypeElement(
345: "java.io.IOException")
346: .asType())), null);
347: }
348:
349: return result;
350: }
351:
352: private BlockTree createBlock(StatementTree... trees) {
353: List<StatementTree> statements = new LinkedList<StatementTree>();
354:
355: for (StatementTree t : trees) {
356: if (t != null) {
357: statements.add(t);
358: }
359: }
360:
361: return make.Block(statements, false);
362: }
363:
364: public @Override
365: Void visitBlock(BlockTree bt, Void p) {
366: List<CatchTree> catches = createCatches(info, make,
367: thandles, statement);
368:
369: //#89379: if inside a constructor, do not wrap the "super"/"this" call:
370: //please note that the "super" or "this" call is supposed to be always
371: //in the constructor body
372: BlockTree toUse = bt;
373: StatementTree toKeep = null;
374: Tree parent = getCurrentPath().getParentPath().getLeaf();
375:
376: if (parent.getKind() == Kind.METHOD
377: && bt.getStatements().size() > 0) {
378: MethodTree mt = (MethodTree) parent;
379:
380: if (mt.getReturnType() == null) {
381: toKeep = bt.getStatements().get(0);
382: toUse = make.Block(bt.getStatements().subList(1,
383: bt.getStatements().size()), false);
384: }
385: }
386:
387: if (!streamAlike) {
388: info.rewrite(bt, createBlock(toKeep, make.Try(toUse,
389: catches, null)));
390: } else {
391: VariableTree originalDeclaration = (VariableTree) statement
392: .getLeaf();
393: VariableTree declaration = make.Variable(make
394: .Modifiers(EnumSet.noneOf(Modifier.class)),
395: originalDeclaration.getName(),
396: originalDeclaration.getType(), make
397: .Identifier("null"));
398: StatementTree assignment = make
399: .ExpressionStatement(make.Assignment(make
400: .Identifier(originalDeclaration
401: .getName()),
402: originalDeclaration.getInitializer()));
403: BlockTree finallyTree = make
404: .Block(
405: Collections
406: .singletonList(createFinallyCloseBlockStatement(originalDeclaration)),
407: false);
408:
409: info.rewrite(originalDeclaration, assignment);
410: info.rewrite(bt, createBlock(toKeep, declaration, make
411: .Try(toUse, catches, finallyTree)));
412: }
413:
414: return null;
415: }
416:
417: }
418:
419: @Override
420: public boolean equals(Object obj) {
421: if (obj == null) {
422: return false;
423: }
424: if (getClass() != obj.getClass()) {
425: return false;
426: }
427: final MagicSurroundWithTryCatchFix other = (MagicSurroundWithTryCatchFix) obj;
428: if (this .js != other.js
429: && (this .js == null || !this .js.equals(other.js))) {
430: return false;
431: }
432: if (!this .fqns.equals(other.fqns)) {
433: return false;
434: }
435: if (this .method != other.method
436: && (this .method == null || !this .method
437: .equals(other.method))) {
438: return false;
439: }
440: return true;
441: }
442:
443: @Override
444: public int hashCode() {
445: int hash = 5;
446: hash = 23 * hash + (this .js != null ? this .js.hashCode() : 0);
447: hash = 23 * hash
448: + (this .method != null ? this .method.hashCode() : 0);
449: return hash;
450: }
451:
452: private static StatementTree createExceptionsStatement(
453: CompilationInfo info, TreeMaker make, String name) {
454: if (!ErrorFixesFakeHint.isUseExceptions()) {
455: return null;
456: }
457:
458: TypeElement exceptions = info.getElements().getTypeElement(
459: "org.openide.util.Exceptions");
460:
461: if (exceptions == null) {
462: return null;
463: }
464:
465: return make.ExpressionStatement(make.MethodInvocation(
466: Collections.<ExpressionTree> emptyList(), make
467: .MemberSelect(make.QualIdent(exceptions),
468: "printStackTrace"), Arrays.asList(make
469: .Identifier(name))));
470: }
471:
472: private static StatementTree createLogStatement(
473: CompilationInfo info, TreeMaker make, TreePath statement,
474: String name) {
475: if (!ErrorFixesFakeHint.isUseLogger()) {
476: return null;
477: }
478:
479: if (!GeneratorUtils.supportsOverride(info.getFileObject())) {
480: return null;
481: }
482:
483: TypeElement logger = info.getElements().getTypeElement(
484: "java.util.logging.Logger");
485: TypeElement level = info.getElements().getTypeElement(
486: "java.util.logging.Level");
487:
488: if (logger == null || level == null) {
489: return null;
490: }
491: // find the containing top level class
492: ClassTree containingTopLevel = null;
493: for (Tree t : statement) {
494: if (Kind.CLASS == t.getKind()) {
495: containingTopLevel = (ClassTree) t;
496: }
497: }
498: // take it easy and make it as an identfier or literal
499: ExpressionTree arg = containingTopLevel != null ? make
500: .Identifier(containingTopLevel.getSimpleName()
501: + ".class.getName()") : make.Literal("global"); // global should never happen
502:
503: // check that there isn't any Logger class imported
504: boolean useFQN = false;
505: for (ImportTree dovoz : info.getCompilationUnit().getImports()) {
506: MemberSelectTree id = (MemberSelectTree) dovoz
507: .getQualifiedIdentifier();
508: if ("Logger".equals(id.getIdentifier())
509: && !"java.util.logging.Logger"
510: .equals(id.toString())) {
511: useFQN = true;
512: }
513: }
514: // finally, make the invocation
515: ExpressionTree etExpression = make.MethodInvocation(Collections
516: .<ExpressionTree> emptyList(), make.MemberSelect(
517: useFQN ? make.Identifier(logger.toString()) : make
518: .QualIdent(logger), "getLogger"), Collections
519: .<ExpressionTree> singletonList(arg));
520: ExpressionTree levelExpression = make.MemberSelect(make
521: .QualIdent(level), "SEVERE");
522:
523: return make.ExpressionStatement(make.MethodInvocation(
524: Collections.<ExpressionTree> emptyList(), make
525: .MemberSelect(etExpression, "log"), Arrays
526: .asList(levelExpression, make.Literal(null),
527: make.Identifier(name))));
528: }
529:
530: private static StatementTree createPrintStackTraceStatement(
531: CompilationInfo info, TreeMaker make, String name) {
532: return make.ExpressionStatement(make.MethodInvocation(
533: Collections.<ExpressionTree> emptyList(), make
534: .MemberSelect(make.Identifier(name),
535: "printStackTrace"), Collections
536: .<ExpressionTree> emptyList()));
537: }
538:
539: private static CatchTree createCatch(CompilationInfo info,
540: TreeMaker make, TreePath statement, String name,
541: TypeMirror type) {
542: StatementTree logStatement = createExceptionsStatement(info,
543: make, name);
544:
545: if (logStatement == null) {
546: logStatement = createLogStatement(info, make, statement,
547: name);
548: }
549:
550: if (logStatement == null) {
551: logStatement = createPrintStackTraceStatement(info, make,
552: name);
553: }
554:
555: return make.Catch(make.Variable(make.Modifiers(EnumSet
556: .noneOf(Modifier.class)), name, make.Type(type), null),
557: make.Block(Collections.singletonList(logStatement),
558: false));
559: }
560:
561: static List<CatchTree> createCatches(CompilationInfo info,
562: TreeMaker make, List<TypeMirrorHandle> thandles,
563: TreePath currentPath) {
564: String name = inferName(info, currentPath);
565: List<CatchTree> catches = new ArrayList<CatchTree>();
566:
567: for (TypeMirrorHandle th : thandles) {
568: catches.add(createCatch(info, make, currentPath, name, th
569: .resolve(info)));
570: }
571:
572: return catches;
573: }
574:
575: private static String inferName(CompilationInfo info,
576: TreePath currentPath) {
577: Scope s = info.getTrees().getScope(currentPath);
578: Set<String> existingVariables = new HashSet<String>();
579:
580: for (Element e : info.getElementUtilities().getLocalVars(s,
581: new ElementAcceptor() {
582: public boolean accept(Element e, TypeMirror type) {
583: return e != null
584: && (e.getKind() == ElementKind.PARAMETER
585: || e.getKind() == ElementKind.LOCAL_VARIABLE || e
586: .getKind() == ElementKind.EXCEPTION_PARAMETER);
587: }
588: })) {
589: existingVariables.add(e.getSimpleName().toString());
590: }
591:
592: int index = 0;
593:
594: while (true) {
595: String proposal = "ex" + (index == 0 ? "" : ("" + index));
596:
597: if (!existingVariables.contains(proposal)) {
598: return proposal;
599: }
600:
601: index++;
602: }
603: }
604:
605: }
|