Skip to content

Commit 5b67a71

Browse files
committed
GH-354: Fail if no compilation units are found/filtered classes not found
- Add new method: PathFileObject#getBinaryName - JctCompilationFactoryImpl will now error if no initial compilation units were found with a clearer error message. - JctCompilationFactoryImpl will now error if any of the explicitly provided class names are not found as compilation units. - JctCompilationFactoryImpl will now error if the list of provided class names is non-null but empty.
1 parent f9d4e4f commit 5b67a71

File tree

4 files changed

+75
-19
lines changed

4 files changed

+75
-19
lines changed

java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/JctCompilationFactory.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ public interface JctCompilationFactory {
4343
* should be discovered automatically.
4444
* @return the compilation result that contains whether the compiler succeeded or failed, amongst
4545
* other information.
46-
* @throws JctCompilerException if compiler raises an unhandled exception and cannot complete.
46+
* @throws JctCompilerException if any prerequisites fail, such as no compilation units being
47+
* found, or if the underlying JSR-199 compiler raises an unhandled exception and cannot
48+
* complete when invoked.
4749
*/
4850
JctCompilation createCompilation(
4951
List<String> flags,

java-compiler-testing/src/main/java/io/github/ascopes/jct/compilers/impl/JctCompilationFactoryImpl.java

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.github.ascopes.jct.compilers.impl;
1717

1818
import static java.util.Objects.requireNonNull;
19+
import static java.util.function.Function.identity;
1920
import static java.util.stream.Collectors.toList;
2021

2122
import io.github.ascopes.jct.compilers.JctCompilation;
@@ -27,15 +28,13 @@
2728
import io.github.ascopes.jct.filemanagers.JctFileManager;
2829
import io.github.ascopes.jct.filemanagers.LoggingMode;
2930
import io.github.ascopes.jct.filemanagers.PathFileObject;
30-
import io.github.ascopes.jct.utils.FileUtils;
3131
import io.github.ascopes.jct.utils.IterableUtils;
3232
import java.io.IOException;
3333
import java.io.OutputStreamWriter;
3434
import java.util.Collection;
3535
import java.util.HashSet;
3636
import java.util.List;
3737
import java.util.Set;
38-
import java.util.function.Predicate;
3938
import java.util.stream.Collectors;
4039
import javax.tools.JavaCompiler;
4140
import javax.tools.JavaFileObject;
@@ -92,6 +91,12 @@ private JctCompilation createCheckedCompilation(
9291
) throws Exception {
9392
var compilationUnits = findFilteredCompilationUnits(fileManager, classNames);
9493

94+
if (compilationUnits.isEmpty()) {
95+
throw classNames == null
96+
? new JctCompilerException("No compilation units were found in the given workspace")
97+
: new JctCompilerException("No compilation units were found for the given class names");
98+
}
99+
95100
// Do not close stdout, it breaks test engines, especially IntellIJ.
96101
var writer = new TeeWriter(new OutputStreamWriter(System.out, compiler.getLogCharset()));
97102

@@ -163,10 +168,11 @@ private Set<JavaFileObject> findFilteredCompilationUnits(
163168
return compilationUnits;
164169
}
165170

166-
return compilationUnits
167-
.stream()
168-
.filter(pathFileObjectIn(classNames))
169-
.collect(Collectors.toSet());
171+
if (classNames.isEmpty()) {
172+
throw new JctCompilerException("The list of explicit class names to compile is empty");
173+
}
174+
175+
return filterCompilationUnitsByBinaryNames(compilationUnits, classNames);
170176
}
171177

172178
private Set<JavaFileObject> findCompilationUnits(JctFileManager fileManager) throws IOException {
@@ -190,20 +196,43 @@ private Set<JavaFileObject> findCompilationUnits(JctFileManager fileManager) thr
190196
objects.addAll(fileManager.list(location, "", Set.of(Kind.SOURCE), true));
191197
}
192198

193-
if (objects.isEmpty()) {
194-
throw new JctCompilerException(
195-
"No compilation units were found. Did you forget to add something?"
196-
);
197-
}
198-
199199
return objects;
200200
}
201201

202-
private Predicate<JavaFileObject> pathFileObjectIn(Collection<String> classNames) {
203-
return fileObject -> {
204-
var pathFileObject = (PathFileObject) fileObject;
205-
var binaryName = FileUtils.pathToBinaryName(pathFileObject.getRelativePath());
206-
return classNames.contains(binaryName);
207-
};
202+
private Set<JavaFileObject> filterCompilationUnitsByBinaryNames(
203+
Set<JavaFileObject> compilationUnits,
204+
Collection<String> classNames
205+
) {
206+
var binaryNamesToCompilationUnits = compilationUnits
207+
.stream()
208+
// Assumption that we always use this class internally. Technically unsafe, but we don't
209+
// care too much as the implementation should conform to this anyway. We just cannot enforce
210+
// it due to covariance rules.
211+
.map(PathFileObject.class::cast)
212+
.collect(Collectors.toMap(
213+
PathFileObject::getBinaryName,
214+
identity()
215+
));
216+
217+
var filteredCompilationUnits = new HashSet<JavaFileObject>();
218+
219+
for (var className : classNames) {
220+
var compilationUnit = binaryNamesToCompilationUnits.get(className);
221+
if (compilationUnit == null) {
222+
throw new JctCompilerException(
223+
"No compilation unit matching " + className + " found in the provided sources"
224+
);
225+
}
226+
227+
filteredCompilationUnits.add(compilationUnit);
228+
}
229+
230+
LOGGER.atDebug()
231+
.setMessage("Filtered {} candidate compilation units down to {} final compilation units")
232+
.addArgument(compilationUnits::size)
233+
.addArgument(filteredCompilationUnits::size)
234+
.log();
235+
236+
return filteredCompilationUnits;
208237
}
209238
}

java-compiler-testing/src/main/java/io/github/ascopes/jct/filemanagers/PathFileObject.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,17 @@ public Modifier getAccessLevel() {
150150
return unknown();
151151
}
152152

153+
/**
154+
* Get the inferred binary name of the file object.
155+
*
156+
* @return the inferred binary name.
157+
* @since 0.2.1
158+
*/
159+
@API(since = "0.2.1", status = Status.STABLE)
160+
public String getBinaryName() {
161+
return FileUtils.pathToBinaryName(relativePath);
162+
}
163+
153164
/**
154165
* Read the character content of the file into memory and decode it using the default character
155166
* set.

java-compiler-testing/src/test/java/io/github/ascopes/jct/tests/unit/filemanagers/PathFileObjectTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static org.mockito.Mockito.mock;
2828

2929
import io.github.ascopes.jct.filemanagers.PathFileObject;
30+
import io.github.ascopes.jct.utils.FileUtils;
3031
import java.io.IOException;
3132
import java.io.StringWriter;
3233
import java.nio.charset.MalformedInputException;
@@ -224,6 +225,19 @@ void getAccessLevelReturnsNull() {
224225
assertThat(fileObject.getAccessLevel()).isNull();
225226
}
226227

228+
@DisplayName(".getBinaryName() gets the binary name of the relative path")
229+
@Test
230+
void getBinaryNameGetsTheBinaryNameOfTheRelativePath() {
231+
// Given
232+
var relativePath = someRelativePath().resolve("Cat.java");
233+
var expectedBinaryName = FileUtils.pathToBinaryName(relativePath);
234+
var fileObject = new PathFileObject(someLocation(), someAbsolutePath(), relativePath);
235+
236+
// Then
237+
assertThat(fileObject.getBinaryName())
238+
.isEqualTo(expectedBinaryName);
239+
}
240+
227241
@DisplayName(".getCharContent(...) returns the character content")
228242
@ValueSource(booleans = {true, false})
229243
@ParameterizedTest(name = "for ignoreEncodingErrors={0}")

0 commit comments

Comments
 (0)