Skip to content

Commit 777d0b7

Browse files
committed
Finalized JDT import organizer wrapper and testing.
1 parent 43eea75 commit 777d0b7

File tree

12 files changed

+210
-55
lines changed

12 files changed

+210
-55
lines changed

_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImpl.java

+16-17
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,28 @@
3131
import org.eclipse.jdt.core.manipulation.JavaManipulation;
3232
import org.eclipse.jdt.core.manipulation.OrganizeImportsOperation;
3333
import org.eclipse.jdt.core.manipulation.SharedASTProviderCore;
34-
import org.eclipse.jdt.internal.core.JavaCorePreferenceInitializer;
3534
import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettingsConstants;
3635

3736
import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework;
3837

3938
/** Clean-up step which calls out to the Eclipse JDT clean-up / import sorter. */
4039
public class EclipseJdtCleanUpStepImpl {
4140

42-
/* The JDT UI shall be used for creating the settings. */
41+
// The JDT UI shall be used for creating the settings.
4342
private final static String JDT_UI_PLUGIN_ID = "org.eclipse.jdt.ui";
44-
45-
private final IJavaProject jdtConfiguration;
43+
private final IJavaProject jdtConfiguration; //The project stores the JDT clean-up configuration
44+
private final EclipseJdtHelper jdtHelper;
4645

4746
public EclipseJdtCleanUpStepImpl(Properties settings) throws Exception {
4847
if (SpotlessEclipseFramework.setup(
4948
core -> {
5049
/*
51-
* For the Clean-Up, the indexer needs to exists (but is not used).
52-
* The indexer is not created in headless mode by the JDT.
53-
* To signal a non-headless mode, the platform state needs to by active
54-
* (it is only resolved by default).
50+
* Indexer needs to exist (but is not used) for JDT clean-up.
51+
* The indexer is not created in headless mode by JDT.
52+
* 'Active' platform state signals non-headless mode ('Resolved' is default state)..
5553
*/
5654
core.add(new org.eclipse.core.internal.registry.osgi.Activator());
55+
5756
core.add(new org.eclipse.core.internal.runtime.PlatformActivator());
5857
core.add(new org.eclipse.core.internal.preferences.Activator());
5958
core.add(new org.eclipse.core.internal.runtime.Activator());
@@ -66,13 +65,12 @@ public EclipseJdtCleanUpStepImpl(Properties settings) throws Exception {
6665
config.changeSystemLineSeparator();
6766

6867
/*
69-
* The default no content type specific handling is insufficient.
68+
* The default 'no content type specific handling' is insufficient.
7069
* The Java source type needs to be recognized by file extension.
7170
*/
7271
config.add(IContentTypeManager.class, new JavaContentTypeManager());
73-
config.useSlf4J(EclipseJdtCleanUpStepImpl.class.getPackage().getName());
7472

75-
//Initialization of jdtConfiguration requires OS set
73+
config.useSlf4J(EclipseJdtCleanUpStepImpl.class.getPackage().getName());
7674
config.set(InternalPlatform.PROP_OS, "");
7775
},
7876
plugins -> {
@@ -82,13 +80,14 @@ public EclipseJdtCleanUpStepImpl(Properties settings) throws Exception {
8280
plugins.add(new org.eclipse.core.internal.filesystem.Activator());
8381
plugins.add(new JavaCore());
8482
})) {
85-
new JavaCorePreferenceInitializer().initializeDefaultPreferences();
8683
initializeJdtUiDefaultSettings();
8784
}
88-
jdtConfiguration = EclipseJdtFactory.createProject(settings);
85+
jdtHelper = EclipseJdtHelper.getInstance();
86+
jdtConfiguration = jdtHelper.createProject(settings);
8987
}
9088

9189
private static void initializeJdtUiDefaultSettings() {
90+
//Following values correspond org.eclipse.jdt.ui.PreferenceConstants
9291
JavaManipulation.setPreferenceNodeId(JDT_UI_PLUGIN_ID);
9392
IEclipsePreferences prefs = DefaultScope.INSTANCE.getNode(JDT_UI_PLUGIN_ID);
9493

@@ -103,12 +102,12 @@ private static void initializeJdtUiDefaultSettings() {
103102
}
104103

105104
public String organizeImport(String raw) throws Exception {
106-
ICompilationUnit sourceContainer = EclipseJdtFactory.createJavaSource(raw, jdtConfiguration);
107-
CompilationUnit ast = SharedASTProviderCore.getAST(sourceContainer, SharedASTProviderCore.WAIT_YES, null);
108-
OrganizeImportsOperation formatOperation = new OrganizeImportsOperation(sourceContainer, ast, true, false, true, null);
105+
ICompilationUnit compilationUnit = jdtHelper.createCompilationUnit(raw, jdtConfiguration);
106+
CompilationUnit ast = SharedASTProviderCore.getAST(compilationUnit, SharedASTProviderCore.WAIT_YES, null);
107+
OrganizeImportsOperation formatOperation = new OrganizeImportsOperation(compilationUnit, ast, true, false, true, null);
109108
try {
110109
formatOperation.run(null);
111-
return sourceContainer.getSource();
110+
return compilationUnit.getSource();
112111
} catch (OperationCanceledException | CoreException e) {
113112
throw new IllegalArgumentException("Invalid java syntax for formatting.", e);
114113
}

_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFactory.java _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtHelper.java

+57-32
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
*/
1616
package com.diffplug.spotless.extra.eclipse.java;
1717

18-
import java.util.Collections;
1918
import java.util.HashMap;
20-
import java.util.Hashtable;
2119
import java.util.List;
2220
import java.util.Map;
2321
import java.util.Properties;
@@ -27,21 +25,24 @@
2725
import org.eclipse.core.resources.IFolder;
2826
import org.eclipse.core.resources.IProject;
2927
import org.eclipse.core.resources.IProjectDescription;
28+
import org.eclipse.core.resources.ProjectScope;
3029
import org.eclipse.core.resources.ResourcesPlugin;
3130
import org.eclipse.core.runtime.IProgressMonitor;
31+
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
3232
import org.eclipse.jdt.core.IBuffer;
3333
import org.eclipse.jdt.core.ICompilationUnit;
3434
import org.eclipse.jdt.core.IJavaProject;
3535
import org.eclipse.jdt.core.IPackageFragment;
3636
import org.eclipse.jdt.core.IPackageFragmentRoot;
37-
import org.eclipse.jdt.core.IType;
3837
import org.eclipse.jdt.core.JavaCore;
3938
import org.eclipse.jdt.core.JavaModelException;
39+
import org.eclipse.jdt.core.manipulation.JavaManipulation;
4040
import org.eclipse.jdt.internal.core.BufferManager;
4141
import org.eclipse.jdt.internal.core.CompilationUnit;
4242
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
43-
import org.eclipse.jdt.internal.core.JavaModelManager;
43+
import org.eclipse.jdt.internal.core.JavaCorePreferenceInitializer;
4444
import org.eclipse.jdt.internal.core.PackageFragment;
45+
import org.eclipse.jdt.internal.core.nd.indexer.Indexer;
4546

4647
/**
4748
* Helper methods to create Java compilation unit.
@@ -51,20 +52,38 @@
5152
* (see {@code org.eclipse.core.internal.resources.LocationValidator} for details).
5253
* </p>
5354
*/
54-
class EclipseJdtFactory extends OS {
55+
class EclipseJdtHelper extends OS {
5556

5657
private final static String ROOT_AS_SRC = "";
5758
private final static String PROJECT_NAME = "spotless";
5859
private final static String SOURCE_NAME = "source.java";
59-
private final static AtomicInteger UNIQUE_PROJECT_ID = new AtomicInteger(0);
60+
private static EclipseJdtHelper INSTANCE;
6061

61-
private final static Map<String, String> DEFAULT_OPTIONS;
62-
63-
static {
64-
Map<String, String> defaultOptions = new HashMap<>();
65-
defaultOptions.put(JavaCore.COMPILER_SOURCE, getJavaCoreVersion());
66-
DEFAULT_OPTIONS = Collections.unmodifiableMap(defaultOptions);
62+
static synchronized EclipseJdtHelper getInstance() {
63+
if(null == INSTANCE) {
64+
INSTANCE = new EclipseJdtHelper();
65+
}
66+
return INSTANCE;
6767
}
68+
69+
private final AtomicInteger uniqueProjectId = new AtomicInteger(0);
70+
private final Map<String, String> defaultOptions;
71+
72+
private EclipseJdtHelper() {
73+
defaultOptions = new HashMap<>();
74+
defaultOptions.put(JavaCore.COMPILER_SOURCE, getJavaCoreVersion());
75+
76+
/*
77+
* Assure that the 'allowed keys' are initialized, otherwise
78+
* JProject will not accept any options.
79+
*/
80+
new JavaCorePreferenceInitializer().initializeDefaultPreferences();
81+
82+
/*
83+
* Don't run indexer in background (does not disable thread but the job scheduling)
84+
*/
85+
Indexer.getInstance().enableAutomaticIndexing(false);
86+
}
6887

6988
private static String getJavaCoreVersion() {
7089
final String javaVersion = System.getProperty("java.version");
@@ -76,30 +95,41 @@ private static String getJavaCoreVersion() {
7695
}
7796
return orderedSupportedCoreVersions.get(orderedSupportedCoreVersions.size() - 1);
7897
}
79-
98+
8099
/**
81100
* Creates a JAVA project and applies the configuration.
82101
* @param settings Configuration settings
83102
* @return Configured JAVA project
84103
* @throws Exception In case the project creation fails
85104
*/
86-
public final static IJavaProject createProject(Properties settings) throws Exception {
87-
String uniqueProjectName = String.format("%s-%d", PROJECT_NAME, UNIQUE_PROJECT_ID.incrementAndGet());
105+
IJavaProject createProject(Properties settings) throws Exception {
106+
String uniqueProjectName = String.format("%s-%d", PROJECT_NAME, uniqueProjectId.incrementAndGet());
88107
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(uniqueProjectName);
89108
// The project must be open before items (natures, folders, sources, ...) can be created
90109
project.create(null);
91110
project.open(0, null);
92-
//If the project nature is not set, things like AST are not created for the Java projects
111+
112+
//If the project nature is not set, the AST is not created for the compilation units
93113
IProjectDescription description = project.getDescription();
94114
description.setNatureIds(new String[]{JavaCore.NATURE_ID});
95115
project.setDescription(description, null);
96116
IJavaProject jProject = JavaCore.create(project);
97117

98-
Map<String, String> settingsMap = new HashMap<>(DEFAULT_OPTIONS);
118+
Map<String, String> allSettings = new HashMap<>(defaultOptions);
99119
settings.forEach((key, value) -> {
100-
settingsMap.put(key.toString(), value.toString());
120+
allSettings.put(key.toString(), value.toString());
101121
});
102-
jProject.setOptions(settingsMap);
122+
//Configure JDT manipulation processor
123+
IEclipsePreferences projectPrefs = new ProjectScope(project.getProject()).getNode(JavaManipulation.getPreferenceNodeId());
124+
allSettings.forEach((key, value) -> {
125+
projectPrefs.put(key.toString(), value.toString());
126+
});
127+
/*
128+
* Configure options taken directly from the Java project (without qualifier).
129+
* Whether a setting is a Java project option or not, is filtered by the
130+
* JavaCorePreferenceInitializer, initialized by the constructor of this class.
131+
*/
132+
jProject.setOptions(allSettings);
103133

104134
// Eclipse source files require an existing source folder for creation
105135
IPackageFragmentRoot src = jProject.getPackageFragmentRoot(jProject.getProject());
@@ -110,27 +140,22 @@ public final static IJavaProject createProject(Properties settings) throws Excep
110140
// Eclipse clean-up requires an existing source file
111141
pkg.createCompilationUnit(SOURCE_NAME, "", true, null);
112142

113-
//
114-
disableSecondaryTypes(project);
115-
116143
return jProject;
117144
}
118145

119-
private static void disableSecondaryTypes(IProject project) {
120-
JavaModelManager.PerProjectInfo info = JavaModelManager.getJavaModelManager().getPerProjectInfo(project, true);
121-
info.secondaryTypes = new Hashtable<String, Map<String, IType>>();
122-
}
123-
124-
public static ICompilationUnit createJavaSource(String contents, IJavaProject jProject) throws Exception {
146+
ICompilationUnit createCompilationUnit(String contents, IJavaProject jProject) throws Exception {
125147
IPackageFragmentRoot src = jProject.getPackageFragmentRoot(jProject.getProject());
126148
IPackageFragment pkg = src.getPackageFragment(ROOT_AS_SRC);
127149
return new RamCompilationUnit((PackageFragment) pkg, contents);
128150
}
129151

130-
/** Spotless keeps compilation units in RAM as long as they are worked on. */
152+
/** Keep compilation units in RAM */
131153
private static class RamCompilationUnit extends CompilationUnit {
132154

133-
//Each RMA compilation unit has its own buffer manager. A drop is therefore prevented.
155+
/*
156+
* Each RAM compilation unit has its own buffer manager to
157+
* prevent dropping of CUs when a maximum size is reached.
158+
*/
134159
private final RamBufferManager manager;
135160

136161
RamCompilationUnit(PackageFragment parent, String contents) {
@@ -153,7 +178,7 @@ protected BufferManager getBufferManager() {
153178

154179
@Override
155180
public void save(IProgressMonitor pm, boolean force) throws JavaModelException {
156-
//RAM CU is never saved to disk
181+
//RAM CU is never stored on disk
157182
}
158183

159184
@Override
@@ -167,7 +192,7 @@ public boolean equals(Object obj) {
167192
}
168193
}
169194

170-
/** Work-around required package privileges when adding buffer for manager singleton */
195+
/** Work around package privileges */
171196
private static class RamBufferManager extends BufferManager {
172197
void add(IBuffer buffer) {
173198
addBuffer(buffer);

_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/JavaContentTypeManager.java

+7
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
import org.eclipse.core.internal.content.ContentType;
1919
import org.eclipse.core.internal.content.ContentTypeCatalog;
2020
import org.eclipse.core.runtime.content.IContentType;
21+
import org.eclipse.core.runtime.content.IContentTypeMatcher;
22+
import org.eclipse.core.runtime.preferences.IScopeContext;
2123
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
2224

2325
import com.diffplug.spotless.extra.eclipse.base.service.NoContentTypeSpecificHandling;
2426

2527
/**
2628
* Java compilation unit validation requires Java content type to be recognized.
29+
* All source is assumed to be Java. A content description is not required/provided.
2730
* <p>
2831
* See {@code org.eclipse.jdt.internal.core.util.Util} for details.
2932
* </p>
@@ -57,4 +60,8 @@ public IContentType[] getAllContentTypes() {
5760
return new IContentType[]{contentType};
5861
}
5962

63+
@Override
64+
public IContentTypeMatcher getMatcher(ISelectionPolicy customPolicy, IScopeContext context) {
65+
return this;
66+
}
6067
}

_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImplTest.java

+39-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
import java.util.Properties;
2121
import java.util.function.Consumer;
2222

23+
import org.assertj.core.util.Arrays;
24+
import org.eclipse.jdt.core.JavaCore;
25+
import org.eclipse.jdt.core.manipulation.CodeStyleConfiguration;
2326
import org.junit.BeforeClass;
2427
import org.junit.Test;
2528

@@ -38,8 +41,42 @@ public void emptyInput() throws Throwable {
3841
}
3942

4043
@Test
41-
public void nominal() throws Throwable {
42-
organizeImportTest("Simple", config -> {});
44+
public void defaultConfiguration() throws Throwable {
45+
for(String testFile:Arrays.array("Simple", "Statics", "Wildcards")) {
46+
organizeImportTest(testFile, config -> {});
47+
}
48+
}
49+
50+
@Test
51+
public void defaultPackage() throws Throwable {
52+
String input = TEST_DATA.input("Simple").replaceFirst("package .+", "");
53+
String expected = TEST_DATA.afterOrganizedImports("Simple").replaceFirst("package .+", "");
54+
organizeImportTest(input, expected, config -> {});
55+
}
56+
57+
@Test
58+
public void invalidConfiguration() throws Throwable {
59+
//Smoke test, no exceptions expected
60+
organizeImportTest("", "", config -> {
61+
config.put("invalid.key", "some.value");
62+
});
63+
organizeImportTest("", "", config -> {
64+
config.put(JavaCore.COMPILER_SOURCE, "-42");
65+
});
66+
organizeImportTest("", "", config -> {
67+
config.put(JavaCore.COMPILER_SOURCE, "Not an integer");
68+
});
69+
}
70+
71+
@Test
72+
public void customConfiguration() throws Throwable {
73+
String defaultOrganizedInput = TEST_DATA.input("Configuration");
74+
organizeImportTest(defaultOrganizedInput, defaultOrganizedInput, config -> {});
75+
76+
String customOrganizedOutput = TEST_DATA.afterOrganizedImports("Configuration");
77+
organizeImportTest(defaultOrganizedInput, customOrganizedOutput, config -> {
78+
config.put(CodeStyleConfiguration.ORGIMPORTS_IMPORTORDER, "foo;#foo;");
79+
});
4380
}
4481

4582
private static void organizeImportTest(final String fileName, final Consumer<Properties> config) throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package bar.foo;
2+
3+
import static foo.bar.B.someMethod; //Default configuration places static imports before none-static
4+
5+
import foo.bar.B;
6+
7+
class A {
8+
static {
9+
someMethod();
10+
new B();
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package bar.foo;
2+
3+
import foo.bar.B;
4+
5+
import static foo.bar.B.someMethod; //Default configuration places static imports before none-static
6+
7+
class A {
8+
static {
9+
someMethod();
10+
new B();
11+
}
12+
}

_ext/eclipse-jdt/src/test/resources/Simple.input

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import javax.net.SocketFactory; //Wrong order in Other group
55
import java.net.Socket; //Wrong within group
66
import java.lang.System; //Can be removed since implicitly imported
77
import java.io.IOException;
8-
import java.io.PrintStream; //Can be found and is not used
8+
import java.io.PrintStream; //Class can be found but is not used
99

10-
import foo.bar.C; //Cannot be found and is not used
11-
import foo.bar.B; //Cannot be found but is used
10+
import foo.bar.C; //Class cannot be found and is not used
11+
import foo.bar.B; //Class cannot be found but is used
1212

1313

1414
class A {

0 commit comments

Comments
 (0)