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:
042: package org.netbeans.modules.java.hints.errors;
043:
044: import com.sun.source.tree.AssignmentTree;
045: import com.sun.source.tree.BlockTree;
046: import com.sun.source.tree.ExpressionStatementTree;
047: import com.sun.source.tree.ExpressionTree;
048: import com.sun.source.tree.IdentifierTree;
049: import com.sun.source.tree.MethodTree;
050: import com.sun.source.tree.StatementTree;
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 com.sun.source.util.TreePathScanner;
056: import java.io.IOException;
057: import java.util.Arrays;
058: import java.util.EnumSet;
059: import java.util.EnumSet;
060: import java.util.logging.Level;
061: import java.util.logging.Logger;
062: import javax.lang.model.element.Element;
063: import javax.lang.model.element.ElementKind;
064: import javax.lang.model.element.ExecutableElement;
065: import javax.lang.model.element.Modifier;
066: import javax.lang.model.element.Modifier;
067: import javax.lang.model.type.TypeKind;
068: import javax.lang.model.type.TypeMirror;
069: import org.netbeans.api.java.source.Task;
070: import org.netbeans.api.java.source.CompilationInfo;
071: import org.netbeans.api.java.source.JavaSource;
072: import org.netbeans.api.java.source.JavaSource.Phase;
073: import org.netbeans.api.java.source.TreeMaker;
074: import org.netbeans.api.java.source.TypeMirrorHandle;
075: import org.netbeans.api.java.source.WorkingCopy;
076: import org.netbeans.spi.editor.hints.ChangeInfo;
077: import org.netbeans.spi.editor.hints.Fix;
078: import org.openide.filesystems.FileObject;
079: import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider;
080: import org.openide.util.NbBundle;
081:
082: /**
083: *
084: * @author Jan Lahoda
085: */
086: public class AddParameterOrLocalFix implements Fix {
087:
088: private FileObject file;
089: private TypeMirrorHandle type;
090: private String name;
091: private boolean parameter;
092:
093: private int /*!!!Position*/unresolvedVariable;
094:
095: public AddParameterOrLocalFix(CompilationInfo info,
096: TypeMirror type, String name, boolean parameter,
097: int /*!!!Position*/unresolvedVariable) {
098: this .file = info.getFileObject();
099: if (type.getKind() == TypeKind.NULL) {
100: type = info.getElements()
101: .getTypeElement("java.lang.Object").asType(); // NOI18N
102: }
103: this .type = TypeMirrorHandle.create(type);
104: this .name = name;
105: this .parameter = parameter;
106: this .unresolvedVariable = unresolvedVariable;
107: }
108:
109: public String getText() {
110: return parameter ? NbBundle.getMessage(
111: AddParameterOrLocalFix.class,
112: "LBL_FIX_Create_Parameter", name) : // NOI18N
113: NbBundle.getMessage(AddParameterOrLocalFix.class,
114: "LBL_FIX_Create_Local_Variable", name); // NOI18N
115: }
116:
117: public ChangeInfo implement() throws IOException {
118: //use the original cp-info so it is "sure" that the proposedType can be resolved:
119: JavaSource js = JavaSource.forFileObject(file);
120:
121: js.runModificationTask(new Task<WorkingCopy>() {
122: public void run(final WorkingCopy working)
123: throws IOException {
124: working.toPhase(Phase.RESOLVED);
125:
126: TypeMirror proposedType = type.resolve(working);
127:
128: if (proposedType == null) {
129: ErrorHintsProvider.LOG.log(Level.INFO,
130: "Cannot resolve proposed type."); // NOI18N
131: return;
132: }
133:
134: TreeMaker make = working.getTreeMaker();
135: TreePath tp = working.getTreeUtilities().pathFor(
136: unresolvedVariable + 1);
137:
138: assert tp.getLeaf().getKind() == Kind.IDENTIFIER;
139:
140: TreePath targetPath = findMethod(tp);
141:
142: if (targetPath == null) {
143: ErrorHintsProvider.LOG.log(Level.INFO,
144: "Cannot resolve target method."); // NOI18N
145: return;
146: }
147:
148: MethodTree targetTree = (MethodTree) targetPath
149: .getLeaf();
150:
151: if (parameter) {
152: if (targetTree == null) {
153: Logger
154: .getLogger("global")
155: .log(Level.WARNING,
156: "Add parameter - cannot find the method."); // NOI18N
157: }
158:
159: Element el = working.getTrees().getElement(
160: targetPath);
161: int index = targetTree.getParameters().size();
162:
163: if (el != null
164: && (el.getKind() == ElementKind.METHOD || el
165: .getKind() == ElementKind.CONSTRUCTOR)) {
166: ExecutableElement ee = (ExecutableElement) el;
167:
168: if (ee.isVarArgs()) {
169: index = ee.getParameters().size() - 1;
170: }
171: }
172:
173: MethodTree result = make.insertMethodParameter(
174: targetTree, index, make
175: .Variable(make.Modifiers(EnumSet
176: .noneOf(Modifier.class)),
177: name,
178: make.Type(proposedType),
179: null));
180:
181: working.rewrite(targetTree, result);
182: } else {
183: if (ErrorFixesFakeHint
184: .isCreateLocalVariableInPlace()) {
185: resolveLocalVariable(working, tp, make,
186: proposedType);
187: } else {
188: resolveLocalVariable55(working, tp, make,
189: proposedType);
190: }
191: }
192: }
193: }).commit();
194:
195: return null;
196: }
197:
198: private void resolveLocalVariable55(final WorkingCopy wc,
199: TreePath tp, TreeMaker make, TypeMirror proposedType) {
200: final String name = ((IdentifierTree) tp.getLeaf()).getName()
201: .toString();
202:
203: //find first usage of this (undeclared) variable:
204: TreePath method = findMethod(tp);
205:
206: if (method == null) {
207: //TODO: probably initializer handle differently
208: return;
209: }
210:
211: int index = 0;
212: MethodTree methodTree = (MethodTree) method.getLeaf();
213: BlockTree block = methodTree.getBody();
214:
215: if (methodTree.getReturnType() == null
216: && !block.getStatements().isEmpty()) {
217: StatementTree stat = block.getStatements().get(0);
218:
219: if (stat.getKind() == Kind.EXPRESSION_STATEMENT) {
220: Element this MethodEl = wc.getTrees().getElement(method);
221: TreePath pathToFirst = new TreePath(new TreePath(
222: new TreePath(method, block), stat),
223: ((ExpressionStatementTree) stat)
224: .getExpression());
225: Element super Call = wc.getTrees().getElement(
226: pathToFirst);
227:
228: if (this MethodEl != null
229: && super Call != null
230: && this MethodEl.getKind() == ElementKind.CONSTRUCTOR
231: && super Call.getKind() == ElementKind.CONSTRUCTOR) {
232: index = 1;
233: }
234: }
235: }
236:
237: VariableTree vt = make.Variable(make.Modifiers(EnumSet
238: .noneOf(Modifier.class)), name,
239: make.Type(proposedType), null);
240:
241: wc.rewrite(block, wc.getTreeMaker().insertBlockStatement(block,
242: index, vt));
243: }
244:
245: private void resolveLocalVariable(final WorkingCopy wc,
246: TreePath tp, TreeMaker make, TypeMirror proposedType) {
247: final String name = ((IdentifierTree) tp.getLeaf()).getName()
248: .toString();
249:
250: final Element el = wc.getTrees().getElement(tp);
251:
252: //find first usage of this (undeclared) variable:
253: TreePath method = findMethod(tp);
254:
255: if (method == null) {
256: //TODO: probably initializer handle differently
257: return;
258: }
259:
260: class FirstUsage extends TreePathScanner<TreePath, Void> {
261: private TreePath found;
262:
263: public @Override
264: TreePath visitIdentifier(IdentifierTree tree, Void v) {
265: if (tree.getName().contentEquals(el.getSimpleName())) {
266: if (found == null) {
267: found = getCurrentPath();
268: }
269: return findStatement(getCurrentPath());
270: }
271: return null;
272: }
273:
274: public @Override
275: TreePath visitBlock(BlockTree tree, Void v) {
276: TreePath result = null;
277: TreePath firstBranchStatementWithUsage = null;
278: for (StatementTree t : tree.getStatements()) {
279: TreePath currentResult = scan(t, null);
280:
281: if (currentResult != null && result == null) {
282: result = currentResult;
283: firstBranchStatementWithUsage = new TreePath(
284: getCurrentPath(), t);
285: }
286:
287: if (currentResult != t
288: && result != null
289: && result.getLeaf() != firstBranchStatementWithUsage
290: .getLeaf()) {
291: //ie.: { x = 1; } ... { x = 1; }
292: result = firstBranchStatementWithUsage;
293: }
294: }
295: super .visitBlock(tree, v);
296: return result;
297: }
298:
299: public @Override
300: TreePath reduce(TreePath tp1, TreePath tp2) {
301: if (tp2 == null)
302: return tp1;
303:
304: return tp2;
305:
306: }
307: }
308:
309: FirstUsage firstUsage = new FirstUsage();
310: TreePath firstUse = firstUsage.scan(method, null);
311:
312: if (firstUse == null || !isStatement(firstUse.getLeaf())) {
313: Logger.getLogger("global").log(Level.WARNING,
314: "Add local variable - cannot find a statement."); // NOI18N
315: return;
316: }
317:
318: StatementTree statement = (StatementTree) firstUse.getLeaf();
319:
320: if (statement.getKind() == Kind.EXPRESSION_STATEMENT) {
321: ExpressionTree exp = ((ExpressionStatementTree) statement)
322: .getExpression();
323:
324: if (exp.getKind() == Kind.ASSIGNMENT) {
325: //replace the expression statement with a variable declaration:
326: AssignmentTree at = (AssignmentTree) exp;
327: VariableTree vt = make.Variable(make.Modifiers(EnumSet
328: .noneOf(Modifier.class)), name, make
329: .Type(proposedType), at.getExpression());
330:
331: vt = Utilities.copyComments(wc, statement, vt);
332:
333: wc.rewrite(statement, vt);
334:
335: return;
336: }
337: }
338:
339: Tree statementParent = firstUse.getParentPath().getLeaf();
340: VariableTree vt = make.Variable(make.Modifiers(EnumSet
341: .noneOf(Modifier.class)), name,
342: make.Type(proposedType), null);
343:
344: if (statementParent.getKind() == Kind.BLOCK) {
345: BlockTree block = (BlockTree) statementParent;
346: BlockTree nueBlock = make.insertBlockStatement(block, block
347: .getStatements().indexOf(statement), vt);
348:
349: wc.rewrite(block, nueBlock);
350: } else {
351: BlockTree block = make.Block(Arrays.asList(vt, statement),
352: false);
353:
354: wc.rewrite(statement, block);
355: }
356: }
357:
358: private TreePath findStatement(TreePath tp) {
359: TreePath statement = tp;
360:
361: while (statement.getLeaf().getKind() != Kind.COMPILATION_UNIT) {
362: if (isStatement(statement.getLeaf())) {
363: return statement;
364: }
365:
366: statement = statement.getParentPath();
367: }
368:
369: return null;
370: }
371:
372: private TreePath findMethod(TreePath tp) {
373: TreePath method = tp;
374:
375: while (method != null) {
376: if (method.getLeaf().getKind() == Kind.METHOD) {
377: return method;
378: }
379:
380: method = method.getParentPath();
381: }
382:
383: return null;
384: }
385:
386: private boolean isStatement(Tree t) {
387: Class intClass = t.getKind().asInterface();
388:
389: return StatementTree.class.isAssignableFrom(intClass);
390: }
391:
392: String toDebugString(CompilationInfo info) {
393: return "AddParameterOrLocalFix:" + name + ":"
394: + type.resolve(info).toString() + ":" + parameter; // NOI18N
395: }
396:
397: boolean isParameter() {
398: return parameter;
399: }
400:
401: @Override
402: public boolean equals(Object obj) {
403: if (obj == null) {
404: return false;
405: }
406: if (getClass() != obj.getClass()) {
407: return false;
408: }
409: final AddParameterOrLocalFix other = (AddParameterOrLocalFix) obj;
410: if (this .name != other.name
411: && (this .name == null || !this .name.equals(other.name))) {
412: return false;
413: }
414: if (this .parameter != other.parameter) {
415: return false;
416: }
417:
418: return true;
419: }
420:
421: @Override
422: public int hashCode() {
423: int hash = 7;
424: hash = 97 * hash
425: + (this .name != null ? this .name.hashCode() : 0);
426: hash = 97 * hash + (this .parameter ? 1 : 0);
427: return hash;
428: }
429:
430: }
|