From 20851590091d9b0ffc8c515edcbacce1e6c3852e Mon Sep 17 00:00:00 2001 From: Frank Vennemeyer Date: Tue, 27 Aug 2019 19:52:20 +0200 Subject: [PATCH] First draft for JDT clean-up. --- _ext/eclipse-jdt/build.gradle | 13 + _ext/eclipse-jdt/gradle.properties | 5 +- .../extra/eclipse/java/CleanUpFactory.java | 216 +++++++++++++++ .../java/EclipseJdtCleanUpStepImpl.java | 262 ++++++++++++------ ...r.java => EclipseJdtCoreManipulation.java} | 133 ++++++--- .../EclipseJdtOrganizeImportStepImpl.java | 47 ++++ .../java/EclipseJdtCleanUpStepImplTest.java | 65 ++--- .../EclipseJdtOrganizeImportStepImplTest.java | 99 +++++++ .../spotless/extra/eclipse/java/TestData.java | 14 + ...ration.input => ImportConfiguration.input} | 0 ...rganized => ImportConfiguration.organized} | 0 11 files changed, 695 insertions(+), 159 deletions(-) create mode 100644 _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/CleanUpFactory.java rename _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/{EclipseJdtHelper.java => EclipseJdtCoreManipulation.java} (59%) create mode 100644 _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImpl.java create mode 100644 _ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImplTest.java rename _ext/eclipse-jdt/src/test/resources/{Configuration.input => ImportConfiguration.input} (100%) rename _ext/eclipse-jdt/src/test/resources/{Configuration.organized => ImportConfiguration.organized} (100%) diff --git a/_ext/eclipse-jdt/build.gradle b/_ext/eclipse-jdt/build.gradle index c1eb1eb4a0..7df2c29e80 100644 --- a/_ext/eclipse-jdt/build.gradle +++ b/_ext/eclipse-jdt/build.gradle @@ -10,9 +10,22 @@ ext { dependencies { compile "com.diffplug.spotless:spotless-eclipse-base:${VER_SPOTLESS_ECLISPE_BASE}" + /* + * JDT core manipulation required for clean-up base interfaces and import sorting + * It depends on JDT core, which is required for fomatting. + */ compile("org.eclipse.jdt:org.eclipse.jdt.core.manipulation:${VER_ECLIPSE_JDT_CORE_MANIPULATION}") { exclude group: 'org.eclipse.jdt', module: 'org.eclipse.jdt.launching' exclude group: 'org.eclipse.platform', module: 'org.eclipse.ant.core' exclude group: 'org.eclipse.platform', module: 'org.eclipse.core.expressions' } + /* + * JDT UI required for clean-up. + * Only the org.eclipse.jdt.internal.corext.fix package is required. + * All dependencies (like SWT) are excluded. + */ + compile("org.eclipse.jdt:org.eclipse.jdt.ui:${VER_ECLIPSE_JDT_UI}") { + exclude group: 'org.eclipse.platform' + exclude group: 'org.eclipse.jdt' + } } \ No newline at end of file diff --git a/_ext/eclipse-jdt/gradle.properties b/_ext/eclipse-jdt/gradle.properties index 4b6cbfb8f2..5aea1b9575 100644 --- a/_ext/eclipse-jdt/gradle.properties +++ b/_ext/eclipse-jdt/gradle.properties @@ -1,6 +1,6 @@ # Mayor/Minor versions correspond to the minimum Eclipse version supported/tested. # Patch version is incremented for backward compatible patches of this library. -ext_version=4.11.0 +ext_version=4.12.0 ext_artifactId=spotless-eclipse-jdt ext_description=Eclipse's JDT formatter bundled for Spotless @@ -12,4 +12,5 @@ ext_VER_JAVA=1.8 # Compile VER_ECLIPSE_JDT_CORE_MANIPULATION=[1.11.0,2.0.0[ -VER_SPOTLESS_ECLISPE_BASE=[3.0.0,4.0.0[ +VER_ECLIPSE_JDT_UI=[3.18.0,4.0.0[ +VER_SPOTLESS_ECLISPE_BASE=[3.2.0,4.0.0[ diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/CleanUpFactory.java b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/CleanUpFactory.java new file mode 100644 index 0000000000..426d08d207 --- /dev/null +++ b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/CleanUpFactory.java @@ -0,0 +1,216 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.extra.eclipse.java; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.xpath.XPathExpressionException; + +import org.assertj.core.util.Sets; +import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; +import org.eclipse.jdt.internal.corext.fix.CleanUpConstantsOptions; +import org.eclipse.jdt.ui.cleanup.CleanUpOptions; +import org.eclipse.jdt.ui.cleanup.ICleanUp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** Provides configured clean-up implementations. */ +final class CleanUpFactory { + + private final static Set UNSUPPORTED_CLASSES = Collections.unmodifiableSet(Sets.newLinkedHashSet( + "org.eclipse.jdt.internal.ui.fix.UnimplementedCodeCleanUp" //Would require Eclipse templates + )); + + @SuppressWarnings("serial") + private final static Map UNSUPPORTED_CONFIG = Collections.unmodifiableMap(new HashMap() { + { + put(CleanUpConstants.REMOVE_UNUSED_CODE_IMPORTS, new FixedValue("false", "Unused import clean-up only works in case all imports can be resolved. As an alternative use: " + CleanUpConstants.ORGANIZE_IMPORTS)); + } + }); + + private final static String CLEAN_UP_CONFIG_FILE_NAME = "plugin.xml"; + private final static String CLEAN_UP_CONFIG_DEPENDENCY_NAME = "org.eclipse.jdt.ui"; + private static List> CLEAN_UP_SEQUENCE = null; + private final CleanUpOptions options; + + CleanUpFactory(Properties settings) { + options = new CleanUpOptions(); + Logger logger = LoggerFactory.getLogger(CleanUpFactory.class); + CleanUpConstantsOptions.setDefaultOptions(CleanUpConstants.DEFAULT_CLEAN_UP_OPTIONS, options); + UNSUPPORTED_CONFIG.entrySet().stream().forEach(entry -> options.setOption(entry.getKey(), entry.getValue().value)); + settings.forEach((key, value) -> { + FixedValue fixed = UNSUPPORTED_CONFIG.get(key); + if (null != fixed && fixed.value != value) { + logger.warn(String.format("Using %s for %s instead of %s: %s", fixed.value, key, value, fixed.reason)); + } else { + options.setOption(key.toString(), value.toString()); + } + }); + try { + initializeCleanupActions(); + } catch (IOException | ParserConfigurationException | XPathExpressionException e) { + throw new RuntimeException("Faild to read Eclipse Clean-Up configuration.", e); + } + } + + private static synchronized void initializeCleanupActions() throws IOException, ParserConfigurationException, XPathExpressionException { + if (null != CLEAN_UP_SEQUENCE) { + return; + } + ClassLoader loader = CleanUpFactory.class.getClassLoader(); + Optional configUrl = Collections.list(loader.getResources(CLEAN_UP_CONFIG_FILE_NAME)).stream().filter(url -> url.getPath().contains(CLEAN_UP_CONFIG_DEPENDENCY_NAME)).findAny(); + if (!configUrl.isPresent()) { + throw new RuntimeException("Could not find JAR containing " + CLEAN_UP_CONFIG_DEPENDENCY_NAME + ":" + CLEAN_UP_CONFIG_FILE_NAME); + } + InputStream configXmlStream = configUrl.get().openStream(); + try { + SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser(); + CleanUpExtensionHandler handler = new CleanUpExtensionHandler(); + saxParser.parse(configXmlStream, handler); + CLEAN_UP_SEQUENCE = handler.getCleanUpSequence(); + } catch (SAXException e) { + //Add information about the XML location + throw new RuntimeException("Failed to parse " + configUrl.get().toExternalForm(), e); + } + } + + public List create() { + return CLEAN_UP_SEQUENCE.stream().map(constructor -> { + try { + ICleanUp cleanUp = constructor.newInstance(); + cleanUp.setOptions(options); + return cleanUp; + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException("Failed to created clean-up action for " + constructor.getName(), e); + } + }).collect(Collectors.toList()); + } + + private static class FixedValue { + public final String value; + public final String reason; + + FixedValue(String value, String reason) { + this.value = value; + this.reason = reason; + } + }; + + private final static class CleanUpExtensionHandler extends DefaultHandler { + private final static String CLEAN_UP_ELEMENT_NAME = "cleanUp"; + private final static String ID_ATTRIBUTE_NAME = "id"; + private final static String CLASS_ATTRIBUTE_NAME = "class"; + private final static String RUN_AFTER_ATTRIBUTE_NAME = "runAfter"; + private final Map> constructor; + private final Map runAfter; + private final LinkedList sorted; + + CleanUpExtensionHandler() { + constructor = new HashMap<>(); + runAfter = new LinkedHashMap<>(); //E.g. the elements are already sorted + sorted = new LinkedList<>(); + } + + @Override + public void startDocument() throws SAXException { + constructor.clear(); + runAfter.clear(); + sorted.clear(); + super.startDocument(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + if (CLEAN_UP_ELEMENT_NAME == qName) { + String id = getMandatoryAttribute(attributes, ID_ATTRIBUTE_NAME); + String className = getMandatoryAttribute(attributes, CLASS_ATTRIBUTE_NAME); + if (!UNSUPPORTED_CLASSES.contains(className)) { + try { + Class clazz = Class.forName(className); + Class clazzImplementsICleanUp = clazz.asSubclass(ICleanUp.class); + constructor.put(id, clazzImplementsICleanUp.getConstructor()); + } catch (ClassNotFoundException | ClassCastException | NoSuchMethodException | SecurityException e) { + throw new SAXException("Failed to obtain constructor for " + CLEAN_UP_ELEMENT_NAME + " element class " + className, e); + } + } + String runAfterId = attributes.getValue(RUN_AFTER_ATTRIBUTE_NAME); + if (null == runAfterId) { + sorted.push(id); + } else { + runAfter.put(id, runAfterId); + } + } + super.startElement(uri, localName, qName, attributes); + } + + private static String getMandatoryAttribute(Attributes attributes, String qName) throws SAXException { + String value = attributes.getValue(qName); + if (null == value) { + throw new SAXException(CLEAN_UP_ELEMENT_NAME + " element without " + qName + " attribute."); + } + return value; + } + + @Override + public void endDocument() throws SAXException { + if (runAfter.isEmpty()) { + throw new SAXException(CLEAN_UP_ELEMENT_NAME + " element has not been found in XML."); + } + while (!runAfter.isEmpty()) { + //E.g. the elements are already sorted. Hence only one iteration is expected. + List foundEntries = new ArrayList<>(runAfter.size()); + for (Map.Entry entry : runAfter.entrySet()) { + int runAfterIndex = sorted.lastIndexOf(entry.getValue()); + if (0 <= runAfterIndex) { + foundEntries.add(entry.getKey()); + sorted.add(runAfterIndex + 1, entry.getKey()); + } + } + foundEntries.forEach(e -> runAfter.remove(e)); + if (foundEntries.isEmpty()) { + throw new SAXException(CLEAN_UP_ELEMENT_NAME + " element the following precessor IDs cannot be resolved: " + runAfter.values().stream().collect(Collectors.joining("; "))); + } + } + super.endDocument(); + } + + public List> getCleanUpSequence() { + return sorted.stream().map(id -> constructor.get(id)).filter(clazz -> null != clazz).collect(Collectors.toList()); + } + } + +} diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImpl.java b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImpl.java index 8125cf7a18..86fdd6a2ff 100644 --- a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImpl.java +++ b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImpl.java @@ -15,102 +15,206 @@ */ package com.diffplug.spotless.extra.eclipse.java; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; import java.util.Properties; -import org.eclipse.core.internal.runtime.InternalPlatform; import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.core.runtime.content.IContentTypeManager; -import org.eclipse.core.runtime.preferences.DefaultScope; -import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.manipulation.CodeStyleConfiguration; -import org.eclipse.jdt.core.manipulation.JavaManipulation; -import org.eclipse.jdt.core.manipulation.OrganizeImportsOperation; import org.eclipse.jdt.core.manipulation.SharedASTProviderCore; -import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettingsConstants; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; +import org.eclipse.jdt.core.refactoring.CompilationUnitChange; +import org.eclipse.jdt.ui.cleanup.CleanUpContext; +import org.eclipse.jdt.ui.cleanup.CleanUpRequirements; +import org.eclipse.jdt.ui.cleanup.ICleanUp; +import org.eclipse.jdt.ui.cleanup.ICleanUpFix; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.text.edits.UndoEdit; /** Clean-up step which calls out to the Eclipse JDT clean-up / import sorter. */ -public class EclipseJdtCleanUpStepImpl { +public class EclipseJdtCleanUpStepImpl extends EclipseJdtCoreManipulation { + + /** + * In case of Eclipse JDT clean-up problems (warnings + errors) + * the clean-up step is skipped if not problems shall not be ignored. + *

+ * Value is either 'true' or 'false' ('false' per default) + *

+ */ + public static final String IGNORE_CLEAN_UP_PROBLEMS = "ignoreCleanUpProblems"; - // The JDT UI shall be used for creating the settings. - private final static String JDT_UI_PLUGIN_ID = "org.eclipse.jdt.ui"; - private final IJavaProject jdtConfiguration; //The project stores the JDT clean-up configuration - private final EclipseJdtHelper jdtHelper; + private final boolean ignoreCleanUpProblems; + private final IJavaProject jdtConfiguration; + private final CleanUpFactory cleanUpFactory; public EclipseJdtCleanUpStepImpl(Properties settings) throws Exception { - if (SpotlessEclipseFramework.setup( - core -> { - /* - * Indexer needs to exist (but is not used) for JDT clean-up. - * The indexer is not created in headless mode by JDT. - * 'Active' platform state signals non-headless mode ('Resolved' is default state).. - */ - core.add(new org.eclipse.core.internal.registry.osgi.Activator()); - - core.add(new org.eclipse.core.internal.runtime.PlatformActivator()); - core.add(new org.eclipse.core.internal.preferences.Activator()); - core.add(new org.eclipse.core.internal.runtime.Activator()); - }, - config -> { - config.hideEnvironment(); - config.disableDebugging(); - config.ignoreUnsupportedPreferences(); - config.useTemporaryLocations(); - config.changeSystemLineSeparator(); - - /* - * The default 'no content type specific handling' is insufficient. - * The Java source type needs to be recognized by file extension. - */ - config.add(IContentTypeManager.class, new JavaContentTypeManager()); - - config.useSlf4J(EclipseJdtCleanUpStepImpl.class.getPackage().getName()); - config.set(InternalPlatform.PROP_OS, ""); - }, - plugins -> { - plugins.applyDefault(); - - //JDT configuration requires an existing project source folder. - plugins.add(new org.eclipse.core.internal.filesystem.Activator()); - plugins.add(new JavaCore()); - })) { - initializeJdtUiDefaultSettings(); - } - jdtHelper = EclipseJdtHelper.getInstance(); - jdtConfiguration = jdtHelper.createProject(settings); + jdtConfiguration = createProject(settings); + cleanUpFactory = new CleanUpFactory(settings); + ignoreCleanUpProblems = Boolean.parseBoolean(settings.getProperty(IGNORE_CLEAN_UP_PROBLEMS, "false")); } - private static void initializeJdtUiDefaultSettings() { - //Following values correspond org.eclipse.jdt.ui.PreferenceConstants - JavaManipulation.setPreferenceNodeId(JDT_UI_PLUGIN_ID); - IEclipsePreferences prefs = DefaultScope.INSTANCE.getNode(JDT_UI_PLUGIN_ID); + /** Formats Java raw text. The file-location is used in log messages. */ + public String format(String raw, String fileLocation) throws Exception { + ICompilationUnit compilationUnit = createCompilationUnit(raw, jdtConfiguration); + SpotlessRefactoring refactoring = new SpotlessRefactoring(compilationUnit, ignoreCleanUpProblems); + RefactoringStatus report = refactoring.apply(cleanUpFactory.create()); + Arrays.stream(report.getEntries()).map(entry -> new SpotlessStatus(entry, fileLocation)).forEach(status -> logger.log(status)); + return compilationUnit.getBuffer().getContents(); + } - prefs.put(CodeStyleConfiguration.ORGIMPORTS_IMPORTORDER, "java;javax;org;com"); - prefs.put(CodeStyleConfiguration.ORGIMPORTS_ONDEMANDTHRESHOLD, "99"); - prefs.put(CodeStyleConfiguration.ORGIMPORTS_STATIC_ONDEMANDTHRESHOLD, "99"); + /** + * Spotless version of {@code org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring}. + *

+ * Spotless does not request (graphical) user feedback neither does it provide undo-information. + * Since Spotless re-factoring / formatting is applied without any further explanation of the changes (preview, warnings, ...), + * it skips per default steps reporting problems (non-fatal errors or warnings) to ensure that the result is as expected by the user. + * Spotless applies the JDT re-factoring without providing a project scope (dependencies, ...). + * Hence steps can cause (fatal) errors which would pass within an Eclipse project. + * Unlike the Eclipse re-factoring process, Spotless does not abort in case a step + * fails, but just reports and skips the step. + */ + private static class SpotlessRefactoring { - prefs.put(CodeGenerationSettingsConstants.CODEGEN_KEYWORD_THIS, "false"); - prefs.put(CodeGenerationSettingsConstants.CODEGEN_USE_OVERRIDE_ANNOTATION, "false"); - prefs.put(CodeGenerationSettingsConstants.CODEGEN_ADD_COMMENTS, "true"); - prefs.put(CodeGenerationSettingsConstants.ORGIMPORTS_IGNORELOWERCASE, "true"); - } + private final ICompilationUnit source; + private final ICompilationUnit[] sources; + private final boolean ignoreProblems; + private final IProgressMonitor doNotMonitor; + private CompilationUnit lazyAst; + private boolean astIsFresh; - public String organizeImport(String raw) throws Exception { - ICompilationUnit compilationUnit = jdtHelper.createCompilationUnit(raw, jdtConfiguration); - CompilationUnit ast = SharedASTProviderCore.getAST(compilationUnit, SharedASTProviderCore.WAIT_YES, null); - OrganizeImportsOperation formatOperation = new OrganizeImportsOperation(compilationUnit, ast, true, false, true, null); - try { - formatOperation.run(null); - return compilationUnit.getSource(); - } catch (OperationCanceledException | CoreException e) { - throw new IllegalArgumentException("Invalid java syntax for formatting.", e); + SpotlessRefactoring(ICompilationUnit sourceToRefactor, boolean ignoreCleanUpProblems) { + source = sourceToRefactor; + sources = new ICompilationUnit[]{sourceToRefactor}; + ignoreProblems = ignoreCleanUpProblems; + doNotMonitor = new NullProgressMonitor(); + lazyAst = null; + astIsFresh = false; + } + + RefactoringStatus apply(List steps) throws CoreException { + RefactoringStatus overallStatus = new RefactoringStatus(); + for (ICleanUp step : steps) { + apply(step, overallStatus); + } + return overallStatus; + } + + private void apply(ICleanUp step, RefactoringStatus overallStatus) throws CoreException { + RefactoringStatus preCheckStatus = step.checkPreConditions(source.getJavaProject(), sources, doNotMonitor); + overallStatus.merge(preCheckStatus); + if (isStepOk(preCheckStatus)) { + CleanUpContext context = createContext(step.getRequirements()); + ICleanUpFix fix = step.createFix(context); + RefactoringStatus postCheckStatus = apply(step, Optional.ofNullable(fix)); + overallStatus.merge(postCheckStatus); + } + } + + private RefactoringStatus apply(ICleanUp step, Optional fix) throws CoreException { + RefactoringStatus postCheckStatus = new RefactoringStatus(); + if (fix.isPresent()) { + CompilationUnitChange change = fix.get().createChange(doNotMonitor); + TextEdit edit = change.getEdit(); + if (null != edit) { + UndoEdit undo = source.applyTextEdit(edit, doNotMonitor); + postCheckStatus = step.checkPostConditions(doNotMonitor); + if (isStepOk(postCheckStatus)) { + astIsFresh = false; + } else { + postCheckStatus.addInfo("Undo step " + step.getClass().getSimpleName()); + if (null != undo) { + source.applyTextEdit(undo, doNotMonitor); + } + } + } + } + return postCheckStatus; + } + + private boolean isStepOk(RefactoringStatus stepStatus) { + if (ignoreProblems) { + return stepStatus.getSeverity() < RefactoringStatus.FATAL; + } + return stepStatus.getSeverity() < RefactoringStatus.WARNING; + } + + private CleanUpContext createContext(CleanUpRequirements requirements) { + if ((requirements.requiresAST() && null == lazyAst) || + (requirements.requiresFreshAST() && false == astIsFresh)) { + lazyAst = SharedASTProviderCore.getAST(source, SharedASTProviderCore.WAIT_YES, null); + astIsFresh = true; + } + return new CleanUpContext(source, lazyAst); + } + + }; + + private static class SpotlessStatus implements IStatus { + private final IStatus cleanUpStatus; + private final String fileLocationAsPluginId; + + SpotlessStatus(RefactoringStatusEntry entry, String fileLocation) { + cleanUpStatus = entry.toStatus(); + fileLocationAsPluginId = fileLocation; + } + + @Override + public IStatus[] getChildren() { + return cleanUpStatus.getChildren(); + } + + @Override + public int getCode() { + return cleanUpStatus.getCode(); + } + + @Override + public Throwable getException() { + return cleanUpStatus.getException(); + } + + @Override + public String getMessage() { + return cleanUpStatus.getMessage(); + } + + @Override + public String getPlugin() { + /* + * The plugin ID of the JDT Clean-Up is always a common string. + * Hence it does not add any valuable information for the Spotless user. + * It is replaced by the file location which is hidden from the JDT re-factoring + * process. + */ + return fileLocationAsPluginId; + } + + @Override + public int getSeverity() { + return cleanUpStatus.getSeverity(); + } + + @Override + public boolean isMultiStatus() { + return cleanUpStatus.isMultiStatus(); + } + + @Override + public boolean isOK() { + return cleanUpStatus.isOK(); + } + + @Override + public boolean matches(int severityMask) { + return cleanUpStatus.matches(severityMask); } - } + }; } diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtHelper.java b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCoreManipulation.java similarity index 59% rename from _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtHelper.java rename to _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCoreManipulation.java index e0dd62da45..74f100fa08 100644 --- a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtHelper.java +++ b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCoreManipulation.java @@ -21,13 +21,16 @@ import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; -import org.eclipse.core.internal.resources.OS; +import org.eclipse.core.internal.runtime.InternalPlatform; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.content.IContentTypeManager; +import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.jdt.core.IBuffer; import org.eclipse.jdt.core.ICompilationUnit; @@ -36,6 +39,7 @@ import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.manipulation.CodeStyleConfiguration; import org.eclipse.jdt.core.manipulation.JavaManipulation; import org.eclipse.jdt.internal.core.BufferManager; import org.eclipse.jdt.internal.core.CompilationUnit; @@ -43,47 +47,96 @@ import org.eclipse.jdt.internal.core.JavaCorePreferenceInitializer; import org.eclipse.jdt.internal.core.PackageFragment; import org.eclipse.jdt.internal.core.nd.indexer.Indexer; +import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettingsConstants; + +import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; /** - * Helper methods to create Java compilation unit. - *

- * The helper provides a pseudo extension of the OS (OS specific JARs are not provided with Spotless). - * The OS initialization is required for compilation unit validation - * (see {@code org.eclipse.core.internal.resources.LocationValidator} for details). - *

+ * Basic set-up for formatting features based on {@code org.eclipse.jdt:org.eclipse.jdt.core.manipulation}. */ -class EclipseJdtHelper extends OS { +class EclipseJdtCoreManipulation { + // The JDT UI shall be used for creating the settings (JavaUI not imported due to dependencies). + private final static String JDT_UI_PLUGIN_ID = "org.eclipse.jdt.ui"; private final static String ROOT_AS_SRC = ""; private final static String PROJECT_NAME = "spotless"; private final static String SOURCE_NAME = "source.java"; - private static EclipseJdtHelper INSTANCE; - static synchronized EclipseJdtHelper getInstance() { - if(null == INSTANCE) { - INSTANCE = new EclipseJdtHelper(); - } - return INSTANCE; - } - + protected final ILog logger; + private final AtomicInteger uniqueProjectId = new AtomicInteger(0); private final Map defaultOptions; - - private EclipseJdtHelper() { + + protected EclipseJdtCoreManipulation() throws Exception { + if (SpotlessEclipseFramework.setup( + core -> { + /* + * Indexer needs to exist (but is not used) for JDT clean-up. + * The indexer is not created in headless mode by JDT. + * 'Active' platform state signals non-headless mode ('Resolved' is default state).. + */ + core.add(new org.eclipse.core.internal.registry.osgi.Activator()); + + core.add(new org.eclipse.core.internal.runtime.PlatformActivator()); + core.add(new org.eclipse.core.internal.preferences.Activator()); + core.add(new org.eclipse.core.internal.runtime.Activator()); + }, + config -> { + config.hideEnvironment(); + config.disableDebugging(); + config.ignoreUnsupportedPreferences(); + config.useTemporaryLocations(); + config.changeSystemLineSeparator(); + + /* + * The default 'no content type specific handling' is insufficient. + * The Java source type needs to be recognized by file extension. + */ + config.add(IContentTypeManager.class, new JavaContentTypeManager()); + + config.useSlf4J(EclipseJdtOrganizeImportStepImpl.class.getPackage().getName()); + config.set(InternalPlatform.PROP_OS, ""); //Required for org.eclipse.core.internal.resources.OS initialization + }, + plugins -> { + plugins.applyDefault(); + + //JDT configuration requires an existing project source folder. + plugins.add(new org.eclipse.core.internal.filesystem.Activator()); + plugins.add(new JavaCore()); + })) { + + initializeJdtUiPreferenceDefaults(); + /* + * Assure that the 'allowed keys' are initialized, otherwise + * JProject will not accept any options. + */ + new JavaCorePreferenceInitializer().initializeDefaultPreferences(); + + /* + * Don't run indexer in background (does not disable thread but the job scheduling) + */ + Indexer.getInstance().enableAutomaticIndexing(false); + } + defaultOptions = new HashMap<>(); defaultOptions.put(JavaCore.COMPILER_SOURCE, getJavaCoreVersion()); - - /* - * Assure that the 'allowed keys' are initialized, otherwise - * JProject will not accept any options. - */ - new JavaCorePreferenceInitializer().initializeDefaultPreferences(); - - /* - * Don't run indexer in background (does not disable thread but the job scheduling) - */ - Indexer.getInstance().enableAutomaticIndexing(false); - } + logger = JavaCore.getPlugin().getLog(); + } + + private static void initializeJdtUiPreferenceDefaults() { + //Following values correspond org.eclipse.jdt.ui.PreferenceConstants (not used due to SWT dependency) + JavaManipulation.setPreferenceNodeId(JDT_UI_PLUGIN_ID); + IEclipsePreferences prefs = DefaultScope.INSTANCE.getNode(JDT_UI_PLUGIN_ID); + + prefs.put(CodeStyleConfiguration.ORGIMPORTS_IMPORTORDER, "java;javax;org;com"); + prefs.put(CodeStyleConfiguration.ORGIMPORTS_ONDEMANDTHRESHOLD, "99"); + prefs.put(CodeStyleConfiguration.ORGIMPORTS_STATIC_ONDEMANDTHRESHOLD, "99"); + + prefs.put(CodeGenerationSettingsConstants.CODEGEN_KEYWORD_THIS, "false"); + prefs.put(CodeGenerationSettingsConstants.CODEGEN_USE_OVERRIDE_ANNOTATION, "false"); + prefs.put(CodeGenerationSettingsConstants.CODEGEN_ADD_COMMENTS, "true"); + prefs.put(CodeGenerationSettingsConstants.ORGIMPORTS_IGNORELOWERCASE, "true"); + } private static String getJavaCoreVersion() { final String javaVersion = System.getProperty("java.version"); @@ -95,20 +148,22 @@ private static String getJavaCoreVersion() { } return orderedSupportedCoreVersions.get(orderedSupportedCoreVersions.size() - 1); } - + /** * Creates a JAVA project and applies the configuration. * @param settings Configuration settings * @return Configured JAVA project * @throws Exception In case the project creation fails */ - IJavaProject createProject(Properties settings) throws Exception { + protected final IJavaProject createProject(Properties settings) throws Exception { String uniqueProjectName = String.format("%s-%d", PROJECT_NAME, uniqueProjectId.incrementAndGet()); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(uniqueProjectName); // The project must be open before items (natures, folders, sources, ...) can be created - project.create(null); + if (!project.exists()) { + project.create(null); //Might still exist in case of restarts and dedicated class loader + } project.open(0, null); - + //If the project nature is not set, the AST is not created for the compilation units IProjectDescription description = project.getDescription(); description.setNatureIds(new String[]{JavaCore.NATURE_ID}); @@ -123,7 +178,7 @@ IJavaProject createProject(Properties settings) throws Exception { IEclipsePreferences projectPrefs = new ProjectScope(project.getProject()).getNode(JavaManipulation.getPreferenceNodeId()); allSettings.forEach((key, value) -> { projectPrefs.put(key.toString(), value.toString()); - }); + }); /* * Configure options taken directly from the Java project (without qualifier). * Whether a setting is a Java project option or not, is filtered by the @@ -135,7 +190,9 @@ IJavaProject createProject(Properties settings) throws Exception { IPackageFragmentRoot src = jProject.getPackageFragmentRoot(jProject.getProject()); IPackageFragment pkg = src.createPackageFragment(ROOT_AS_SRC, true, null); IFolder folder = project.getFolder(uniqueProjectName); - folder.create(0, false, null); + if (!folder.exists()) { + folder.create(0, false, null); + } // Eclipse clean-up requires an existing source file pkg.createCompilationUnit(SOURCE_NAME, "", true, null); @@ -143,7 +200,7 @@ IJavaProject createProject(Properties settings) throws Exception { return jProject; } - ICompilationUnit createCompilationUnit(String contents, IJavaProject jProject) throws Exception { + protected final ICompilationUnit createCompilationUnit(String contents, IJavaProject jProject) throws Exception { IPackageFragmentRoot src = jProject.getPackageFragmentRoot(jProject.getProject()); IPackageFragment pkg = src.getPackageFragment(ROOT_AS_SRC); return new RamCompilationUnit((PackageFragment) pkg, contents); @@ -183,7 +240,7 @@ public void save(IProgressMonitor pm, boolean force) throws JavaModelException { @Override public ICompilationUnit getWorkingCopy(IProgressMonitor monitor) throws JavaModelException { - throw new UnsupportedOperationException("Spotless RAM compilation unit cannot be copied."); + return new RamCompilationUnit((PackageFragment) this.getParent(), getBuffer().getContents()); } @Override diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImpl.java b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImpl.java new file mode 100644 index 0000000000..f99480704c --- /dev/null +++ b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImpl.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.extra.eclipse.java; + +import java.util.Properties; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.manipulation.OrganizeImportsOperation; +import org.eclipse.jdt.core.manipulation.SharedASTProviderCore; + +/** Clean-up step which calls out to the Eclipse JDT clean-up / import sorter. */ +public class EclipseJdtOrganizeImportStepImpl extends EclipseJdtCoreManipulation { + private final IJavaProject jdtConfiguration; //The project stores the JDT clean-up configuration + + public EclipseJdtOrganizeImportStepImpl(Properties settings) throws Exception { + jdtConfiguration = createProject(settings); + } + + public String format(String raw) throws Exception { + ICompilationUnit compilationUnit = createCompilationUnit(raw, jdtConfiguration); + CompilationUnit ast = SharedASTProviderCore.getAST(compilationUnit, SharedASTProviderCore.WAIT_YES, null); + OrganizeImportsOperation formatOperation = new OrganizeImportsOperation(compilationUnit, ast, false, false, true, null); + try { + formatOperation.run(null); + return compilationUnit.getSource(); + } catch (OperationCanceledException | CoreException e) { + throw new IllegalArgumentException("Invalid java syntax for formatting.", e); + } + } +} diff --git a/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImplTest.java b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImplTest.java index 8f966335ce..1ebe9b92c2 100644 --- a/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImplTest.java +++ b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImplTest.java @@ -24,10 +24,12 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.manipulation.CodeStyleConfiguration; import org.junit.BeforeClass; +import org.junit.ComparisonFailure; import org.junit.Test; /** Eclipse JDT wrapper integration tests */ public class EclipseJdtCleanUpStepImplTest { + private static String SOURCE_FILE_PATH = "some .. / \\ ill&formatted/$path"; private static TestData TEST_DATA = null; @BeforeClass @@ -37,72 +39,55 @@ public static void initializeStatic() throws Exception { @Test public void emptyInput() throws Throwable { - organizeImportTest("", "", config -> {}); + cleanUpTest("", "", config -> {}); } @Test public void defaultConfiguration() throws Throwable { - for(String testFile:Arrays.array("Simple", "Statics", "Wildcards")) { - organizeImportTest(testFile, config -> {}); + for (String testFile : Arrays.array("Simple", "Statics", "Wildcards")) { + try { + cleanUpTest(testFile, config -> {}); + } catch (ComparisonFailure e) { + throw new ComparisonFailure(testFile + " - " + e.getMessage(), e.getExpected(), e.getActual()); + } } } - - @Test - public void defaultPackage() throws Throwable { - String input = TEST_DATA.input("Simple").replaceFirst("package .+", ""); - String expected = TEST_DATA.afterOrganizedImports("Simple").replaceFirst("package .+", ""); - organizeImportTest(input, expected, config -> {}); - } - + @Test public void invalidConfiguration() throws Throwable { //Smoke test, no exceptions expected - organizeImportTest("", "", config -> { + cleanUpTest("", "", config -> { config.put("invalid.key", "some.value"); }); - organizeImportTest("", "", config -> { + cleanUpTest("", "", config -> { config.put(JavaCore.COMPILER_SOURCE, "-42"); }); - organizeImportTest("", "", config -> { + cleanUpTest("", "", config -> { config.put(JavaCore.COMPILER_SOURCE, "Not an integer"); }); } - + @Test - public void customConfiguration() throws Throwable { - String defaultOrganizedInput = TEST_DATA.input("Configuration"); - organizeImportTest(defaultOrganizedInput, defaultOrganizedInput, config -> {}); - - String customOrganizedOutput = TEST_DATA.afterOrganizedImports("Configuration"); - organizeImportTest(defaultOrganizedInput, customOrganizedOutput, config -> { + public void importConfiguration() throws Throwable { + String defaultOrganizedInput = TEST_DATA.input("ImportConfiguration"); + cleanUpTest(defaultOrganizedInput, defaultOrganizedInput, config -> {}); + + String customOrganizedOutput = TEST_DATA.afterOrganizedImports("ImportConfiguration"); + cleanUpTest(defaultOrganizedInput, customOrganizedOutput, config -> { config.put(CodeStyleConfiguration.ORGIMPORTS_IMPORTORDER, "foo;#foo;"); }); } - private static void organizeImportTest(final String fileName, final Consumer config) throws Exception { - organizeImportTest(TEST_DATA.input(fileName), TEST_DATA.afterOrganizedImports(fileName), config); + private static void cleanUpTest(final String fileName, final Consumer config) throws Exception { + cleanUpTest(TEST_DATA.input(fileName), TEST_DATA.afterCleanUp(fileName), config); } - private static void organizeImportTest(final String input, final String expected, final Consumer config) throws Exception { + private static void cleanUpTest(final String input, final String expected, final Consumer config) throws Exception { Properties properties = new Properties(); config.accept(properties); EclipseJdtCleanUpStepImpl formatter = new EclipseJdtCleanUpStepImpl(properties); - String output = formatter.organizeImport(input); - assertEquals("Unexpected import organization " + toString(properties), + String output = formatter.format(input, SOURCE_FILE_PATH); + assertEquals("Unexpected clean-up result " + TestData.toString(properties), expected, output); } - - private static String toString(Properties properties) { - StringBuilder result = new StringBuilder(); - result.append('['); - properties.forEach((k, v) -> { - result.append(k.toString()); - result.append('='); - result.append(v.toString()); - result.append(';'); - }); - result.append(']'); - return result.toString(); - } - } diff --git a/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImplTest.java b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImplTest.java new file mode 100644 index 0000000000..fb1fcc44da --- /dev/null +++ b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtOrganizeImportStepImplTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.extra.eclipse.java; + +import static org.junit.Assert.*; + +import java.util.Properties; +import java.util.function.Consumer; + +import org.assertj.core.util.Arrays; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.manipulation.CodeStyleConfiguration; +import org.junit.BeforeClass; +import org.junit.ComparisonFailure; +import org.junit.Test; + +/** Eclipse JDT wrapper integration tests */ +public class EclipseJdtOrganizeImportStepImplTest { + private static TestData TEST_DATA = null; + + @BeforeClass + public static void initializeStatic() throws Exception { + TEST_DATA = TestData.getTestDataOnFileSystem(); + } + + @Test + public void emptyInput() throws Throwable { + organizeImportTest("", "", config -> {}); + } + + @Test + public void defaultConfiguration() throws Throwable { + for (String testFile : Arrays.array("Simple", "Statics", "Wildcards")) { + try { + organizeImportTest(testFile, config -> {}); + } catch (ComparisonFailure e) { + throw new ComparisonFailure(testFile + " - " + e.getMessage(), e.getExpected(), e.getActual()); + } + } + } + + @Test + public void defaultPackage() throws Throwable { + String input = TEST_DATA.input("Simple").replaceFirst("package .+", ""); + String expected = TEST_DATA.afterOrganizedImports("Simple").replaceFirst("package .+", ""); + organizeImportTest(input, expected, config -> {}); + } + + @Test + public void invalidConfiguration() throws Throwable { + //Smoke test, no exceptions expected + organizeImportTest("", "", config -> { + config.put("invalid.key", "some.value"); + }); + organizeImportTest("", "", config -> { + config.put(JavaCore.COMPILER_SOURCE, "-42"); + }); + organizeImportTest("", "", config -> { + config.put(JavaCore.COMPILER_SOURCE, "Not an integer"); + }); + } + + @Test + public void customConfiguration() throws Throwable { + String defaultOrganizedInput = TEST_DATA.input("ImportConfiguration"); + organizeImportTest(defaultOrganizedInput, defaultOrganizedInput, config -> {}); + + String customOrganizedOutput = TEST_DATA.afterOrganizedImports("ImportConfiguration"); + organizeImportTest(defaultOrganizedInput, customOrganizedOutput, config -> { + config.put(CodeStyleConfiguration.ORGIMPORTS_IMPORTORDER, "foo;#foo;"); + }); + } + + private static void organizeImportTest(final String fileName, final Consumer config) throws Exception { + organizeImportTest(TEST_DATA.input(fileName), TEST_DATA.afterOrganizedImports(fileName), config); + } + + private static void organizeImportTest(final String input, final String expected, final Consumer config) throws Exception { + Properties properties = new Properties(); + config.accept(properties); + EclipseJdtOrganizeImportStepImpl formatter = new EclipseJdtOrganizeImportStepImpl(properties); + String output = formatter.format(input); + assertEquals("Unexpected import organization " + TestData.toString(properties), + expected, output); + } +} diff --git a/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/TestData.java b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/TestData.java index b319568896..98b6ebae34 100644 --- a/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/TestData.java +++ b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/TestData.java @@ -34,6 +34,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Properties; public class TestData { private static final String EXTENSION_INPUT = ".input"; @@ -84,4 +85,17 @@ private String read(final Path filePath) { throw new IllegalArgumentException(String.format("Failed to read '%1$s'.", filePath), e); } } + + public static String toString(Properties properties) { + StringBuilder result = new StringBuilder(); + result.append('['); + properties.forEach((k, v) -> { + result.append(k.toString()); + result.append('='); + result.append(v.toString()); + result.append(';'); + }); + result.append(']'); + return result.toString(); + } } diff --git a/_ext/eclipse-jdt/src/test/resources/Configuration.input b/_ext/eclipse-jdt/src/test/resources/ImportConfiguration.input similarity index 100% rename from _ext/eclipse-jdt/src/test/resources/Configuration.input rename to _ext/eclipse-jdt/src/test/resources/ImportConfiguration.input diff --git a/_ext/eclipse-jdt/src/test/resources/Configuration.organized b/_ext/eclipse-jdt/src/test/resources/ImportConfiguration.organized similarity index 100% rename from _ext/eclipse-jdt/src/test/resources/Configuration.organized rename to _ext/eclipse-jdt/src/test/resources/ImportConfiguration.organized