InvalidPathException from Configuration.parse on Windows
### Context
On Windows, paths typically start with a device identifier like `C:`.
This needs to be considered when handling paths in some places.

### Problem
Using a unzipped CPAchecker archive (r35978) the cmdline `.\scripts\cpa.bat -bmc E:\test\file.c`
throws an `InvalidPathException` when accessing files from the CPAchecker jar-file.

### Stacktrace
<pre>
java.nio.file.InvalidPathException: Illegal char <:> at index 3: jar:file:/C:/Users/kfrie/Downloads/CPAchecker-1.9.2-svn-35978M-windows/cpachecker.jar!/org/sosy_lab/cpachecker/util/automaton/find-target-locations.properties
        at java.base/sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
        at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
        at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
        at java.base/sun.nio.fs.WindowsPath.parse(WindowsPath.java:92)
        at java.base/sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:229)
        at java.base/java.nio.file.Path.of(Path.java:147)
        at java.base/java.nio.file.Paths.get(Paths.java:69)
        at org.sosy_lab.common.configuration.Parser.parse(Parser.java:306)
        at org.sosy_lab.common.configuration.Parser.parse(Parser.java:160)
        at org.sosy_lab.common.configuration.ConfigurationBuilder.loadFromResource(ConfigurationBuilder.java:273)
        at org.sosy_lab.cpachecker.util.automaton.TargetLocationProviderImpl.tryGetAutomatonTargetLocations(TargetLocationProviderImpl.java:58)
[...]
</pre>

I do not see this problem as part of CPAchecker.
I hope that this problem can be solved in `java-common-lib`.
It appears for different OpenJDK versions (11 and 15) on Windows 10.
diff --git a/build/gitlab-ci.yml b/build/gitlab-ci.yml
index 05cf074..d93bcbe 100644
--- a/build/gitlab-ci.yml
+++ b/build/gitlab-ci.yml
@@ -167,7 +167,7 @@ unit-tests:jdk-14:
 
 refaster:
   <<: *source_check
-  image: ${CI_REGISTRY_IMAGE}/test:jdk-11
+  image: ${CI_REGISTRY_IMAGE}/test:jdk-14
   before_script:
     - 'test -d refaster || git clone https://gitlab.com/sosy-lab/software/refaster.git'
     - 'cd refaster'
diff --git a/src/org/sosy_lab/common/configuration/Configuration.java b/src/org/sosy_lab/common/configuration/Configuration.java
index 25679c5..faf0e91 100644
--- a/src/org/sosy_lab/common/configuration/Configuration.java
+++ b/src/org/sosy_lab/common/configuration/Configuration.java
@@ -75,9 +75,6 @@ public final class Configuration {
   /** Signal for the processor that the deprecated-prefix feature is not used. */
   static final String NO_DEPRECATED_PREFIX = "<NO_DEPRECATION>";
 
-  /** Dummy value used for source paths that have no correspondence to the file system. */
-  static final String NO_NAMED_SOURCE = "manually set";
-
   private static boolean secureMode = false;
 
   /** Create a new Builder instance. */
@@ -285,7 +282,7 @@ public final class Configuration {
     checkNotNull(pSources);
     checkNotNull(pPrefix);
 
-    assert pProperties.keySet().equals(pSources.keySet());
+    assert pProperties.keySet().containsAll(pSources.keySet());
     properties = pProperties;
     sources = pSources;
     prefix = (pPrefix.isEmpty() ? "" : (pPrefix + "."));
@@ -786,7 +783,7 @@ public final class Configuration {
   private String getOptionSourceForLogging(String optionDeprecatedName) {
     if (sources.containsKey(optionDeprecatedName)) {
       String source = sources.get(optionDeprecatedName).toString();
-      if (!source.isEmpty() && !source.equals(NO_NAMED_SOURCE)) {
+      if (!source.isEmpty()) {
         return " in file " + source;
       }
     }
@@ -1011,7 +1008,7 @@ public final class Configuration {
       annotation = Iterators.getNext(parts, null);
     }
 
-    Path source;
+    @Nullable Path source;
     if (!prefix.isEmpty() && properties.get(prefix + optionName) != null) {
       source = sources.get(prefix + optionName);
     } else {
diff --git a/src/org/sosy_lab/common/configuration/ConfigurationBuilder.java b/src/org/sosy_lab/common/configuration/ConfigurationBuilder.java
index 3cc69db..729029e 100644
--- a/src/org/sosy_lab/common/configuration/ConfigurationBuilder.java
+++ b/src/org/sosy_lab/common/configuration/ConfigurationBuilder.java
@@ -19,12 +19,17 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.errorprone.annotations.CheckReturnValue;
 import com.google.errorprone.annotations.Var;
 import java.io.IOException;
+import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
 import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -71,7 +76,7 @@ public final class ConfigurationBuilder {
     setupProperties();
 
     properties.put(name, value);
-    sources.put(name, Paths.get(Configuration.NO_NAMED_SOURCE));
+    sources.remove(name);
 
     return this;
   }
@@ -93,9 +98,7 @@ public final class ConfigurationBuilder {
     setupProperties();
 
     properties.putAll(options);
-    for (String name : options.keySet()) {
-      sources.put(name, Paths.get(Configuration.NO_NAMED_SOURCE));
-    }
+    sources.keySet().removeAll(options.keySet());
 
     return this;
   }
@@ -154,7 +157,11 @@ public final class ConfigurationBuilder {
     setupProperties();
 
     properties.put(option, sourceConfig.properties.get(option));
-    sources.put(option, sourceConfig.sources.get(option));
+    if (sourceConfig.sources.containsKey(option)) {
+      sources.put(option, sourceConfig.sources.get(option));
+    } else {
+      sources.remove(option);
+    }
 
     return this;
   }
@@ -188,8 +195,8 @@ public final class ConfigurationBuilder {
    *
    * @param source The source to read from.
    * @param basePath The directory where relative #include directives should be based on.
-   * @param sourceName A string to use as source of the file in error messages (this should usually
-   *     be a filename or something similar).
+   * @param sourceName A string to use as source of the file in error messages or for other uses by
+   *     the {@link TypeConverter}. This needs to be convertible into a {@link Path}.
    * @throws IOException If the stream cannot be read.
    * @throws InvalidConfigurationException If the stream contains an invalid format.
    */
@@ -199,9 +206,18 @@ public final class ConfigurationBuilder {
     checkNotNull(basePath);
     setupProperties();
 
+    @Var Path sourcePath;
+    try {
+      sourcePath = Paths.get(sourceName);
+    } catch (InvalidPathException e) {
+      // If this fails, e.g., because sourceName is a HTTP URL, we can also go without source
+      // information. This will not allow resolving relative path names, but everything else works.
+      sourcePath = null;
+    }
+
     // Need to append something to base path because resolveSibling() is used.
     Path base = Paths.get(basePath).resolve("dummy");
-    Parser parser = Parser.parse(source, Optional.of(base), sourceName);
+    Parser parser = Parser.parse(source, Optional.of(base), sourcePath);
     properties.putAll(parser.getOptions());
     sources.putAll(parser.getSources());
 
@@ -250,35 +266,68 @@ public final class ConfigurationBuilder {
    * @throws IllegalArgumentException If the resource cannot be found or read, or contains invalid
    *     syntax or #include directives.
    */
+  @SuppressWarnings("try")
   public ConfigurationBuilder loadFromResource(Class<?> contextClass, String resourceName) {
     URL url = Resources.getResource(contextClass, resourceName);
     CharSource source = Resources.asCharSource(url, StandardCharsets.UTF_8);
 
     setupProperties();
 
-    // Get the path to the source, used for error messages and resolving relative path names.
-    @Var Path sourcePath;
-    @Var String sourceString;
     try {
-      sourcePath = Paths.get(url.toURI());
-      sourceString = sourcePath.toString();
-    } catch (URISyntaxException | FileSystemNotFoundException | IllegalArgumentException e) {
-      // If this fails, e.g., because url is a HTTP URL, we can also use the raw string.
+      URI uri = url.toURI();
+      try (FileSystem fs = getFileSystemForUriInJars(uri)) {
+        // Path uses FileSystemProvider internally to access the file, thus fs is unused.
+        Path sourcePath = Paths.get(uri);
+        parseSource(contextClass, resourceName, source, Optional.of(sourcePath));
+      }
+    } catch (URISyntaxException
+        | FileSystemNotFoundException
+        | IllegalArgumentException
+        | IOException e) {
+      // If this fails, e.g., because url is a HTTP URL, we can also go without source information.
       // This will not allow resolving relative path names, but everything else works.
-      sourcePath = null;
-      sourceString = url.toString();
+      parseSource(contextClass, resourceName, source, Optional.empty());
     }
 
+    return this;
+  }
+
+  private void parseSource(
+      Class<?> contextClass, String resourceName, CharSource source, Optional<Path> sourcePath) {
     try {
-      Parser parser = Parser.parse(source, Optional.ofNullable(sourcePath), sourceString);
+      Parser parser = Parser.parse(source, sourcePath, sourcePath.orElse(null));
       properties.putAll(parser.getOptions());
       sources.putAll(parser.getSources());
     } catch (InvalidConfigurationException | IOException e) {
       throw new IllegalArgumentException(
           "Error in resource " + resourceName + " relative to " + contextClass.getName(), e);
     }
+  }
 
-    return this;
+  /**
+   * If the URI is part of a JAR file, we open the file system from the JAR. We only register/open a
+   * new file system for the JAR file, if it was not already open before. The paths for this file
+   * system are valid as long as the file system not closed. If anything fails, e.g., if the URI
+   * does not point to a JAR file or the JAR is already open, we return <code>null</code>.
+   *
+   * <p>A JAR-based file system uses ZipFileSystem that can be opened and closed several times.
+   *
+   * @return the opened file system of the JAR file if it was not open before or <code>null</code>.
+   */
+  private static FileSystem getFileSystemForUriInJars(URI uri) throws IOException {
+    if ("jar".equals(uri.getScheme())) {
+      for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
+        if (provider.getScheme().equalsIgnoreCase("jar")) {
+          try {
+            // try to register a new file system (provider) for the JAR.
+            return provider.newFileSystem(uri, ImmutableMap.of());
+          } catch (FileSystemAlreadyExistsException e) {
+            // file system already exists: ignore it and return null after the loop
+          }
+        }
+      }
+    }
+    return null; // default case: we do not need an extra file system
   }
 
   /**
diff --git a/src/org/sosy_lab/common/configuration/PackageSanityTest.java b/src/org/sosy_lab/common/configuration/PackageSanityTest.java
index d5a0a0e..eecca76 100644
--- a/src/org/sosy_lab/common/configuration/PackageSanityTest.java
+++ b/src/org/sosy_lab/common/configuration/PackageSanityTest.java
@@ -23,7 +23,7 @@ public class PackageSanityTest extends AbstractPackageSanityTests {
     setDefault(String[].class, new String[] {"test"});
     setDefault(Path.class, Paths.get("test"));
     setDefault(Configuration.class, Configuration.defaultConfiguration());
-    setDefault(CharSource.class, CharSource.wrap("test"));
+    setDefault(CharSource.class, CharSource.wrap("key=value"));
     setDefault(Optional.class, Optional.empty());
   }
 }
diff --git a/src/org/sosy_lab/common/configuration/Parser.java b/src/org/sosy_lab/common/configuration/Parser.java
index 3383e4f..e6293ea 100644
--- a/src/org/sosy_lab/common/configuration/Parser.java
+++ b/src/org/sosy_lab/common/configuration/Parser.java
@@ -12,6 +12,8 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Verify.verify;
 
+import com.google.common.base.Functions;
+import com.google.common.collect.Maps;
 import com.google.common.io.CharSource;
 import com.google.errorprone.annotations.CheckReturnValue;
 import com.google.errorprone.annotations.Var;
@@ -29,6 +31,8 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.regex.Pattern;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.sosy_lab.common.configuration.converters.TypeConverter;
 import org.sosy_lab.common.io.IO;
 
 /**
@@ -68,8 +72,9 @@ class Parser {
 
     private static final long serialVersionUID = 8146907093750189669L;
 
-    private InvalidConfigurationFileException(String msg, int lineno, String source, String line) {
-      super(msg + " in line " + lineno + " of " + source + ": " + line);
+    private InvalidConfigurationFileException(
+        String msg, int lineno, @Nullable Path source, String line) {
+      super(msg + " in line " + lineno + (source != null ? " of " + source : "") + ": " + line);
     }
 
     private InvalidConfigurationFileException(String msg) {
@@ -132,7 +137,7 @@ class Parser {
     includeStack.addLast(fileName);
 
     try (BufferedReader r = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
-      parse(r, Optional.of(file), file.toString());
+      parse(r, Optional.of(file), file);
     }
     includeStack.removeLast();
   }
@@ -146,18 +151,18 @@ class Parser {
    * @param source The source to read the file from.
    * @param basePath If #include filenames are relative, resolve them as sibling of basePath. Source
    *     must not contain #include if absent.
-   * @param sourceName A string to use as source of the file in error messages (this should usually
-   *     be a filename or something similar).
+   * @param sourcePath An optional Path to use as source of the options in error messages or for
+   *     other uses by the {@link TypeConverter}.
    * @throws IOException If an I/O error occurs.
    * @throws InvalidConfigurationException If the configuration file has an invalid format.
    */
   @CheckReturnValue
-  static Parser parse(CharSource source, Optional<Path> basePath, String sourceName)
+  static Parser parse(CharSource source, Optional<Path> basePath, @Nullable Path sourcePath)
       throws IOException, InvalidConfigurationException {
 
     Parser parser = new Parser();
     try (BufferedReader r = source.openBufferedStream()) {
-      parser.parse(r, basePath, sourceName);
+      parser.parse(r, basePath, sourcePath);
     }
     verify(parser.includeStack.isEmpty());
     return parser;
@@ -172,18 +177,17 @@ class Parser {
    * @param r The reader to read the file from.
    * @param basePath If #include filenames are relative, resolve them as sibling of basePath. Source
    *     must not contain #include if absent.
-   * @param source A string to use as source of the file in error messages (this should usually be a
-   *     filename or something similar).
+   * @param source An optional Path to use as source of the options in error messages or for other
+   *     uses by the {@link TypeConverter}.
    * @throws IOException If an I/O error occurs.
    * @throws InvalidConfigurationException If the configuration file has an invalid format.
    */
   @SuppressFBWarnings(
       value = "SBSC_USE_STRINGBUFFER_CONCATENATION",
       justification = "performance irrelevant compared to I/O, String much more convenient")
-  private void parse(BufferedReader r, Optional<Path> basePath, String source)
+  private void parse(BufferedReader r, Optional<Path> basePath, @Nullable Path source)
       throws IOException, InvalidConfigurationException {
     checkNotNull(basePath);
-    checkNotNull(source);
 
     @Var String line;
     @Var int lineno = 0;
@@ -303,9 +307,10 @@ class Parser {
     // now overwrite included options with local ones
     options.putAll(definedOptions);
 
-    Path thisSource = Paths.get(source);
-    for (String name : definedOptions.keySet()) {
-      sources.put(name, thisSource);
+    if (source != null) {
+      sources.putAll(Maps.asMap(definedOptions.keySet(), Functions.constant(source)));
+    } else {
+      sources.keySet().removeAll(definedOptions.keySet());
     }
   }
 }
diff --git a/src/org/sosy_lab/common/configuration/ParserTest.java b/src/org/sosy_lab/common/configuration/ParserTest.java
index 9507d11..fe6a57f 100644
--- a/src/org/sosy_lab/common/configuration/ParserTest.java
+++ b/src/org/sosy_lab/common/configuration/ParserTest.java
@@ -45,14 +45,15 @@ public class ParserTest {
 
   private Map<String, String> test(String content)
       throws IOException, InvalidConfigurationException {
-    return Parser.parse(CharSource.wrap(content), Optional.ofNullable(basePath), "test")
+    return Parser.parse(CharSource.wrap(content), Optional.ofNullable(basePath), basePath)
         .getOptions();
   }
 
   private void testInvalid(String content) {
     CharSource source = CharSource.wrap(content);
     Optional<Path> path = Optional.ofNullable(basePath);
-    assertThrows(InvalidConfigurationFileException.class, () -> Parser.parse(source, path, "test"));
+    assertThrows(
+        InvalidConfigurationFileException.class, () -> Parser.parse(source, path, basePath));
   }
 
   private void testEmpty(String content) {
@@ -220,7 +221,7 @@ public class ParserTest {
   private void testInvalidInclude(String content) {
     CharSource source = CharSource.wrap(content);
     Optional<Path> path = Optional.ofNullable(basePath);
-    assertThrows(FileNotFoundException.class, () -> Parser.parse(source, path, "test"));
+    assertThrows(FileNotFoundException.class, () -> Parser.parse(source, path, basePath));
   }
 
   @Test
@@ -291,7 +292,7 @@ public class ParserTest {
   public final void includeWithoutBasePath() {
     CharSource source = CharSource.wrap("#include test.properties");
     assertThrows(
-        IllegalArgumentException.class, () -> Parser.parse(source, Optional.empty(), "test"));
+        IllegalArgumentException.class, () -> Parser.parse(source, Optional.empty(), basePath));
   }
 
   @Test
diff --git a/src/org/sosy_lab/common/configuration/converters/FileTypeConverter.java b/src/org/sosy_lab/common/configuration/converters/FileTypeConverter.java
index 4d47c95..9fb3886 100644
--- a/src/org/sosy_lab/common/configuration/converters/FileTypeConverter.java
+++ b/src/org/sosy_lab/common/configuration/converters/FileTypeConverter.java
@@ -26,6 +26,7 @@ import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Objects;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.sosy_lab.common.configuration.Configuration;
 import org.sosy_lab.common.configuration.FileOption;
@@ -78,9 +79,7 @@ public final class FileTypeConverter implements TypeConverter {
           "disable all default output files\n(any explicitly given file will still be written)")
   private boolean disableOutput = false;
 
-  @Option(
-      description =
-          "base directory for all input & output files\n(except for the configuration file itself)")
+  @Option(description = "base directory for all paths in default values")
   private String rootDirectory = ".";
 
   @VisibleForTesting final Path rootPath;
@@ -237,7 +236,7 @@ public final class FileTypeConverter implements TypeConverter {
         path,
         ((FileOption) secondaryOption).value(),
         type,
-        pSource,
+        Objects.requireNonNullElse(pSource, Paths.get("")),
         /*doResolve=*/ true);
   }
 
@@ -331,7 +330,7 @@ public final class FileTypeConverter implements TypeConverter {
       @Var Path file,
       FileOption.Type typeInfo,
       Class<?> targetType,
-      Path source,
+      @Nullable Path source,
       boolean doResolve)
       throws InvalidConfigurationException {
 
diff --git a/src/org/sosy_lab/common/configuration/converters/TypeConverter.java b/src/org/sosy_lab/common/configuration/converters/TypeConverter.java
index 8db36df..75c55fe 100644
--- a/src/org/sosy_lab/common/configuration/converters/TypeConverter.java
+++ b/src/org/sosy_lab/common/configuration/converters/TypeConverter.java
@@ -47,7 +47,7 @@ public interface TypeConverter {
    * @param secondaryOption An optional second annotation for the option (this is one of the
    *     annotations marked with {@link OptionDetailAnnotation}).
    * @param source The file where the configuration option was read from. May contain a dummy value
-   *     if the option was given somehow else.
+   *     or null if the option was given somehow else.
    * @param logger A logger for warnings etc.
    * @return An instance of the target type produced from the string representation-
    * @throws UnsupportedOperationException If the option specification in the source code is invalid
@@ -60,7 +60,7 @@ public interface TypeConverter {
       String value,
       TypeToken<?> type,
       @Nullable Annotation secondaryOption,
-      Path source,
+      @Nullable Path source,
       LogManager logger)
       throws InvalidConfigurationException;
 
['build/gitlab-ci.yml', 'src/org/sosy_lab/common/configuration/Parser.java', 'src/org/sosy_lab/common/configuration/ConfigurationBuilder.java', 'src/org/sosy_lab/common/configuration/Configuration.java', 'src/org/sosy_lab/common/configuration/ParserTest.java', 'src/org/sosy_lab/common/configuration/converters/TypeConverter.java', 'src/org/sosy_lab/common/configuration/converters/FileTypeConverter.java', 'src/org/sosy_lab/common/configuration/PackageSanityTest.java']