Skip to content

Commit

Permalink
Import package detector from analysis-model.
Browse files Browse the repository at this point in the history
  • Loading branch information
uhafner committed Nov 19, 2024
1 parent 37da6da commit 4d30cb6
Show file tree
Hide file tree
Showing 11 changed files with 519 additions and 0 deletions.
99 changes: 99 additions & 0 deletions src/main/java/edu/hm/hafner/util/AbstractPackageDetector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package edu.hm.hafner.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.file.InvalidPathException;
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.*;

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

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

/**
* Detects the package or namespace name of the specified file.
*
* @param fileName
* the file name of the file to scan
* @param charset
* the charset to use when reading the source files
*
* @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
}
}
return UNDEFINED_PACKAGE;
}

@VisibleForTesting
String detectPackageName(final InputStream stream, final Charset charset) throws IOException {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(BOMInputStream.builder().setInputStream(stream).get(), charset))) {
return detectPackageName(buffer.lines());
}
}

/**
* Detects the package or namespace name of the specified input stream. The stream will be closed automatically by
* the caller of this method.
*
* @param lines
* the content of the file to scan
*
* @return the detected package or namespace name
*/
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();
}

/**
* Returns the Pattern for the Package Name in this kind of file.
* @return the Pattern.
*/
abstract Pattern getPattern();

/**
* Returns whether this classifier accepts the specified file for processing.
*
* @param fileName
* the file name
*
* @return {@code true} if the classifier accepts the specified file for processing.
*/
abstract boolean accepts(String fileName);
}
34 changes: 34 additions & 0 deletions src/main/java/edu/hm/hafner/util/CSharpNamespaceDetector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package edu.hm.hafner.util;

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 {
private static final Pattern NAMESPACE_PATTERN = Pattern.compile("^\\s*namespace\\s+([^{]*)\\s*\\{?\\s*$");

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

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

@Override
public boolean accepts(final String fileName) {
return fileName.endsWith(".cs");
}

@Override
Pattern getPattern() {
return NAMESPACE_PATTERN;
}
}

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

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 {
private static final Pattern PACKAGE_PATTERN = Pattern.compile(
"^\\s*package\\s*([a-z]+[.\\w]*)\\s*;.*");

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

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

@Override
Pattern getPattern() {
return PACKAGE_PATTERN;
}

@Override
public boolean accepts(final String fileName) {
return fileName.endsWith(".java");
}
}
34 changes: 34 additions & 0 deletions src/main/java/edu/hm/hafner/util/KotlinPackageDetector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package edu.hm.hafner.util;

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 {
private static final Pattern PACKAGE_PATTERN = Pattern.compile(
"^\\s*package\\s*([a-z]+[.\\w]*)\\s*.*");

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

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

@Override
Pattern getPattern() {
return PACKAGE_PATTERN;
}

@Override
boolean accepts(final String fileName) {
return fileName.endsWith(".kt");
}
}
58 changes: 58 additions & 0 deletions src/main/java/edu/hm/hafner/util/PackageDetectors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package edu.hm.hafner.util;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.util.List;

import com.google.errorprone.annotations.MustBeClosed;

/**
* Provides convenient methods to determine the package or namespace names of a file.
*
* @author Ullrich Hafner
*/
class PackageDetectors {
/** If no package could be assigned this value is used as package name. */
static final String UNDEFINED_PACKAGE = "-";

private final List<AbstractPackageDetector> detectors;

@VisibleForTesting
PackageDetectors(final List<AbstractPackageDetector> detectors) {
this.detectors = 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 package name or the String {@link #UNDEFINED_PACKAGE} if no package could be detected
*/
public String detectPackageName(final String fileName, final Charset charset) {
for (AbstractPackageDetector detector : detectors) {
if (detector.accepts(fileName)) {
return detector.detectPackageName(fileName, charset);
}
}
return UNDEFINED_PACKAGE;
}

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

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

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

import static edu.hm.hafner.util.assertions.Assertions.*;
import static org.mockito.Mockito.*;

/**
* Tests the class {@link PackageDetectors}.
*
* @author Ullrich Hafner
*/
class PackageDetectorsTest extends ResourceTest {
@ParameterizedTest(name = "{index} => file={0}, expected package={1}")
@CsvSource({
"MavenJavaTest.txt.java, hudson.plugins.tasks.util",
"ActionBinding.cs, Avaloq.SmartClient.Utilities",
"MavenJavaTest.txt, -",
"relative.txt, -",
"KotlinTest.txt, -",
"KotlinTest.txt.kt, edu.hm.kersting",
})
void shouldExtractPackageNames(final String fileName, final String expectedPackage) throws IOException {
try (InputStream stream = asInputStream(fileName)) {
FileSystem fileSystem = mock(FileSystem.class);
when(fileSystem.openFile(fileName)).thenReturn(stream);

ArrayList<AbstractPackageDetector> detectors = new ArrayList<>(Arrays.asList(
new JavaPackageDetector(fileSystem),
new CSharpNamespaceDetector(fileSystem),
new KotlinPackageDetector(fileSystem)
));

assertThat(new PackageDetectors(detectors).detectPackageName(fileName, StandardCharsets.UTF_8))
.isEqualTo(expectedPackage);
}
}
}
Loading

0 comments on commit 4d30cb6

Please sign in to comment.