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.ClassTree;
044: import com.sun.source.tree.ModifiersTree;
045: import com.sun.source.tree.NewClassTree;
046: import com.sun.source.tree.Tree;
047: import com.sun.source.tree.Tree.Kind;
048: import com.sun.source.util.TreePath;
049: import com.sun.source.util.TreePathScanner;
050: import java.io.IOException;
051: import java.util.ArrayList;
052: import java.util.Arrays;
053: import java.util.HashSet;
054: import java.util.List;
055: import java.util.Set;
056: import javax.lang.model.element.Element;
057: import javax.lang.model.element.ExecutableElement;
058: import javax.lang.model.element.Modifier;
059: import javax.lang.model.util.ElementFilter;
060: import javax.swing.text.BadLocationException;
061: import org.netbeans.api.java.source.Task;
062: import org.netbeans.api.java.source.CompilationInfo;
063: import org.netbeans.api.java.source.JavaSource;
064: import org.netbeans.api.java.source.JavaSource.Phase;
065: import org.netbeans.api.java.source.WorkingCopy;
066: import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
067: import org.netbeans.modules.java.hints.spi.ErrorRule;
068: import org.netbeans.spi.editor.hints.ChangeInfo;
069: import org.netbeans.spi.editor.hints.Fix;
070: import org.openide.util.Exceptions;
071: import org.openide.util.NbBundle;
072:
073: /**
074: *
075: * @author Jan Lahoda
076: */
077: public final class ImplementAllAbstractMethods implements
078: ErrorRule<Void> {
079:
080: /** Creates a new instance of ImplementAllAbstractMethodsCreator */
081: public ImplementAllAbstractMethods() {
082: }
083:
084: public Set<String> getCodes() {
085: return new HashSet<String>(Arrays.asList(
086: "compiler.err.abstract.cant.be.instantiated", // NOI18N
087: "compiler.err.does.not.override.abstract", // NOI18N
088: "compiler.err.abstract.cant.be.instantiated")); // NOI18N
089: }
090:
091: public List<Fix> run(final CompilationInfo info,
092: String diagnosticKey, final int offset, TreePath treePath,
093: Data<Void> data) {
094: final List<Fix> result = new ArrayList<Fix>();
095:
096: analyze(info.getJavaSource(), offset, info, new Performer() {
097: public void fixAllAbstractMethods(TreePath pathToModify,
098: Tree toModify) {
099: result.add(new FixImpl(info.getJavaSource(), offset,
100: null));
101: }
102:
103: public void makeClassAbstract(Tree toModify,
104: String className) {
105: result.add(new FixImpl(info.getJavaSource(), offset,
106: className));
107: }
108: });
109:
110: return result;
111: }
112:
113: public void cancel() {
114: //XXX: not done yet
115: }
116:
117: public String getId() {
118: return ImplementAllAbstractMethods.class.getName();
119: }
120:
121: public String getDisplayName() {
122: return NbBundle.getMessage(ImplementAllAbstractMethods.class,
123: "LBL_Impl_Abstract_Methods"); // NOI18N
124: }
125:
126: public String getDescription() {
127: return NbBundle.getMessage(ImplementAllAbstractMethods.class,
128: "DSC_Impl_Abstract_Methods"); // NOI18N
129: }
130:
131: private static interface Performer {
132:
133: public void fixAllAbstractMethods(TreePath pathToModify,
134: Tree toModify);
135:
136: public void makeClassAbstract(Tree toModify, String className);
137:
138: }
139:
140: private static void analyze(JavaSource js, int offset,
141: CompilationInfo info, Performer performer) {
142: final TreePath path = info.getTreeUtilities().pathFor(
143: offset + 1);
144: Element e = info.getTrees().getElement(path);
145: boolean isUsableElement = e != null
146: && (e.getKind().isClass() || e.getKind().isInterface());
147:
148: if (isUsableElement) {
149: //#85806: do not propose implement all abstract methods when the current class contains abstract methods:
150: for (ExecutableElement ee : ElementFilter.methodsIn(e
151: .getEnclosedElements())) {
152: if (ee.getModifiers().contains(Modifier.ABSTRACT)) {
153: performer.makeClassAbstract(path.getLeaf(), e
154: .getSimpleName().toString());
155: return;
156: }
157: }
158:
159: performer.fixAllAbstractMethods(path, path.getLeaf());
160: } else {
161: if (path.getLeaf().getKind() == Kind.NEW_CLASS) {
162: //if the parent of path.getLeaf is an error, the situation probably is like:
163: //new Runnable {}
164: //(missing '()' for constructor)
165: //do not propose the hint in this case:
166: final boolean[] parentError = new boolean[] { false };
167: new TreePathScanner() {
168: @Override
169: public Object visitNewClass(NewClassTree nct,
170: Object o) {
171: if (path.getLeaf() == nct) {
172: parentError[0] = getCurrentPath()
173: .getParentPath().getLeaf()
174: .getKind() == Kind.ERRONEOUS;
175: }
176: return super .visitNewClass(nct, o);
177: }
178: }.scan(path.getParentPath(), null);
179: if (!parentError[0]) {
180: performer.fixAllAbstractMethods(path, path
181: .getLeaf());
182: }
183: }
184: }
185: }
186:
187: private static final class FixImpl implements Fix {
188:
189: private JavaSource js;
190: private int offset;
191: private String makeClassAbstractName;
192:
193: public FixImpl(JavaSource js, int offset,
194: String makeClassAbstractName) {
195: this .js = js;
196: this .offset = offset;
197: this .makeClassAbstractName = makeClassAbstractName;
198: }
199:
200: public String getText() {
201: return makeClassAbstractName == null ? NbBundle.getMessage(
202: ImplementAllAbstractMethods.class,
203: "LBL_FIX_Impl_Abstract_Methods") : // MOI18N
204: NbBundle.getMessage(
205: ImplementAllAbstractMethods.class,
206: "LBL_FIX_Make_Class_Abstract",
207: makeClassAbstractName); // MOI18N
208: }
209:
210: public ChangeInfo implement() throws IOException {
211: final boolean[] repeat = new boolean[] { true };
212:
213: while (repeat[0]) {
214: repeat[0] = false;
215: js.runModificationTask(new Task<WorkingCopy>() {
216:
217: public void run(final WorkingCopy copy)
218: throws IOException {
219: copy.toPhase(Phase.RESOLVED);
220: analyze(js, offset, copy, new Performer() {
221: public void fixAllAbstractMethods(
222: TreePath pathToModify, Tree toModify) {
223: if (toModify.getKind() == Kind.NEW_CLASS) {
224: int insertOffset = (int) copy
225: .getTrees()
226: .getSourcePositions()
227: .getEndPosition(
228: copy
229: .getCompilationUnit(),
230: toModify);
231: if (insertOffset != (-1)) {
232: try {
233: copy
234: .getDocument()
235: .insertString(
236: insertOffset,
237: " {}", null);
238: offset = insertOffset + 1;
239: repeat[0] = true;
240: } catch (BadLocationException e) {
241: Exceptions
242: .printStackTrace(e);
243: } catch (IOException e) {
244: Exceptions
245: .printStackTrace(e);
246: }
247: }
248: } else {
249: GeneratorUtils
250: .generateAllAbstractMethodImplementations(
251: copy, pathToModify);
252: }
253: }
254:
255: public void makeClassAbstract(
256: Tree toModify, String className) {
257: //the toModify has to be a class tree:
258: if (toModify.getKind() == Kind.CLASS) {
259: ClassTree clazz = (ClassTree) toModify;
260: ModifiersTree modifiers = clazz
261: .getModifiers();
262: Set<Modifier> newModifiersSet = new HashSet<Modifier>(
263: modifiers.getFlags());
264:
265: newModifiersSet
266: .add(Modifier.ABSTRACT);
267:
268: copy
269: .rewrite(
270: modifiers,
271: copy
272: .getTreeMaker()
273: .Modifiers(
274: newModifiersSet,
275: modifiers
276: .getAnnotations()));
277: }
278: }
279: });
280: }
281: }).commit();
282: }
283: return null;
284: }
285:
286: }
287: }
|