diff --git a/_ext/eclipse-jdt/build.gradle b/_ext/eclipse-jdt/build.gradle index e079a42c41..c1eb1eb4a0 100644 --- a/_ext/eclipse-jdt/build.gradle +++ b/_ext/eclipse-jdt/build.gradle @@ -10,9 +10,9 @@ ext { dependencies { compile "com.diffplug.spotless:spotless-eclipse-base:${VER_SPOTLESS_ECLISPE_BASE}" - compile("org.eclipse.jdt:org.eclipse.jdt.core:${VER_ECLIPSE_JDT_CORE}") { + 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' - exclude group: 'org.eclipse.platform', module: 'org.eclipse.core.filesystem' } } \ No newline at end of file diff --git a/_ext/eclipse-jdt/gradle.properties b/_ext/eclipse-jdt/gradle.properties index 3b035ebd6b..4b6cbfb8f2 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.8.0 +ext_version=4.11.0 ext_artifactId=spotless-eclipse-jdt ext_description=Eclipse's JDT formatter bundled for Spotless @@ -11,5 +11,5 @@ ext_group=com.diffplug.spotless ext_VER_JAVA=1.8 # Compile -VER_ECLIPSE_JDT_CORE=[3.12.0,4.0.0[ +VER_ECLIPSE_JDT_CORE_MANIPULATION=[1.11.0,2.0.0[ VER_SPOTLESS_ECLISPE_BASE=[3.0.0,4.0.0[ 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 new file mode 100644 index 0000000000..d94ad891ab --- /dev/null +++ b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImpl.java @@ -0,0 +1,117 @@ +/* + * 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.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.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.core.JavaCorePreferenceInitializer; +import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettingsConstants; + +import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; + +/** Clean-up step which calls out to the Eclipse JDT clean-up / import sorter. */ +public class EclipseJdtCleanUpStepImpl { + + /* 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; + + public EclipseJdtCleanUpStepImpl(Properties settings) throws Exception { + if (SpotlessEclipseFramework.setup( + core -> { + /* + * For the Clean-Up, the indexer needs to exists (but is not used). + * The indexer is not created in headless mode by the JDT. + * To signal a non-headless mode, the platform state needs to by active + * (it is only resolved by default). + */ + 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()); + + //Initialization of jdtConfiguration requires OS set + 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()); + })) { + new JavaCorePreferenceInitializer().initializeDefaultPreferences(); + initializeJdtUiDefaultSettings(); + } + jdtConfiguration = EclipseJdtFactory.createProject(settings); + } + + private static void initializeJdtUiDefaultSettings() { + 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"); + } + + public String organizeImport(String raw) throws Exception { + ICompilationUnit sourceContainer = EclipseJdtFactory.createJavaSource(raw, jdtConfiguration); + CompilationUnit ast = SharedASTProviderCore.getAST(sourceContainer, SharedASTProviderCore.WAIT_YES, null); + OrganizeImportsOperation formatOperation = new OrganizeImportsOperation(sourceContainer, ast, true, false, true, null); + try { + formatOperation.run(null); + return sourceContainer.getSource(); + } catch (OperationCanceledException | CoreException e) { + throw new IllegalArgumentException("Invalid java syntax for formatting.", e); + } + } + +} diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFactory.java b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFactory.java new file mode 100644 index 0000000000..2a8e8bc534 --- /dev/null +++ b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFactory.java @@ -0,0 +1,176 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.core.internal.resources.OS; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IBuffer; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.core.BufferManager; +import org.eclipse.jdt.internal.core.CompilationUnit; +import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; +import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.internal.core.PackageFragment; + +/** + * 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). + *

+ */ +class EclipseJdtFactory extends OS { + + private final static String ROOT_AS_SRC = ""; + private final static String PROJECT_NAME = "spotless"; + private final static String SOURCE_NAME = "source.java"; + private final static AtomicInteger UNIQUE_PROJECT_ID = new AtomicInteger(0); + + private final static Map DEFAULT_OPTIONS; + + static { + Map defaultOptions = new HashMap<>(); + defaultOptions.put(JavaCore.COMPILER_SOURCE, getJavaCoreVersion()); + DEFAULT_OPTIONS = Collections.unmodifiableMap(defaultOptions); + } + + private static String getJavaCoreVersion() { + final String javaVersion = System.getProperty("java.version"); + final List orderedSupportedCoreVersions = JavaCore.getAllVersions(); + for (String coreVersion : orderedSupportedCoreVersions) { + if (javaVersion.startsWith(coreVersion)) { + return coreVersion; + } + } + 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 + */ + public final static IJavaProject createProject(Properties settings) throws Exception { + String uniqueProjectName = String.format("%s-%d", PROJECT_NAME, UNIQUE_PROJECT_ID.incrementAndGet()); + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(uniqueProjectName); + // The project must be open before items (natures, folders, sources, ...) can be created + project.create(null); + project.open(0, null); + //If the project nature is not set, things like AST are not created for the Java projects + IProjectDescription description = project.getDescription(); + description.setNatureIds(new String[]{JavaCore.NATURE_ID}); + project.setDescription(description, null); + IJavaProject jProject = JavaCore.create(project); + + Map settingsMap = new HashMap<>(DEFAULT_OPTIONS); + settings.forEach((key, value) -> { + settingsMap.put(key.toString(), value.toString()); + }); + jProject.setOptions(settingsMap); + + // Eclipse source files require an existing source folder for creation + 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); + + // Eclipse clean-up requires an existing source file + pkg.createCompilationUnit(SOURCE_NAME, "", true, null); + + // + disableSecondaryTypes(project); + + return jProject; + } + + private static void disableSecondaryTypes(IProject project) { + JavaModelManager.PerProjectInfo info = JavaModelManager.getJavaModelManager().getPerProjectInfo(project, true); + info.secondaryTypes = new Hashtable>(); + } + + public static ICompilationUnit createJavaSource(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); + } + + /** Spotless keeps compilation units in RAM as long as they are worked on. */ + private static class RamCompilationUnit extends CompilationUnit { + + //Each RMA compilation unit has its own buffer manager. A drop is therefore prevented. + private final RamBufferManager manager; + + RamCompilationUnit(PackageFragment parent, String contents) { + super(parent, SOURCE_NAME, DefaultWorkingCopyOwner.PRIMARY); + manager = new RamBufferManager(); + IBuffer buffer = BufferManager.createBuffer(this); + buffer.setContents(contents.toCharArray()); + manager.add(buffer); + } + + @Override + public boolean exists() { + return true; + } + + @Override + protected BufferManager getBufferManager() { + return manager; + } + + @Override + public void save(IProgressMonitor pm, boolean force) throws JavaModelException { + //RAM CU is never saved to disk + } + + @Override + public ICompilationUnit getWorkingCopy(IProgressMonitor monitor) throws JavaModelException { + throw new UnsupportedOperationException("Spotless RAM compilation unit cannot be copied."); + } + + @Override + public boolean equals(Object obj) { + return this == obj; //Working copies are not supported + } + } + + /** Work-around required package privileges when adding buffer for manager singleton */ + private static class RamBufferManager extends BufferManager { + void add(IBuffer buffer) { + addBuffer(buffer); + } + } +} diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/JavaContentTypeManager.java b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/JavaContentTypeManager.java new file mode 100644 index 0000000000..4f16b43741 --- /dev/null +++ b/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/JavaContentTypeManager.java @@ -0,0 +1,60 @@ +/* + * 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 org.eclipse.core.internal.content.ContentType; +import org.eclipse.core.internal.content.ContentTypeCatalog; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.jdt.internal.compiler.util.SuffixConstants; + +import com.diffplug.spotless.extra.eclipse.base.service.NoContentTypeSpecificHandling; + +/** + * Java compilation unit validation requires Java content type to be recognized. + *

+ * See {@code org.eclipse.jdt.internal.core.util.Util} for details. + *

+ */ +public class JavaContentTypeManager extends NoContentTypeSpecificHandling { + + private final IContentType contentType; + + public JavaContentTypeManager() { + contentType = ContentType.createContentType( + new ContentTypeCatalog(null, 0), + "", + "", + (byte) 0, + new String[]{SuffixConstants.EXTENSION_java, SuffixConstants.EXTENSION_JAVA}, + new String[0], + new String[0], + "", + "", + null, + null); + } + + @Override + public IContentType getContentType(String contentTypeIdentifier) { + return contentType; + } + + @Override + public IContentType[] getAllContentTypes() { + return new IContentType[]{contentType}; + } + +} 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 new file mode 100644 index 0000000000..14ddca94d6 --- /dev/null +++ b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtCleanUpStepImplTest.java @@ -0,0 +1,71 @@ +/* + * 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.junit.BeforeClass; +import org.junit.Test; + +/** Eclipse JDT wrapper integration tests */ +public class EclipseJdtCleanUpStepImplTest { + 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 nominal() throws Throwable { + organizeImportTest("Simple", config -> {}); + } + + 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); + EclipseJdtCleanUpStepImpl formatter = new EclipseJdtCleanUpStepImpl(properties); + String output = formatter.organizeImport(input); + assertEquals("Unexpected import organization " + 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/TestData.java b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/TestData.java new file mode 100644 index 0000000000..b319568896 --- /dev/null +++ b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/TestData.java @@ -0,0 +1,87 @@ +/* + * 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; +/* + * 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. + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class TestData { + private static final String EXTENSION_INPUT = ".input"; + private static final String EXTENSION_ORGANIZED_IMPORTS = ".organized"; + private static final String EXTENSION_CLEAN_UP = ".cleanup"; + + public static TestData getTestDataOnFileSystem() { + final String userDir = System.getProperty("user.dir", "."); + Path dataPath = Paths.get(userDir, "src", "test", "resources"); + if (Files.isDirectory(dataPath)) { + return new TestData(dataPath); + } + throw new IllegalArgumentException("Test data not found:" + dataPath.toString()); + } + + private final Path resourcesPath; + + private TestData(Path resourcesPath) { + this.resourcesPath = resourcesPath.toAbsolutePath(); + if (!Files.isDirectory(resourcesPath)) { + throw new IllegalArgumentException(String.format("'%1$s' is not a directory.", resourcesPath)); + } + } + + public String input(final String fileName) throws Exception { + Path filePath = resourcesPath.resolve(fileName + EXTENSION_INPUT); + return read(filePath); + } + + public String afterOrganizedImports(final String fileName) { + Path filePath = resourcesPath.resolve(fileName + EXTENSION_ORGANIZED_IMPORTS); + return read(filePath); + } + + public String afterCleanUp(final String fileName) { + Path filePath = resourcesPath.resolve(fileName + EXTENSION_CLEAN_UP); + return read(filePath); + } + + private String read(final Path filePath) { + if (!Files.isRegularFile(filePath)) { + throw new IllegalArgumentException(String.format("'%1$s' is not a regular file.", filePath)); + } + try { + String checkedOutFileContent = new String(java.nio.file.Files.readAllBytes(filePath), "ASCII"); + return checkedOutFileContent.replace("\r", ""); //Align GIT end-of-line normalization + } catch (IOException e) { + throw new IllegalArgumentException(String.format("Failed to read '%1$s'.", filePath), e); + } + } +} diff --git a/_ext/eclipse-jdt/src/test/resources/Simple.input b/_ext/eclipse-jdt/src/test/resources/Simple.input new file mode 100644 index 0000000000..92bbcc9c50 --- /dev/null +++ b/_ext/eclipse-jdt/src/test/resources/Simple.input @@ -0,0 +1,25 @@ +package bar.foo; + +import javax.net.SocketFactory; //Wrong order in Other group + +import java.net.Socket; //Wrong within group +import java.lang.System; //Can be removed since implicitly imported +import java.io.IOException; +import java.io.PrintStream; //Can be found and is not used + +import foo.bar.C; //Cannot be found and is not used +import foo.bar.B; //Cannot be found but is used + + +class A { + private static final SocketFactory FACTORY; + static { + FACTORY = SocketFactory.getDefault(); + } + public static Socket open() throws IOException { + return FACTORY.createSocket(); + } + public static B create() { + return new B(); + } +} diff --git a/_ext/eclipse-jdt/src/test/resources/Simple.organized b/_ext/eclipse-jdt/src/test/resources/Simple.organized new file mode 100644 index 0000000000..96a92d3fbf --- /dev/null +++ b/_ext/eclipse-jdt/src/test/resources/Simple.organized @@ -0,0 +1,22 @@ +package bar.foo; + +import java.io.IOException; +import java.net.Socket; //Wrong within group + +import javax.net.SocketFactory; //Wrong order in Other group + +import foo.bar.B; //Cannot be found but is used + + +class A { + private static final SocketFactory FACTORY; + static { + FACTORY = SocketFactory.getDefault(); + } + public static Socket open() throws IOException { + return FACTORY.createSocket(); + } + public static B create() { + return new B(); + } +}