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);