Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
f096a14
build: gate the whole library's unit-test coverage at 100%
claude Sep 29, 2026
287395f
docs: run the coverage gate before pushing
claude Sep 29, 2026
ac78c73
test: cover classic spi value objects (LoggerContextVO, LoggingEventV…
claude Sep 29, 2026
993e8eb
test: pin caller-data laziness, throwable conversion and identity equ…
claude Sep 29, 2026
bc61476
refactor: drop unreachable SecurityException handlers in OptionHelper
claude Sep 29, 2026
deec093
test: cover ch.qos.logback.core.util classes fully
claude Sep 29, 2026
0ea690a
test: note the JDK dependency of LoaderTest's permission tests
claude Sep 29, 2026
e4b35a7
refactor: remove unreachable code from SaxEventRecorder and ElementPath
claude Sep 29, 2026
1912f39
test: cover GenericConfigurator and the joran event/spi classes
claude Sep 29, 2026
c78d060
test: kill surviving mutants in joran event/spi tests
claude Sep 29, 2026
d2711bc
fix: remove a dir emptied by archive cleanup in the same pass
claude Sep 29, 2026
b190678
refactor: fold Compressor's unreachable enum-switch defaults into NONE
claude Sep 29, 2026
7e62640
test: cover core.rolling.helper to 100% of lines and branches
claude Sep 29, 2026
698ff16
test: tighten core.rolling.helper tests against surviving mutants
claude Sep 29, 2026
d13cbd9
test: cover SMTPAppender and SMTPAppenderBase
claude Sep 29, 2026
36d3478
test: pin SMTP appender behavior that mutations slipped past
claude Sep 29, 2026
ac83838
test: cover core.net.server socket appenders and core.net.ssl factories
claude Sep 29, 2026
b353ef3
test: pin client-auth values and slash-prefixed listener addresses
claude Sep 29, 2026
07f0c60
fix: make CyclicBuffer.resize() work on partially filled buffers
claude Sep 29, 2026
486ddd3
refactor: fold unreachable enum-switch defaults into the last case in…
claude Sep 29, 2026
c7597f9
test: cover core.helpers and core.subst lines and branches
claude Sep 29, 2026
c8df5f3
test: spell the replacement character as a \uFFFD escape in Transform…
claude Sep 29, 2026
be7afea
test: cover classic Logger overloads, turbo replies and child creation
claude Sep 29, 2026
6324798
test: pin Logger.FQCN as the caller boundary of every print overload
claude Sep 29, 2026
2a3ae28
refactor: call Throwable.getSuppressed() directly in ThrowableProxy
claude Sep 29, 2026
b03d015
test: cover classic spi (CallerData, LoggingEvent, packaging data, ...)
claude Sep 29, 2026
524a3de
test: strengthen classic spi tests against surviving mutants
claude Sep 29, 2026
98933d3
refactor: drop impossible null check on ReconfigureOnChangeTask fallb…
claude Sep 29, 2026
9f37e55
test: cover ReconfigureOnChangeFilter and the classic turbo filters
claude Sep 29, 2026
5fecbcf
test: pin reset, Android properties and include filtering on reconfig…
claude Sep 29, 2026
ada2cd9
test: read the SMTP part's content type without naming DataHandler
claude Sep 29, 2026
e7a6c48
fix: start an SSL server from SimpleSSLSocketServer.main
claude Sep 29, 2026
316b66e
test: cover classic.net and classic.net.server receivers, servers and…
claude Sep 29, 2026
962c719
test: run SimpleSocketServer main tests on the plain JVM
claude Sep 29, 2026
a671e04
refactor: merge HardenedObjectInputStream's unreachable reflection ca…
claude Sep 29, 2026
3908174
refactor: make AbstractSocketAppender's dispatch loop fully testable
claude Sep 29, 2026
1459a32
test: cover the core.net appenders, connectors and syslog classes
claude Sep 29, 2026
4b5b16f
test: pin core.net behaviors that surviving mutants exposed
claude Sep 29, 2026
7226724
fix: write OutputStreamAppender events through writeOut() so prudent …
claude Sep 29, 2026
ae1fb2b
test: cover ch.qos.logback.core appender bases, context and file/stre…
claude Sep 29, 2026
994d883
test: tighten core appender tests against surviving mutants
claude Sep 29, 2026
0786f9a
fix: report an unterminated quoted pattern option as a ScanException
claude Sep 29, 2026
a509ab2
refactor: fold TokenStream's unreachable enum-switch defaults into cases
claude Sep 29, 2026
a8c8aa2
test: cover the core pattern parser (Parser, Compiler, tokenizers, no…
claude Sep 29, 2026
92885d5
test: pin option trimming and escape after ')' in the pattern tokenizers
claude Sep 29, 2026
f9acb7b
test: cover classic.pattern converters and abbreviators
claude Sep 29, 2026
aa381a2
test: pin common-frame elision and root-cause-first suppressed prefix
claude Sep 29, 2026
73f4b1d
test: cover Level, LoggerContext, filters, evaluators, layouts and mi…
claude Sep 29, 2026
3b6665c
test: pin XMLLayout buffer reset and bound AsyncAppender flush wait
claude Sep 29, 2026
d9cda05
test: cover HTML layouts and the org.slf4j.impl binders
claude Sep 29, 2026
f60a140
test: pin HTML escaping and logger caching; make stderr checks order-…
claude Sep 29, 2026
7ffe2dc
refactor: drop unreachable branches in joran.util introspection
claude Sep 29, 2026
01f221f
test: cover ConfigurationWatchListUtil, joran.util beans and property…
claude Sep 29, 2026
401b6ba
test: pin setter-type priority and null conversions in joran.util
claude Sep 29, 2026
2db148e
refactor: drop ContextInitializer's always-true search flags
claude Sep 29, 2026
622a54a
test: cover classic joran actions and classic.util helpers
claude Sep 29, 2026
cf6578f
test: pin status levels and MDC key-view semantics in classic tests
claude Sep 29, 2026
36c69a7
test: cover core boolex, encoder, filter, hook, read and status classes
claude Sep 29, 2026
da0432a
test: fail fast if DefaultShutdownHook's interrupted sleep regresses
claude Sep 29, 2026
c674d89
refactor: let SYSTEM share the default branch of ActionUtil's scope s…
claude Sep 29, 2026
560337b
refactor: drop AbstractIncludeAction's unreachable MalformedURLExcept…
claude Sep 29, 2026
aad9691
test: cover Action, ActionConst and the evaluator, appender, appender…
claude Sep 29, 2026
3097746
test: pin attribute resets, substitution and empty attributes in jora…
claude Sep 29, 2026
c69494a
test: stop SocketAppenderMessageLossTest timing out on a loaded runner
claude Sep 29, 2026
218fef5
refactor: route FixedWindowRollingPolicy's enum-switch default to ZIP
claude Sep 29, 2026
9f385de
test: cover core.recovery resilient streams and core.rolling policies
claude Sep 29, 2026
e10ddaf
test: pin window boundaries and the one-time no-context warning
claude Sep 29, 2026
af9f9af
refactor: drop unreachable FormatInfo.valueOf part checks
claude Sep 29, 2026
f716687
test: cover core.pattern converters, layouts, colors and escaping
claude Sep 29, 2026
e3efbc8
test: pin core.pattern padding bounds, parse errors and post-compile …
claude Sep 29, 2026
3711bc4
test: cover core.spi, core.sift and classic.sift to 100%
claude Sep 29, 2026
2e8795d
test: pin status origins and event timestamps in spi/sift tests
claude Sep 29, 2026
87129c1
refactor: fold the nested-property IAs' unreachable enum-switch defaults
claude Sep 29, 2026
91c260b
test: cover the core joran actions (property, include, nested IAs, ...)
claude Sep 29, 2026
0548e22
test: make the timestamp test locale-independent; pin joran action re…
claude Sep 29, 2026
22c2df0
test: cover the unreadable-active-file branch on any OS and user
claude Sep 29, 2026
77934f6
docs: note SimpleSocketServer.doMain's divergence from upstream
claude Sep 29, 2026
9497c8b
revert: keep OutputStreamAppender.subAppend() off writeOut()
claude Sep 29, 2026
89c09b2
test: cover SocketReceiver's interrupted wait deterministically
claude Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .claude/skills/phase-start/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,14 @@ flavors separately.
Write the failing test first where the phase fixes a bug. Before pushing, run what CI runs:

```
./gradlew lint testJdk11DebugUnitTest testJdk8DebugUnitTest
./gradlew lint testJdk11DebugUnitTest testJdk8DebugUnitTest verifyCoverage
./scripts/check-architecture-docs.sh
```

`verifyCoverage` fails unless the unit tests cover every line and branch of every class, so new
or changed code comes with the tests that cover it. When it fails, `./gradlew
coverageReportJdk11Debug` and `scripts/coverage-gaps.py` on the XML report list what is missing.

Open the PR as a draft (see `AGENTS.md`), with a Conventional Commits title and a short
"deferred work considered" note naming what you folded in and what you deliberately left. Two
lines. It is the record that the check happened, and it is what lets the next phase trust that
Expand Down
6 changes: 4 additions & 2 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ sockets, syslog, email).
- **Tests:** JUnit tests live under `logback-android/src/test/java`, except the
tests of the Android-specific layer (`ch.qos.logback.{classic,core}.android`),
which are written in Kotlin under `logback-android/src/test/kotlin`. Run with
`./gradlew test`. That layer is gated at 100% line and branch coverage
(`./gradlew verifyAndroidLayerCoverage`); keep it there when changing it.
`./gradlew test`. The whole library, Java and Kotlin, is gated at 100% line
and branch coverage (`./gradlew verifyCoverage`; `scripts/coverage-gaps.py`
lists what a report misses); a change must come with the tests that keep it
there.
- **Upstream parity:** Much of the code mirrors upstream logback. Prefer changes
that stay close to upstream behavior and naming so the port remains easy to
sync.
Expand Down
26 changes: 20 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,32 @@ jobs:
- name: Unit tests (jdk8 variant)
run: ./gradlew testJdk8DebugUnitTest

# The Android-specific layer (ch.qos.logback.{classic,core}.android) must
# stay at 100% line and branch coverage (rule in logback-android/build.gradle).
# Reuses the coverage the unit test runs above recorded.
- name: Coverage gate (Android-specific layer)
run: ./gradlew verifyAndroidLayerCoverage androidLayerCoverageReportJdk11Debug
# Every class in the library, Java and Kotlin, must stay at 100% line and
# branch coverage (rule in logback-android/build.gradle). Reuses the
# coverage the unit test runs above recorded. The reports are written
# first so that the next step can list what a failing gate is missing.
- name: Coverage reports
run: ./gradlew coverageReportJdk11Debug coverageReportJdk8Debug

- name: Coverage gaps
run: |
for variant in Jdk11Debug Jdk8Debug; do
report="logback-android/build/reports/jacoco/coverageReport$variant/coverageReport$variant.xml"
echo "::group::Uncovered lines and branches ($variant)"
python3 scripts/coverage-gaps.py "$report"
echo "::endgroup::"
{ echo "### Uncovered lines and branches ($variant)"; echo '```'; python3 scripts/coverage-gaps.py "$report"; echo '```'; } >> "$GITHUB_STEP_SUMMARY"
done

- name: Coverage gate
run: ./gradlew verifyCoverage

- name: Upload coverage report
if: ${{ !cancelled() && matrix.java == 21 }}
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: logback-android/build/reports/jacoco/androidLayerCoverageReportJdk11Debug/
path: logback-android/build/reports/jacoco/coverageReportJdk11Debug/

- name: Upload test reports
if: failure()
Expand Down
40 changes: 20 additions & 20 deletions logback-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ android {
debug {
debuggable true
// Records JaCoCo coverage of the debug unit tests, which the
// Android-layer coverage gate at the end of this file checks.
// coverage gate at the end of this file checks.
enableUnitTestCoverage true
}
}
Expand Down Expand Up @@ -137,37 +137,37 @@ afterEvaluate {
}
}

// Coverage gate for the Android-specific layer (ch.qos.logback.{classic,core}.android):
// its unit tests must cover 100% of its lines and branches, so that they pin
// the behavior of every path. For each debug variant,
// androidLayerCoverageReport<Variant> writes an HTML/XML report and
// androidLayerCoverageVerification<Variant> enforces the rule;
// verifyAndroidLayerCoverage runs both variants' checks.
def androidLayerClasses = ['ch/qos/logback/classic/android/**', 'ch/qos/logback/core/android/**']
def androidLayerCoverage = { JacocoReportBase task, String variant ->
// Coverage gate for the whole library, Java and Kotlin: the unit tests must
// cover 100% of the lines and branches of every class, so that they pin the
// behavior of every path. For each debug variant, coverageReport<Variant>
// writes an HTML/XML report (scripts/coverage-gaps.py lists what it misses)
// and coverageVerification<Variant> enforces the rule; verifyCoverage runs
// both variants' checks.
def libraryClasses = ['ch/qos/logback/**', 'org/slf4j/impl/**']
def libraryCoverage = { JacocoReportBase task, String variant ->
def testTask = tasks.named("test${variant}UnitTest", Test)
task.dependsOn(testTask)
task.executionData.from(testTask.map { it.extensions.getByType(JacocoTaskExtension).destinationFile })
task.classDirectories.from(files(
tasks.named("compile${variant}Kotlin").flatMap { it.destinationDirectory },
tasks.named("compile${variant}JavaWithJavac").flatMap { it.destinationDirectory },
).asFileTree.matching { include androidLayerClasses })
).asFileTree.matching { include libraryClasses })
task.sourceDirectories.from(files('src/main/kotlin', 'src/main/java'))
}
def androidLayerVerifications = ['Jdk11Debug', 'Jdk8Debug'].collect { variant ->
tasks.register("androidLayerCoverageReport${variant}", JacocoReport) {
def coverageVerifications = ['Jdk11Debug', 'Jdk8Debug'].collect { variant ->
tasks.register("coverageReport${variant}", JacocoReport) {
group = 'verification'
description = "Reports the ${variant} unit tests' coverage of the Android-specific layer."
androidLayerCoverage(it, variant)
description = "Reports the ${variant} unit tests' coverage of the library."
libraryCoverage(it, variant)
reports {
html.required = true
xml.required = true
}
}
tasks.register("androidLayerCoverageVerification${variant}", JacocoCoverageVerification) {
tasks.register("coverageVerification${variant}", JacocoCoverageVerification) {
group = 'verification'
description = "Fails unless the ${variant} unit tests cover every line and branch of the Android-specific layer."
androidLayerCoverage(it, variant)
description = "Fails unless the ${variant} unit tests cover every line and branch of the library."
libraryCoverage(it, variant)
violationRules {
rule {
element = 'CLASS'
Expand All @@ -177,10 +177,10 @@ def androidLayerVerifications = ['Jdk11Debug', 'Jdk8Debug'].collect { variant ->
}
}
}
tasks.register('verifyAndroidLayerCoverage') {
tasks.register('verifyCoverage') {
group = 'verification'
description = 'Fails unless the unit tests cover every line and branch of the Android-specific layer.'
dependsOn androidLayerVerifications
description = 'Fails unless the unit tests cover every line and branch of the library.'
dependsOn coverageVerifications
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private void fallbackConfiguration(LoggerContext lc, List<SaxEvent> eventList) {
ConfigurationWatchList oldCWL = ConfigurationWatchListUtil.getConfigurationWatchList(lc);
ConfigurationWatchList newCWL = oldCWL.buildClone();

if (failsafeEvents == null || failsafeEvents.isEmpty()) {
if (failsafeEvents.isEmpty()) {
addWarn("No previous configuration to fall back on.");
} else {
addWarn(FALLING_BACK_TO_SAFE_CONFIGURATION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ protected static void doMain(Class<? extends SimpleSocketServer> serverClass,
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
configureLC(lc, configFile);

SimpleSocketServer sss = new SimpleSocketServer(lc, port);
// Unlike upstream logback 1.2, which ignores serverClass and always
// starts a plain SimpleSocketServer (so SimpleSSLSocketServer.main
// started a server without SSL), create the requested class.
SimpleSocketServer sss = serverClass.getConstructor(LoggerContext.class, int.class)
.newInstance(lc, port);
sss.start();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

import ch.qos.logback.core.CoreConstants;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;
Expand All @@ -38,18 +36,6 @@ public class ThrowableProxy implements IThrowableProxy {
private transient PackagingDataCalculator packagingDataCalculator;
private boolean calculatedPackageData = false;

private static final Method GET_SUPPRESSED_METHOD;

static {
Method method = null;
try {
method = Throwable.class.getMethod("getSuppressed");
} catch (NoSuchMethodException e) {
// ignore, will get thrown in Java < 7
}
GET_SUPPRESSED_METHOD = method;
}

private static final ThrowableProxy[] NO_SUPPRESSED = new ThrowableProxy[0];
private static final StackTraceElementProxy[] NO_STACK_TRACE = new StackTraceElementProxy[0];

Expand Down Expand Up @@ -78,26 +64,16 @@ private ThrowableProxy(Throwable throwable, Set<Throwable> visited) {
stackTraceElementProxyArray);
}

if (GET_SUPPRESSED_METHOD != null) {
// this will only execute on Java >= 7
try {
Object obj = GET_SUPPRESSED_METHOD.invoke(throwable);
if (obj instanceof Throwable[]) {
Throwable[] throwableSuppressed = (Throwable[]) obj;
if (throwableSuppressed.length > 0) {
suppressed = new ThrowableProxy[throwableSuppressed.length];
for (int i = 0; i < throwableSuppressed.length; i++) {
this.suppressed[i] = new ThrowableProxy(throwableSuppressed[i], visited);
this.suppressed[i].commonFrames = ThrowableProxyUtil
.findNumberOfCommonFrames(throwableSuppressed[i].getStackTrace(),
stackTraceElementProxyArray);
}
}
}
} catch (IllegalAccessException e) {
// ignore
} catch (InvocationTargetException e) {
// ignore
Throwable[] throwableSuppressed = throwable.getSuppressed();
// while JDK's implementation of getSuppressed() will always return a non-null array,
// this might not be the case in mocked throwables or in other implementations
if (throwableSuppressed != null && throwableSuppressed.length > 0) {
suppressed = new ThrowableProxy[throwableSuppressed.length];
for (int i = 0; i < throwableSuppressed.length; i++) {
this.suppressed[i] = new ThrowableProxy(throwableSuppressed[i], visited);
this.suppressed[i].commonFrames = ThrowableProxyUtil
.findNumberOfCommonFrames(throwableSuppressed[i].getStackTrace(),
stackTraceElementProxyArray);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,14 @@ public ContextInitializer(LoggerContext loggerContext) {
* Finds a configuration file by system property
* @return the file; or {@code null} if not found
*/
private URL findConfigFileFromSystemProperties(boolean updateStatus) {
private URL findConfigFileFromSystemProperties() {
String logbackConfigFile = OptionHelper.getSystemProperty(CONFIG_FILE_PROPERTY);
if (logbackConfigFile != null) {
URL result = null;
try {
File file = new File(logbackConfigFile);
if (file.exists() && file.isFile()) {
if (updateStatus) {
statusOnResourceSearch(logbackConfigFile, logbackConfigFile);
}
statusOnResourceSearch(logbackConfigFile, logbackConfigFile);
result = file.toURI().toURL();
} else {
result = new URL(logbackConfigFile);
Expand All @@ -77,9 +75,7 @@ private URL findConfigFileFromSystemProperties(boolean updateStatus) {
return result;
}
} finally {
if (updateStatus) {
statusOnResourceSearch(logbackConfigFile, result != null ? result.toString() : null);
}
statusOnResourceSearch(logbackConfigFile, result != null ? result.toString() : null);
}
}
return null;
Expand All @@ -89,23 +85,21 @@ private URL findConfigFileFromSystemProperties(boolean updateStatus) {
* Finds a configuration file in the application's assets directory
* @return the URL of the file; or {@code null} if not found
*/
private URL findConfigFileURLFromAssets(boolean updateStatus) {
return getResource(AUTOCONFIG_FILE, this.classLoader, updateStatus);
private URL findConfigFileURLFromAssets() {
return getResource(AUTOCONFIG_FILE, this.classLoader);
}

/**
* Uses the given classloader to search for a resource
* @return the URL of the resource; or {@code null} if not found
*/
private URL getResource(String filename, ClassLoader myClassLoader, boolean updateStatus) {
private URL getResource(String filename, ClassLoader myClassLoader) {
URL url = myClassLoader.getResource(filename);
if (updateStatus) {
String resourcePath = null;
if (url != null) {
resourcePath = filename;
}
statusOnResourceSearch(filename, resourcePath);
String resourcePath = null;
if (url != null) {
resourcePath = filename;
}
statusOnResourceSearch(filename, resourcePath);
return url;
}

Expand All @@ -121,24 +115,21 @@ private URL getResource(String filename, ClassLoader myClassLoader, boolean upda
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);

boolean verbose = true;
boolean configured = false;

JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);

// search system property
if (!configured) {
URL url = findConfigFileFromSystemProperties(verbose);
if (url != null) {
configurator.doConfigure(url);
configured = true;
}
URL url = findConfigFileFromSystemProperties();
if (url != null) {
configurator.doConfigure(url);
configured = true;
}

// search assets
if (!configured) {
URL assetsConfigXml = findConfigFileURLFromAssets(verbose);
URL assetsConfigXml = findConfigFileURLFromAssets();
if (assetsConfigXml != null) {
configurator.doConfigure(assetsConfigXml);
configured = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ public void resize(int newSize) {
throw new IllegalArgumentException("Negative array size [" + newSize
+ "] not allowed.");
}
if (newSize == numElems)
// Upstream compares with numElems here and in the loop below, which ignores a
// resize to the current length and wraps a partially filled buffer at the wrong
// index; the capacity (maxSize) is what both checks are about.
if (newSize == maxSize)
return; // nothing to do

//
Expand All @@ -167,7 +170,7 @@ public void resize(int newSize) {
for (int i = 0; i < loopLen; i++) {
temp[i] = ea[first];
ea[first] = null;
if (++first == numElems)
if (++first == maxSize)
first = 0;
}
ea = temp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;

Expand Down Expand Up @@ -69,7 +68,7 @@ public void begin(InterpretationContext ec, String name, Attributes attributes)
if (url != null) {
processInclude(ec, url);
}
} catch (JoranException e) {
} catch (JoranException | MalformedURLException e) {
optionalWarning("Error while parsing " + attributeInUse, e);
}

Expand Down Expand Up @@ -140,21 +139,15 @@ private URL resourceAsURL(String resourceAttribute) {
return url;
}

private URL filePathAsURL(String path) {
private URL filePathAsURL(String path) throws MalformedURLException {
File file = new File(path);
if (!file.exists() || !file.isFile()) {
optionalWarning("File does not exist [" + path + "]", new FileNotFoundException(path));
return null;
}

URI uri = file.toURI();
try {
return uri.toURL();
} catch (MalformedURLException e) {
// impossible to get here
e.printStackTrace();
return null;
}
// cannot throw: a file's URI always has the "file" scheme, whose URL handler exists
return file.toURI().toURL();
}

protected String getAttributeInUse() {
Expand All @@ -165,7 +158,7 @@ protected boolean isOptional() {
return this.optional;
}

private URL getInputURL(InterpretationContext ec, Attributes attributes) {
private URL getInputURL(InterpretationContext ec, Attributes attributes) throws MalformedURLException {
String fileAttribute = attributes.getValue(FILE_ATTR);
String urlAttribute = attributes.getValue(URL_ATTR);
String resourceAttribute = attributes.getValue(RESOURCE_ATTR);
Expand Down
Loading
Loading