diff -r 0df446056874 java.hints/src/org/netbeans/modules/java/hints/Bundle.properties --- a/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Sat May 02 11:43:41 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Tue May 05 22:08:18 2009 +0100 @@ -263,6 +263,12 @@ ERR_SynchronizationOnNonFinalField=Synchronization on non-final field DN_SynchronizationOnNonFinalField=Synchronization on non-final field +DSC_SerialVersionUID=serialVersionUID not defined +DN_SerialVersionUID=serialVersionUID not defined +ERR_SerialVersionUID=serialVersionUID not defined +HINT_SerialVersionUID=Add default serialVersionUID +HINT_SerialVersionUID_Generated=Add generated serialVersionUID + HINT_SuspiciousCall=Suspicious call to {0}:\nExpected type {2}, actual type {1} HINT_SuspiciousCallIncompatibleTypes=Suspicious call to {0}:\nGiven object cannot contain instances of {1} (expected {2}) DN_CollectionRemove=Suspicous method call diff -r 0df446056874 java.hints/src/org/netbeans/modules/java/hints/SerialVersionUID.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/src/org/netbeans/modules/java/hints/SerialVersionUID.java Tue May 05 22:08:18 2009 +0100 @@ -0,0 +1,212 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ +package org.netbeans.modules.java.hints; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.Tree.Kind; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreePath; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import static javax.lang.model.element.Modifier.*; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.GeneratorUtilities; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.JavaSource.Phase; +import org.netbeans.api.java.source.Task; +import org.netbeans.api.java.source.TreeMaker; +import org.netbeans.api.java.source.TreePathHandle; +import org.netbeans.api.java.source.WorkingCopy; +import org.netbeans.modules.java.editor.codegen.GeneratorUtils; +import org.netbeans.modules.java.hints.spi.AbstractHint; +import org.netbeans.modules.java.hints.spi.support.FixFactory; +import org.netbeans.spi.editor.hints.ChangeInfo; +import org.netbeans.spi.editor.hints.ErrorDescription; +import org.netbeans.spi.editor.hints.ErrorDescriptionFactory; +import org.netbeans.spi.editor.hints.Fix; +import org.openide.util.NbBundle; + +/** + * @author Michal Hlavac + * @author Samuel Halliday + * + * @see RFE 70746 + * @see Original Implementation Source Code + */ +public class SerialVersionUID extends AbstractHint { + + private static final String SERIAL = "serial"; //NOI18N + private static final String SVUID = "serialVersionUID"; //NOI18N + private static final String SERIALIZABLE = "java.io.Serializable"; //NOI18N + private final AtomicBoolean cancel = new AtomicBoolean(); + + public SerialVersionUID() { + super(true, false, AbstractHint.HintSeverity.WARNING); + } + + @Override + public String getDescription() { + return NbBundle.getMessage(getClass(), "DSC_SerialVersionUID"); //NOI18N + } + + public Set getTreeKinds() { + return EnumSet.of(Kind.CLASS); + } + + public List run(CompilationInfo info, TreePath treePath) { + if (treePath == null || treePath.getLeaf().getKind() != Kind.CLASS) { + return null; + } + cancel.set(false); + TypeElement type = (TypeElement) info.getTrees().getElement(treePath); + if (type == null || type.getKind() == ElementKind.INTERFACE || !isSerializable(type) || hasSerialVersionUID(type) || hasSuppressWarning(type, SERIAL)) { + return null; + } + // Contrary to popular belief, abstract classes *should* define serialVersionUID, + // according to the documentation of Serializable. It refers to "all classes". + List fixes = new ArrayList(); + fixes.add(new FixImpl(TreePathHandle.create(treePath, info), false)); + // fixes.add(new FixImpl(TreePathHandle.create(treePath, info), true)); + fixes.addAll(FixFactory.createSuppressWarnings(info, treePath, SERIAL)); + + String desc = NbBundle.getMessage(getClass(), "ERR_SerialVersionUID"); //NOI18N + int[] span = info.getTreeUtilities().findNameSpan((ClassTree) treePath.getLeaf()); + ErrorDescription ed = ErrorDescriptionFactory.createErrorDescription(getSeverity().toEditorSeverity(), desc, fixes, info.getFileObject(), span[0], span[1]); + if (cancel.get()) { + return null; + } + return Collections.singletonList(ed); + } + + public String getId() { + return getClass().getName(); + } + + public String getDisplayName() { + return NbBundle.getMessage(getClass(), "DN_SerialVersionUID");//NOI18N + } + + public void cancel() { + cancel.set(true); + } + + private static class FixImpl implements Fix, Task { + + private final TreePathHandle handle; + private final boolean generated; + + /** + * @param handle to the CLASS + * @param generated true will insert a generated value, false will use a default + */ + public FixImpl(TreePathHandle handle, boolean generated) { + this.handle = handle; + this.generated = generated; + if (generated) { + throw new UnsupportedOperationException("TODO: implement"); + } + } + + public String getText() { + if (generated) { + return NbBundle.getMessage(getClass(), "HINT_SerialVersionUID_Generated");//NOI18N + } + return NbBundle.getMessage(getClass(), "HINT_SerialVersionUID");//NOI18N + } + + public ChangeInfo implement() throws Exception { + JavaSource js = JavaSource.forFileObject(handle.getFileObject()); + js.runModificationTask(this).commit(); + return null; + } + + public void run(WorkingCopy copy) throws Exception { + if (copy.toPhase(Phase.RESOLVED).compareTo(Phase.RESOLVED) < 0) { + return; + } + TreePath treePath = handle.resolve(copy); + if (treePath == null || treePath.getLeaf().getKind() != Kind.CLASS) { + return; + } + ClassTree classTree = (ClassTree) treePath.getLeaf(); + TreeMaker make = copy.getTreeMaker(); + + // documentation recommends private + Set modifiers = EnumSet.of(PRIVATE, STATIC, FINAL); + VariableTree svuid = make.Variable(make.Modifiers(modifiers), SVUID, make.Identifier("long"), make.Literal(1L)); //NO18N + + ClassTree decl = GeneratorUtilities.get(copy).insertClassMember(classTree, svuid); + copy.rewrite(classTree, decl); + } + } + + private boolean isSerializable(TypeElement type) { + for (TypeElement t : GeneratorUtils.getAllParents(type)) { + if (t.getKind() == ElementKind.INTERFACE && t.getQualifiedName().contentEquals(SERIALIZABLE)) { + return true; + } + } + return false; + } + + private boolean hasSerialVersionUID(TypeElement type) { + for (VariableElement e : ElementFilter.fieldsIn(type.getEnclosedElements())) { + if (e.getSimpleName().contentEquals(SVUID)) { + Set modifiers = e.getModifiers(); + // documentation says ANY-ACCESS-MODIFIER static final long serialVersionUID + if (modifiers.containsAll(EnumSet.of(STATIC, FINAL))) { + TypeMirror t = e.asType(); + if (t.getKind() != null && t.getKind() == TypeKind.LONG) { + return true; + } + } + + } + } + return false; + } + + private static boolean hasSuppressWarning(TypeElement type, String warning) { + SuppressWarnings annotation = type.getAnnotation(SuppressWarnings.class); + if (annotation != null) { + for (String val : annotation.value()) { + if (val.equals(warning)) { + return true; + } + } + } + return false; + } +} diff -r 0df446056874 java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml --- a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Sat May 02 11:43:41 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Tue May 05 22:08:18 2009 +0100 @@ -140,6 +140,7 @@ + diff -r 0df446056874 java.hints/test/unit/src/org/netbeans/modules/java/hints/SerialVersionUIDTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/src/org/netbeans/modules/java/hints/SerialVersionUIDTest.java Tue May 05 22:08:18 2009 +0100 @@ -0,0 +1,151 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ +package org.netbeans.modules.java.hints; + +import com.sun.source.util.TreePath; +import java.util.List; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.modules.java.hints.errors.SuppressWarningsFixer; +import org.netbeans.modules.java.hints.infrastructure.TreeRuleTestBase; +import org.netbeans.spi.editor.hints.ErrorDescription; +import org.netbeans.spi.editor.hints.Fix; +import org.openide.util.NbBundle; +import static org.junit.Assert.*; + +/** + * The following shell script was used to generate the code snippets + * cat test/unit/data/test/Test.java | tr '\n' ' ' | tr '\t' ' ' | sed -E 's| +| |g' | sed 's|"|\\"|g' + * @author Samuel Halliday + */ +public class SerialVersionUIDTest extends TreeRuleTestBase { + + private final SerialVersionUID computer = new SerialVersionUID(); + private static final String HINT_SUPPRESS = NbBundle.getMessage(SuppressWarningsFixer.class, "LBL_FIX_Suppress_Waning", "serial"); + private static final String HINT_DEFAULT = NbBundle.getMessage(SerialVersionUID.class, "HINT_SerialVersionUID"); + private static final String HINT_GENERATED = NbBundle.getMessage(SerialVersionUID.class, "HINT_SerialVersionUID_Generated"); + + public SerialVersionUIDTest(String name) { + super(name); + } + + public void testSerialVersionUID1() throws Exception { + String test = "package test; import java.io.Serializable; public interface T|est implements Serializable { }"; + performAnalysisTest(test); + } + + public void testSerialVersionUID2() throws Exception { + String test = "package test; import java.io.Serializable; @SuppressWarnings(\"serial\") public class T|est implements Serializable { }"; + performAnalysisTest(test); + } + + public void testSerialVersionUID3() throws Exception { + String test = "package test; import java.io.Serializable; @SuppressWarnings(\"serial\") abstract public class T|est implements Serializable { }"; + performAnalysisTest(test); + } + + public void testSerialVersionUID4() throws Exception { + String test = "package test; import java.io.Serializable; public class Te|st implements Serializable { private static final long serialVersionUID = 1L; }"; + performAnalysisTest(test); + } + + public void testSerialVersionUID5() throws Exception { + String test = "package test; import java.io.Serializable; abstract public class Te|st implements Serializable { private static final long serialVersionUID = 1L; }"; + performAnalysisTest(test); + } + + public void testSerialVersionUIDSuppress1() throws Exception { + String test = "package test; import java.io.Serializable; public class Te|st implements Serializable { }"; + String golden = "package test; import java.io.Serializable; @SuppressWarnings(\"serial\") public class Test implements Serializable { }"; + performFixTest(test, golden, HINT_SUPPRESS); + } + + public void testSerialVersionUIDSuppress2() throws Exception { + String test = "package test; import java.io.Serializable; abstract public class T|est implements Serializable { }"; + String golden = "package test; import java.io.Serializable; @SuppressWarnings(\"serial\") abstract public class Test implements Serializable { }"; + performFixTest(test, golden, HINT_SUPPRESS); + } + + public void testSerialVersionUIDDefault1() throws Exception { + String test = "package test; import java.io.Serializable; public class Te|st implements Serializable { }"; + String golden = "package test; import java.io.Serializable; public class Test implements Serializable { private static final long serialVersionUID = 1L; }"; + performFixTest(test, golden, HINT_DEFAULT); + } + + public void testSerialVersionUIDDefault2() throws Exception { + String test = "package test; import java.io.Serializable; abstract public class Te|st implements Serializable { }"; + String golden = "package test; import java.io.Serializable; abstract public class Test implements Serializable { private static final long serialVersionUID = 1L; }"; + performFixTest(test, golden, HINT_DEFAULT); + } + + // test is single line source code for test.Test, | in the CLASS, space before, space after + // golden is the output to test against + private void performFixTest(String test, String golden, String hint) throws Exception { + int offset = test.indexOf("|"); + assertTrue(offset != -1); + int end = test.indexOf(" ", offset) - 1; + assertTrue(end > 0); + int start = test.lastIndexOf(" ", offset) + 1; + assertTrue(start > 0); + performFixTest("test/Test.java", + test.replace("|", ""), + offset, + "0:" + start + "-0:" + end + ":verifier:" + NbBundle.getMessage(SerialVersionUID.class, "DSC_SerialVersionUID"), + hint, + golden); + } + + // test is single line source code for test.Test, | in the CLASS, space before, space after + // completes successfully if there are no hints presented + private void performAnalysisTest(String test) throws Exception { + int offset = test.indexOf("|"); + assertTrue(offset != -1); + performAnalysisTest("test/Test.java", test.replace("|", ""), offset); + } + + @Override + protected List computeErrors(CompilationInfo info, TreePath path) { + return computer.run(info, path); + } + + @Override + protected String toDebugString(CompilationInfo info, Fix f) { + return f.getText(); + } +// // uncomment to speed up development cycle +// @Override +// public void testIssue105979() throws Exception { +// } +// +// @Override +// public void testIssue108246() throws Exception { +// } +// +// @Override +// public void testIssue113933() throws Exception { +// } +// +// @Override +// public void testNoHintsForSimpleInitialize() throws Exception { +// } +} \ No newline at end of file