Bug 569847 - [AutoRefactor immigration #51/146] [cleanup & saveaction]
Static inner class
Change-Id: I6c93d06ac53003fed1989e65237e179ed3fdf159
Signed-off-by: Fabrice Tiercelin <fabrice.tiercelin@yahoo.fr>
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
index a324ede..78dee43 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.java
@@ -124,6 +124,7 @@
public static String NullAnnotationsCleanUp_remove_redundant_nullness_annotation;
public static String BreakLoopCleanUp_description;
+ public static String StaticInnerClassCleanUp_description;
public static String StringBuilderCleanUp_description;
public static String CodeStyleCleanUp_LazyLogical_description;
public static String PrimitiveSerializationCleanUp_description;
diff --git a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
index 9a6ee96..7109282 100644
--- a/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
+++ b/org.eclipse.jdt.core.manipulation/common/org/eclipse/jdt/internal/ui/fix/MultiFixMessages.properties
@@ -106,6 +106,7 @@
NullAnnotationsCleanUp_remove_redundant_nullness_annotation=Remove redundant nullness annotation
BreakLoopCleanUp_description=Exit loop earlier
+StaticInnerClassCleanUp_description=Make inner classes static where possible
StringBuilderCleanUp_description=Replace String concatenation by StringBuilder
CodeStyleCleanUp_LazyLogical_description=Use lazy logical operator (&& and ||)
PrimitiveSerializationCleanUp_description=Primitive serialization
diff --git a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
index 25da27c..3b772ae 100644
--- a/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
+++ b/org.eclipse.jdt.core.manipulation/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstants.java
@@ -985,6 +985,18 @@
public static final String BREAK_LOOP= "cleanup.break_loop"; //$NON-NLS-1$
/**
+ * Make inner <code>class</code> static.
+ * <p>
+ * Possible values: {TRUE, FALSE}
+ * <p>
+ *
+ * @see CleanUpOptionsCore#TRUE
+ * @see CleanUpOptionsCore#FALSE
+ * @since 4.19
+ */
+ public static final String STATIC_INNER_CLASS= "cleanup.static_inner_class"; //$NON-NLS-1$
+
+ /**
* Replaces String concatenation by StringBuilder when possible.
* <p>
* Possible values: {TRUE, FALSE}
diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
index 8158980..8389584 100644
--- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
+++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/CleanUpTest.java
@@ -11932,6 +11932,369 @@
}
@Test
+ public void testStaticInnerClass() throws Exception {
+ // Given
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String given= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import static java.lang.Integer.bitCount;\n" //
+ + "\n" //
+ + "import java.io.File;\n" //
+ + "import java.util.Arrays;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public class RefactorThisInnerClass {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorThisInnerClassThatAccessesField {\n" //
+ + " File picture;\n" //
+ + "\n" //
+ + " public char anotherMethod() {\n" //
+ + " return picture.separatorChar;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorThisInnerClassThatUsesStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return CONSTANT != null;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorThisInnerClassThatUsesQualifiedStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return E.CONSTANT != null;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorThisInnerClassThatUsesFullyQualifiedStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return test1.E.CONSTANT != null;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorInnerClassThatOnlyUsesItsFields {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return i == 0;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorInnerClassThatUsesStaticMethod {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return aStaticMethod();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public final class RefactorThisFinalInnerClass {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " class RefactorThisInnerClassWithoutModifier {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " @Deprecated\n" //
+ + " class RefactorThisInnerClassWithAnnotation {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorInnerClassThatUsesStaticImport {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public int anotherMethod() {\n" //
+ + " return bitCount(0);\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorInnerClassThatUsesStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public char anotherMethod() {\n" //
+ + " return File.separatorChar;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorInheritedInnerClass extends File {\n" //
+ + " private static final long serialVersionUID = -1124849036813595100L;\n" //
+ + " private int i;\n" //
+ + "\n" //
+ + " public RefactorInheritedInnerClass(File arg0, String arg1) {\n" //
+ + " super(arg0, arg1);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class RefactorGenericInnerClass<T> {\n" //
+ + " T i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final String CONSTANT= \"foo\";\n" //
+ + "\n" //
+ + " private String aString= \"bar\";\n" //
+ + "\n" //
+ + " public static boolean aStaticMethod() {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean aMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+
+ String expected= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "import static java.lang.Integer.bitCount;\n" //
+ + "\n" //
+ + "import java.io.File;\n" //
+ + "import java.util.Arrays;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public static class RefactorThisInnerClass {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorThisInnerClassThatAccessesField {\n" //
+ + " File picture;\n" //
+ + "\n" //
+ + " public char anotherMethod() {\n" //
+ + " return picture.separatorChar;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorThisInnerClassThatUsesStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return CONSTANT != null;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorThisInnerClassThatUsesQualifiedStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return E.CONSTANT != null;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorThisInnerClassThatUsesFullyQualifiedStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return test1.E.CONSTANT != null;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorInnerClassThatOnlyUsesItsFields {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return i == 0;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorInnerClassThatUsesStaticMethod {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return aStaticMethod();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static final class RefactorThisFinalInnerClass {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " static class RefactorThisInnerClassWithoutModifier {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " @Deprecated\n" //
+ + " static\n" //
+ + " class RefactorThisInnerClassWithAnnotation {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorInnerClassThatUsesStaticImport {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public int anotherMethod() {\n" //
+ + " return bitCount(0);\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorInnerClassThatUsesStaticField {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public char anotherMethod() {\n" //
+ + " return File.separatorChar;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorInheritedInnerClass extends File {\n" //
+ + " private static final long serialVersionUID = -1124849036813595100L;\n" //
+ + " private int i;\n" //
+ + "\n" //
+ + " public RefactorInheritedInnerClass(File arg0, String arg1) {\n" //
+ + " super(arg0, arg1);\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class RefactorGenericInnerClass<T> {\n" //
+ + " T i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final String CONSTANT= \"foo\";\n" //
+ + "\n" //
+ + " private String aString= \"bar\";\n" //
+ + "\n" //
+ + " public static boolean aStaticMethod() {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean aMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+
+ // When
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", given, false, null);
+ enable(CleanUpConstants.STATIC_INNER_CLASS);
+
+ // Then
+ assertNotEquals("The class must be changed", given, expected);
+ assertGroupCategoryUsed(new ICompilationUnit[] { cu }, new HashSet<>(Arrays.asList(MultiFixMessages.StaticInnerClassCleanUp_description)));
+ assertRefactoringResultAsExpected(new ICompilationUnit[] { cu }, new String[] { expected });
+ }
+
+ @Test
+ public void testDoNotUseStaticInnerClass() throws Exception {
+ IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
+ String sample= "" //
+ + "package test1;\n" //
+ + "\n" //
+ + "public class E {\n" //
+ + " public interface DoNotRefactorInnerInterface {\n" //
+ + " boolean anotherMethod();\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class DoNotRefactorThisInnerClass {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return aString != null;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class DoNotRefactorInnerClassThatUsesMethod {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return aMethod();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public static class DoNotRefactorAlreadyStaticInnerClass {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public class NotStaticClass {\n" //
+ + " public class DoNotRefactorInnerClassInNotStaticClass {\n" //
+ + " int i;\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean anotherMethod() {\n" //
+ + " return aMethod();\n" //
+ + " }\n" //
+ + " }\n" //
+ + "\n" //
+ + " private static final String CONSTANT= \"foo\";\n" //
+ + "\n" //
+ + " private String aString= \"bar\";\n" //
+ + "\n" //
+ + " public static boolean aStaticMethod() {\n" //
+ + " return false;\n" //
+ + " }\n" //
+ + "\n" //
+ + " public boolean aMethod() {\n" //
+ + " return true;\n" //
+ + " }\n" //
+ + "}\n";
+ ICompilationUnit cu= pack.createCompilationUnit("E.java", sample, false, null);
+
+ enable(CleanUpConstants.STATIC_INNER_CLASS);
+
+ assertRefactoringHasNoChange(new ICompilationUnit[] { cu });
+ }
+
+ @Test
public void testStringBuilder() throws Exception {
IPackageFragment pack= fSourceFolder.createPackageFragment("test1", false, null);
String given= "" //
diff --git a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
index fd0cba2..0964eee 100644
--- a/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
+++ b/org.eclipse.jdt.ui/core extension/org/eclipse/jdt/internal/corext/fix/CleanUpConstantsOptions.java
@@ -77,6 +77,7 @@
// Optimization
options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
+ options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
options.setOption(PRIMITIVE_SERIALIZATION, CleanUpOptions.FALSE);
@@ -220,6 +221,7 @@
// Optimization
options.setOption(BREAK_LOOP, CleanUpOptions.FALSE);
+ options.setOption(STATIC_INNER_CLASS, CleanUpOptions.FALSE);
options.setOption(STRINGBUILDER, CleanUpOptions.FALSE);
options.setOption(USE_LAZY_LOGICAL_OPERATOR, CleanUpOptions.FALSE);
options.setOption(PRIMITIVE_SERIALIZATION, CleanUpOptions.FALSE);
diff --git a/org.eclipse.jdt.ui/plugin.xml b/org.eclipse.jdt.ui/plugin.xml
index d2597cd..3a30bea 100644
--- a/org.eclipse.jdt.ui/plugin.xml
+++ b/org.eclipse.jdt.ui/plugin.xml
@@ -7107,9 +7107,14 @@
runAfter="org.eclipse.jdt.ui.cleanup.number_suffix">
</cleanUp>
<cleanUp
+ class="org.eclipse.jdt.internal.ui.fix.StaticInnerClassCleanUp"
+ id="org.eclipse.jdt.ui.cleanup.static_inner_class"
+ runAfter="org.eclipse.jdt.ui.cleanup.break_loop">
+ </cleanUp>
+ <cleanUp
class="org.eclipse.jdt.internal.ui.fix.StringBuilderCleanUp"
id="org.eclipse.jdt.ui.cleanup.stringbuilder"
- runAfter="org.eclipse.jdt.ui.cleanup.break_loop">
+ runAfter="org.eclipse.jdt.ui.cleanup.static_inner_class">
</cleanUp>
<cleanUp
class="org.eclipse.jdt.internal.ui.fix.LazyLogicalCleanUp"
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/StaticInnerClassCleanUp.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/StaticInnerClassCleanUp.java
new file mode 100644
index 0000000..e05c4ca
--- /dev/null
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/fix/StaticInnerClassCleanUp.java
@@ -0,0 +1,236 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Fabrice TIERCELIN and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Fabrice TIERCELIN - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.internal.ui.fix;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.text.edits.TextEditGroup;
+
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.FieldAccess;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IExtendedModifier;
+import org.eclipse.jdt.core.dom.Modifier;
+import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
+import org.eclipse.jdt.core.dom.QualifiedName;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.SuperFieldAccess;
+import org.eclipse.jdt.core.dom.TypeDeclaration;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
+
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.InterruptibleVisitor;
+import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix;
+import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation;
+import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
+import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
+
+import org.eclipse.jdt.ui.cleanup.CleanUpRequirements;
+import org.eclipse.jdt.ui.cleanup.ICleanUpFix;
+import org.eclipse.jdt.ui.text.java.IProblemLocation;
+
+/**
+ * A fix that makes inner <code>class</code> static:
+ * <ul>
+ * <li>It should not use top level <code>class</code> members</li>
+ * </ul>
+ */
+public class StaticInnerClassCleanUp extends AbstractMultiFix {
+ public StaticInnerClassCleanUp() {
+ this(Collections.emptyMap());
+ }
+
+ public StaticInnerClassCleanUp(final Map<String, String> options) {
+ super(options);
+ }
+
+ @Override
+ public CleanUpRequirements getRequirements() {
+ boolean requireAST= isEnabled(CleanUpConstants.STATIC_INNER_CLASS);
+ return new CleanUpRequirements(requireAST, false, false, null);
+ }
+
+ @Override
+ public String[] getStepDescriptions() {
+ if (isEnabled(CleanUpConstants.STATIC_INNER_CLASS)) {
+ return new String[] { MultiFixMessages.StaticInnerClassCleanUp_description };
+ }
+
+ return new String[0];
+ }
+
+ @Override
+ public String getPreview() {
+ StringBuilder bld= new StringBuilder();
+ if (isEnabled(CleanUpConstants.STATIC_INNER_CLASS)) {
+ bld.append("public static class InnerClass {\n"); //$NON-NLS-1$
+ } else {
+ bld.append("public class InnerClass {\n"); //$NON-NLS-1$
+ }
+ bld.append(" int i;\n"); //$NON-NLS-1$
+ bld.append("\n"); //$NON-NLS-1$
+ bld.append(" public boolean anotherMethod() {\n"); //$NON-NLS-1$
+ bld.append(" return true;\n"); //$NON-NLS-1$
+ bld.append(" }\n"); //$NON-NLS-1$
+ bld.append("}\n"); //$NON-NLS-1$
+
+ return bld.toString();
+ }
+
+ @Override
+ protected ICleanUpFix createFix(final CompilationUnit unit) throws CoreException {
+ if (!isEnabled(CleanUpConstants.STATIC_INNER_CLASS)) {
+ return null;
+ }
+
+ final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>();
+
+ unit.accept(new ASTVisitor() {
+ class TopLevelClassMemberVisitor extends InterruptibleVisitor {
+ private final TypeDeclaration innerClass;
+ private boolean isTopLevelClassMemberUsed;
+
+ public TopLevelClassMemberVisitor(final TypeDeclaration innerClass) {
+ this.innerClass= innerClass;
+ }
+
+ public boolean isTopLevelClassMemberUsed() {
+ return isTopLevelClassMemberUsed;
+ }
+
+ @Override
+ public boolean visit(final SimpleName node) {
+ if (innerClass.getName() == node
+ || node.getLocationInParent() == QualifiedName.NAME_PROPERTY
+ || node.getLocationInParent() == FieldAccess.NAME_PROPERTY
+ || node.getLocationInParent() == SuperFieldAccess.NAME_PROPERTY) {
+ return true;
+ }
+
+ IBinding binding= node.resolveBinding();
+ ASTNode root= node.getRoot();
+
+ if (binding == null || !(root instanceof CompilationUnit)) {
+ isTopLevelClassMemberUsed= true;
+ return interruptVisit();
+ }
+
+ if (!Modifier.isStatic(binding.getModifiers())
+ && binding.getKind() != IBinding.ANNOTATION
+ && binding.getKind() != IBinding.MEMBER_VALUE_PAIR
+ && binding.getKind() != IBinding.MODULE
+ && binding.getKind() != IBinding.PACKAGE
+ && binding.getKind() != IBinding.TYPE) {
+ ASTNode declaration= ((CompilationUnit) root).findDeclaringNode(binding);
+
+ if (!ASTNodes.isParent(declaration, innerClass)) {
+ isTopLevelClassMemberUsed= true;
+ return interruptVisit();
+ }
+ }
+
+ return true;
+ }
+ }
+
+ @Override
+ public boolean visit(final TypeDeclaration visited) {
+ if (!visited.isInterface()) {
+ TypeDeclaration parent= ASTNodes.getTypedAncestor(visited, TypeDeclaration.class);
+ TypeDeclaration topLevelClass= null;
+
+ while (parent != null) {
+ topLevelClass= parent;
+ parent= ASTNodes.getTypedAncestor(topLevelClass, TypeDeclaration.class);
+
+ if (parent != null && !Modifier.isStatic(topLevelClass.getModifiers())) {
+ return true;
+ }
+ }
+
+ if (topLevelClass != null && !Modifier.isStatic(visited.getModifiers())) {
+ TopLevelClassMemberVisitor topLevelClassMemberVisitor= new TopLevelClassMemberVisitor(visited);
+ topLevelClassMemberVisitor.traverseNodeInterruptibly(visited);
+
+ if (!topLevelClassMemberVisitor.isTopLevelClassMemberUsed()) {
+ rewriteOperations.add(new StaticInnerClassOperation(visited));
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ });
+
+ if (rewriteOperations.isEmpty()) {
+ return null;
+ }
+
+ return new CompilationUnitRewriteOperationsFix(MultiFixMessages.StaticInnerClassCleanUp_description, unit,
+ rewriteOperations.toArray(new CompilationUnitRewriteOperation[0]));
+ }
+
+ @Override
+ public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) {
+ return false;
+ }
+
+ @Override
+ protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException {
+ return null;
+ }
+
+ private static class StaticInnerClassOperation extends CompilationUnitRewriteOperation {
+ private final TypeDeclaration visited;
+
+ public StaticInnerClassOperation(final TypeDeclaration visited) {
+ this.visited= visited;
+ }
+
+ @Override
+ public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException {
+ ASTRewrite rewrite= cuRewrite.getASTRewrite();
+ ListRewrite listRewrite= rewrite.getListRewrite(visited, TypeDeclaration.MODIFIERS2_PROPERTY);
+ AST ast= cuRewrite.getRoot().getAST();
+ TextEditGroup group= createTextEditGroup(MultiFixMessages.StaticInnerClassCleanUp_description, cuRewrite);
+
+ List<?> modifiers= visited.modifiers();
+ Modifier static0= ast.newModifier(ModifierKeyword.STATIC_KEYWORD);
+
+ if (modifiers.isEmpty()) {
+ listRewrite.insertFirst(static0, group);
+ } else {
+ IExtendedModifier lastModifier= (IExtendedModifier) modifiers.get(modifiers.size() - 1);
+
+ if (lastModifier.isModifier() && ((Modifier) lastModifier).isFinal()) {
+ listRewrite.insertBefore(static0, (ASTNode) lastModifier, group);
+ } else {
+ listRewrite.insertLast(static0, group);
+ }
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
index 7d51e3c..506b097 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.java
@@ -82,6 +82,7 @@
public static String OptimizationTabPage_GroupName_Optimization;
public static String OptimizationTabPage_CheckboxName_BreakLoop;
+ public static String OptimizationTabPage_CheckboxName_StaticInnerClass;
public static String OptimizationTabPage_CheckboxName_StringBuilder;
public static String OptimizationTabPage_CheckboxName_UseLazyLogicalOperator;
public static String OptimizationTabPage_CheckboxName_PrimitiveSerialization;
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
index 6cbdf24..8cf3e53 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/CleanUpMessages.properties
@@ -61,6 +61,7 @@
OptimizationTabPage_GroupName_Optimization=Optimization
OptimizationTabPage_CheckboxName_BreakLoop=Exit &loop earlier
+OptimizationTabPage_CheckboxName_StaticInnerClass=Make inner classes static where possible
OptimizationTabPage_CheckboxName_StringBuilder=Replace &String concatenation by StringBuilder
OptimizationTabPage_CheckboxName_UseLazyLogicalOperator=Use la&zy logical operator
OptimizationTabPage_CheckboxName_PrimitiveSerialization=&Primitive serialization
diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
index de8a198..9d258ec 100644
--- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
+++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/cleanup/OptimizationTabPage.java
@@ -27,6 +27,7 @@
import org.eclipse.jdt.internal.ui.fix.NoStringCreationCleanUp;
import org.eclipse.jdt.internal.ui.fix.PatternCleanUp;
import org.eclipse.jdt.internal.ui.fix.PrimitiveSerializationCleanUp;
+import org.eclipse.jdt.internal.ui.fix.StaticInnerClassCleanUp;
import org.eclipse.jdt.internal.ui.fix.StringBuilderCleanUp;
public final class OptimizationTabPage extends AbstractCleanUpTabPage {
@@ -36,6 +37,7 @@
protected AbstractCleanUp[] createPreviewCleanUps(Map<String, String> values) {
return new AbstractCleanUp[] {
new BreakLoopCleanUp(values),
+ new StaticInnerClassCleanUp(values),
new StringBuilderCleanUp(values),
new LazyLogicalCleanUp(values),
new PrimitiveSerializationCleanUp(values),
@@ -52,6 +54,9 @@
final CheckboxPreference breakLoopPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_BreakLoop, CleanUpConstants.BREAK_LOOP, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(breakLoopPref);
+ final CheckboxPreference staticInnerClassPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_StaticInnerClass, CleanUpConstants.STATIC_INNER_CLASS, CleanUpModifyDialog.FALSE_TRUE);
+ registerPreference(staticInnerClassPref);
+
final CheckboxPreference stringBuilderPref= createCheckboxPref(optimizationGroup, numColumns, CleanUpMessages.OptimizationTabPage_CheckboxName_StringBuilder, CleanUpConstants.STRINGBUILDER, CleanUpModifyDialog.FALSE_TRUE);
registerPreference(stringBuilderPref);