Skip to content

Commit

Permalink
Restructure package detectors.
Browse files Browse the repository at this point in the history
  • Loading branch information
uhafner committed Nov 20, 2024
1 parent 4d30cb6 commit 00cee56
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 328 deletions.
11 changes: 2 additions & 9 deletions src/main/java/edu/hm/hafner/util/CSharpNamespaceDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,15 @@

import java.util.regex.Pattern;

import edu.hm.hafner.util.PackageDetectors.FileSystem;

/**
* Detects the namespace of a C# workspace file.
*
* @author Ullrich Hafner
*/
class CSharpNamespaceDetector extends AbstractPackageDetector {
class CSharpNamespaceDetector extends PackageDetector {
private static final Pattern NAMESPACE_PATTERN = Pattern.compile("^\\s*namespace\\s+([^{]*)\\s*\\{?\\s*$");

CSharpNamespaceDetector() {
this(new FileSystem());
}

@VisibleForTesting
CSharpNamespaceDetector(final FileSystem fileSystem) {
CSharpNamespaceDetector(final FileSystemFacade fileSystem) {
super(fileSystem);
}

Expand Down
11 changes: 2 additions & 9 deletions src/main/java/edu/hm/hafner/util/JavaPackageDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,16 @@

import java.util.regex.Pattern;

import edu.hm.hafner.util.PackageDetectors.FileSystem;

/**
* Detects the package name of a Java file.
*
* @author Ullrich Hafner
*/
class JavaPackageDetector extends AbstractPackageDetector {
class JavaPackageDetector extends PackageDetector {
private static final Pattern PACKAGE_PATTERN = Pattern.compile(
"^\\s*package\\s*([a-z]+[.\\w]*)\\s*;.*");

JavaPackageDetector() {
this(new FileSystem());
}

@VisibleForTesting
JavaPackageDetector(final FileSystem fileSystem) {
JavaPackageDetector(final FileSystemFacade fileSystem) {
super(fileSystem);
}

Expand Down
10 changes: 2 additions & 8 deletions src/main/java/edu/hm/hafner/util/KotlinPackageDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,17 @@

import java.util.regex.Pattern;

import edu.hm.hafner.util.PackageDetectors.FileSystem;

/**
* Detects the package name of a Kotlin file.
*
* @author Bastian Kersting
*/
class KotlinPackageDetector extends AbstractPackageDetector {
class KotlinPackageDetector extends PackageDetector {
private static final Pattern PACKAGE_PATTERN = Pattern.compile(
"^\\s*package\\s*([a-z]+[.\\w]*)\\s*.*");

KotlinPackageDetector() {
this(new FileSystem());
}

@VisibleForTesting
KotlinPackageDetector(final FileSystem fileSystem) {
KotlinPackageDetector(final FileSystemFacade fileSystem) {
super(fileSystem);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,33 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.apache.commons.io.input.BOMInputStream;

import edu.hm.hafner.util.PackageDetectors.*;

import static edu.hm.hafner.util.PackageDetectors.*;
import com.google.errorprone.annotations.MustBeClosed;

/**
* Base class for package detectors.
*
* @author Ullrich Hafner
*/
abstract class AbstractPackageDetector {
private final FileSystem fileSystem;
abstract class PackageDetector {
private final FileSystemFacade fileSystem;

/**
* Creates a new instance of {@link AbstractPackageDetector}.
* Creates a new instance of {@link PackageDetector}.
*
* @param fileSystem
* file system facade
*/
AbstractPackageDetector(final FileSystem fileSystem) {
PackageDetector(final FileSystemFacade fileSystem) {
this.fileSystem = fileSystem;
}

Expand All @@ -44,21 +45,19 @@ abstract class AbstractPackageDetector {
*
* @return the detected package or namespace name
*/
public String detectPackageName(final String fileName, final Charset charset) {
if (accepts(fileName)) {
try (InputStream stream = fileSystem.openFile(fileName)) {
return detectPackageName(stream, charset);
}
catch (IOException | InvalidPathException ignore) {
// ignore IO errors
}
public Optional<String> detectPackageName(final String fileName, final Charset charset) {
try (var stream = fileSystem.openFile(fileName)) {
return detectPackageName(stream, charset);
}
catch (IOException | InvalidPathException ignore) {
// ignore IO errors
}
return UNDEFINED_PACKAGE;
return Optional.empty();
}

@VisibleForTesting
String detectPackageName(final InputStream stream, final Charset charset) throws IOException {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(BOMInputStream.builder().setInputStream(stream).get(), charset))) {
private Optional<String> detectPackageName(final InputStream stream, final Charset charset) throws IOException {
try (var buffer = new BufferedReader(
new InputStreamReader(BOMInputStream.builder().setInputStream(stream).get(), charset))) {
return detectPackageName(buffer.lines());
}
}
Expand All @@ -72,17 +71,18 @@ String detectPackageName(final InputStream stream, final Charset charset) throws
*
* @return the detected package or namespace name
*/
String detectPackageName(final Stream<String> lines) {
private Optional<String> detectPackageName(final Stream<String> lines) {
Pattern pattern = getPattern();
return lines.map(pattern::matcher)
.filter(Matcher::matches)
.findFirst()
.map(matcher -> matcher.group(1))
.orElse(UNDEFINED_PACKAGE).trim();
.map(String::trim);
}

/**
* Returns the Pattern for the Package Name in this kind of file.
*
* @return the Pattern.
*/
abstract Pattern getPattern();
Expand All @@ -96,4 +96,15 @@ String detectPackageName(final Stream<String> lines) {
* @return {@code true} if the classifier accepts the specified file for processing.
*/
abstract boolean accepts(String fileName);

/**
* Facade for file system operations. May be replaced by stubs in test cases.
*/
@VisibleForTesting
static class FileSystemFacade {
@MustBeClosed
InputStream openFile(final String fileName) throws IOException, InvalidPathException {
return Files.newInputStream(Paths.get(fileName));
}
}
}
31 changes: 31 additions & 0 deletions src/main/java/edu/hm/hafner/util/PackageDetectorFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package edu.hm.hafner.util;

import edu.hm.hafner.util.PackageDetector.FileSystemFacade;

/**
* Factory to create package detectors.
*
* @author Ullrich Hafner
*/
public final class PackageDetectorFactory {
/**
* Creates a new package detector runner that uses the detectors for Java, Kotlin, and C#.
*
* @return the package detector runner
*/
public static PackageDetectorRunner createPackageDetectors() {
return createPackageDetectors(new FileSystemFacade());
}

@VisibleForTesting
static PackageDetectorRunner createPackageDetectors(final FileSystemFacade facade) {
return new PackageDetectorRunner(
new JavaPackageDetector(facade),
new KotlinPackageDetector(facade),
new CSharpNamespaceDetector(facade));
}

private PackageDetectorFactory() {
// prevents instantiation
}
}
37 changes: 37 additions & 0 deletions src/main/java/edu/hm/hafner/util/PackageDetectorRunner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package edu.hm.hafner.util;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
* Detects package or namespace names of files in the file system.
*
* @author Ullrich Hafner
*/
public class PackageDetectorRunner {
private final List<PackageDetector> detectors;

PackageDetectorRunner(final PackageDetector... detectors) {
this.detectors = Arrays.asList(detectors);
}

/**
* Detects the package name of the specified file based on several detector strategies.
*
* @param fileName
* the filename of the file to scan
* @param charset
* the charset to use when reading the source files
*
* @return the detected package name or {@link Optional#empty()} if no package name could be detected
*/
public Optional<String> detectPackageName(final String fileName, final Charset charset) {
return detectors.stream()
.filter(detector -> detector.accepts(fileName))
.map(detector -> detector.detectPackageName(fileName, charset))
.flatMap(Optional::stream)
.findFirst();
}
}
58 changes: 0 additions & 58 deletions src/main/java/edu/hm/hafner/util/PackageDetectors.java

This file was deleted.

59 changes: 59 additions & 0 deletions src/test/java/edu/hm/hafner/util/PackageDetectorRunnerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package edu.hm.hafner.util;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import edu.hm.hafner.util.PackageDetector.FileSystemFacade;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

/**
* Tests the class {@link PackageDetectorRunner}.
*
* @author Ullrich Hafner
*/
class PackageDetectorRunnerTest extends ResourceTest {
@ParameterizedTest(name = "{index} => file={0}, expected package={1}")
@CsvSource({
"MavenJavaTest.txt.java, hudson.plugins.tasks.util",
"ActionBinding.cs, Avaloq.SmartClient.Utilities",
"KotlinTest.txt.kt, edu.hm.kersting",
})
void shouldExtractPackageNames(final String fileName, final String expectedPackage) throws IOException {
assertThat(detect(fileName)).contains(expectedPackage);
}

@ParameterizedTest(name = "{index} => file={0}, no package found")
@ValueSource(strings = {"MavenJavaTest.txt", "empty.txt", "KotlinTest.txt"})
void shouldNotAcceptFile(final String fileName) throws IOException {
assertThat(detect(fileName)).isEmpty();
}

private Optional<String> detect(final String fileName) throws IOException {
try (InputStream stream = asInputStream(fileName)) {
var fileSystem = mock(FileSystemFacade.class);
when(fileSystem.openFile(fileName)).thenReturn(stream);

var detectors = PackageDetectorFactory.createPackageDetectors(fileSystem);

return detectors.detectPackageName(fileName, StandardCharsets.UTF_8);
}
}

@Test
void shouldHandleException() throws IOException {
var fileSystem = mock(FileSystemFacade.class);
when(fileSystem.openFile(anyString())).thenThrow(new IOException("Simulated"));

assertThat(PackageDetectorFactory.createPackageDetectors(fileSystem)
.detectPackageName("file.java", StandardCharsets.UTF_8)).isEmpty();
}
}
Loading

0 comments on commit 00cee56

Please sign in to comment.