From aeca6207d2274fb2ebf1e4159fc4d2f4b3ced767 Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Mon, 13 Mar 2023 21:18:44 +0100
Subject: [PATCH 01/12] bump cryptolib

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index ba019eb0..bea3654b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
 		<maven.compiler.release>17</maven.compiler.release>
 
 		<!-- dependencies -->
-		<cryptolib.version>2.1.0</cryptolib.version>
+		<cryptolib.version>2.1.2</cryptolib.version>
 		<jwt.version>4.2.1</jwt.version>
 		<dagger.version>2.44.2</dagger.version>
 		<guava.version>31.1-jre</guava.version>

From e060ab0aefb6480fa91e4351258ea6de6d9aeefc Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Tue, 14 Mar 2023 21:41:33 +0100
Subject: [PATCH 02/12] bump java-jwt, dependency-check

---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index bea3654b..4a539e05 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
 
 		<!-- dependencies -->
 		<cryptolib.version>2.1.2</cryptolib.version>
-		<jwt.version>4.2.1</jwt.version>
+		<jwt.version>4.3.0</jwt.version>
 		<dagger.version>2.44.2</dagger.version>
 		<guava.version>31.1-jre</guava.version>
 		<slf4j.version>2.0.3</slf4j.version>
@@ -30,7 +30,7 @@
 		<hamcrest.version>2.2</hamcrest.version>
 
 		<!-- build plugin dependencies -->
-		<dependency-check.version>7.3.2</dependency-check.version>
+		<dependency-check.version>8.1.2</dependency-check.version>
 		<jacoco.version>0.8.8</jacoco.version>
 		<nexus-staging.version>1.6.13</nexus-staging.version>
 	</properties>

From d61d57ae8e2913c3cb4841dde9f558ba4e91677d Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Tue, 14 Mar 2023 21:42:27 +0100
Subject: [PATCH 03/12] suppress false positive

---
 suppression.xml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/suppression.xml b/suppression.xml
index ebb877b8..a7fe7425 100644
--- a/suppression.xml
+++ b/suppression.xml
@@ -9,4 +9,12 @@
 		<cpe>cpe:/a:cryptomator:cryptomator</cpe>
 		<cve>CVE-2022-25366</cve>
 	</suppress>
+	<suppress>
+		<notes><![CDATA[
+  		Suppress false positive, because com.google.common.io.Files.createTempDir() is not used
+   ]]></notes>
+		<packageUrl regex="true">^pkg:maven/com\.google\.guava/guava@.*$</packageUrl>
+		<vulnerabilityName>CVE-2020-8908</vulnerabilityName>
+		<cve>CVE-2020-8908</cve>
+	</suppress>
 </suppressions>
\ No newline at end of file

From 33225b95c34594600652d2b78cbebd3e9cf2a475 Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 10:06:01 +0100
Subject: [PATCH 04/12] replace builder by factory for CrypoFileSystemComponent

---
 .../cryptofs/CryptoFileSystemComponent.java   | 26 +++++--------------
 .../cryptofs/CryptoFileSystems.java           | 20 +++-----------
 .../cryptofs/CryptoFileSystemsTest.java       | 18 +++----------
 3 files changed, 15 insertions(+), 49 deletions(-)

diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java
index d82b2675..85e0379f 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemComponent.java
@@ -12,25 +12,13 @@ public interface CryptoFileSystemComponent {
 
 	CryptoFileSystemImpl cryptoFileSystem();
 
-	@Subcomponent.Builder
-	interface Builder {
-
-		@BindsInstance
-		Builder cryptor(Cryptor cryptor);
-
-		@BindsInstance
-		Builder vaultConfig(VaultConfig vaultConfig);
-
-		@BindsInstance
-		Builder provider(CryptoFileSystemProvider provider);
-
-		@BindsInstance
-		Builder pathToVault(@PathToVault Path pathToVault);
-
-		@BindsInstance
-		Builder properties(CryptoFileSystemProperties cryptoFileSystemProperties);
-
-		CryptoFileSystemComponent build();
+	@Subcomponent.Factory
+	interface Factory {
+		CryptoFileSystemComponent create(@BindsInstance Cryptor cryptor, //
+										 @BindsInstance VaultConfig vaultConfig, //
+										 @BindsInstance CryptoFileSystemProvider provider, //
+										 @BindsInstance @PathToVault Path pathToVault, //
+										 @BindsInstance CryptoFileSystemProperties cryptoFileSystemProperties);
 	}
 
 }
diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java
index 646f8628..e64ab36a 100644
--- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java
+++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java
@@ -33,13 +33,13 @@ class CryptoFileSystems {
 	private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystems.class);
 
 	private final ConcurrentMap<Path, CryptoFileSystemImpl> fileSystems = new ConcurrentHashMap<>();
-	private final CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder; // sharing reusable builder via synchronized
+	private final CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory;
 	private final FileSystemCapabilityChecker capabilityChecker;
 	private final SecureRandom csprng;
 
 	@Inject
-	public CryptoFileSystems(CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder, FileSystemCapabilityChecker capabilityChecker, SecureRandom csprng) {
-		this.cryptoFileSystemComponentBuilder = cryptoFileSystemComponentBuilder;
+	public CryptoFileSystems(CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory, FileSystemCapabilityChecker capabilityChecker, SecureRandom csprng) {
+		this.cryptoFileSystemComponentFactory = cryptoFileSystemComponentFactory;
 		this.capabilityChecker = capabilityChecker;
 		this.csprng = csprng;
 	}
@@ -59,7 +59,7 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT
 				checkVaultRootExistence(pathToVault, cryptor);
 				return fileSystems.compute(normalizedPathToVault, (path, fs) -> {
 					if (fs == null) {
-						return create(provider, normalizedPathToVault, adjustedProperties, cryptor, config);
+						return cryptoFileSystemComponentFactory.create(cryptor, config, provider, normalizedPathToVault, adjustedProperties).cryptoFileSystem();
 					} else {
 						throw new FileSystemAlreadyExistsException();
 					}
@@ -86,18 +86,6 @@ private void checkVaultRootExistence(Path pathToVault, Cryptor cryptor) throws C
 		}
 	}
 
-	// synchronized access to non-threadsafe cryptoFileSystemComponentBuilder required
-	private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, Cryptor cryptor, VaultConfig config) {
-		return cryptoFileSystemComponentBuilder //
-				.cryptor(cryptor) //
-				.vaultConfig(config) //
-				.pathToVault(pathToVault) //
-				.properties(properties) //
-				.provider(provider) //
-				.build() //
-				.cryptoFileSystem();
-	}
-
 	/**
 	 * Attempts to read a vault config file
 	 *
diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java
index fc2e80ac..26dcf5e6 100644
--- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemsTest.java
@@ -57,7 +57,7 @@ public class CryptoFileSystemsTest {
 	private final CryptorProvider cryptorProvider = mock(CryptorProvider.class);
 	private final Cryptor cryptor = mock(Cryptor.class);
 	private final FileNameCryptor fileNameCryptor = mock(FileNameCryptor.class);
-	private final CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder = mock(CryptoFileSystemComponent.Builder.class);
+	private final CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory = mock(CryptoFileSystemComponent.Factory.class);
 
 
 	private MockedStatic<VaultConfig> vaultConficClass;
@@ -65,7 +65,7 @@ public class CryptoFileSystemsTest {
 	private MockedStatic<CryptorProvider> cryptorProviderClass;
 	private MockedStatic<BackupHelper> backupHelperClass;
 
-	private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker, csprng);
+	private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentFactory, capabilityChecker, csprng);
 
 	@BeforeEach
 	public void setup() throws IOException, MasterkeyLoadingFailedException {
@@ -96,12 +96,7 @@ public void setup() throws IOException, MasterkeyLoadingFailedException {
 		when(dataDirPath.resolve("AB")).thenReturn(preContenRootPath);
 		when(preContenRootPath.resolve("CDEFGHIJKLMNOP")).thenReturn(contenRootPath);
 		filesClass.when(() -> Files.exists(contenRootPath)).thenReturn(true);
-		when(cryptoFileSystemComponentBuilder.cryptor(any())).thenReturn(cryptoFileSystemComponentBuilder);
-		when(cryptoFileSystemComponentBuilder.vaultConfig(any())).thenReturn(cryptoFileSystemComponentBuilder);
-		when(cryptoFileSystemComponentBuilder.pathToVault(any())).thenReturn(cryptoFileSystemComponentBuilder);
-		when(cryptoFileSystemComponentBuilder.properties(any())).thenReturn(cryptoFileSystemComponentBuilder);
-		when(cryptoFileSystemComponentBuilder.provider(any())).thenReturn(cryptoFileSystemComponentBuilder);
-		when(cryptoFileSystemComponentBuilder.build()).thenReturn(cryptoFileSystemComponent);
+		when(cryptoFileSystemComponentFactory.create(any(),any(),any(),any(),any())).thenReturn(cryptoFileSystemComponent);
 		when(cryptoFileSystemComponent.cryptoFileSystem()).thenReturn(cryptoFileSystem);
 	}
 
@@ -124,12 +119,7 @@ public void testContainsReturnsTrueForContainedFileSystem() throws IOException,
 
 		Assertions.assertSame(cryptoFileSystem, impl);
 		Assertions.assertTrue(inTest.contains(cryptoFileSystem));
-		verify(cryptoFileSystemComponentBuilder).cryptor(cryptor);
-		verify(cryptoFileSystemComponentBuilder).vaultConfig(vaultConfig);
-		verify(cryptoFileSystemComponentBuilder).pathToVault(normalizedPathToVault);
-		verify(cryptoFileSystemComponentBuilder).properties(properties);
-		verify(cryptoFileSystemComponentBuilder).provider(provider);
-		verify(cryptoFileSystemComponentBuilder).build();
+		verify(cryptoFileSystemComponentFactory,Mockito.times(1)).create(cryptor, vaultConfig, provider, normalizedPathToVault, properties);
 	}
 
 	@Test

From e442d3277ad68974bee9c5023b95afc183b5a90e Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 10:26:37 +0100
Subject: [PATCH 05/12] replace builder by factory in AttributeComponent

---
 .../cryptofs/attr/AttributeComponent.java     | 16 ++----
 .../cryptofs/attr/AttributeProvider.java      | 18 +++---
 .../cryptofs/attr/AttributeProviderTest.java  | 55 ++++++-------------
 3 files changed, 30 insertions(+), 59 deletions(-)

diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeComponent.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeComponent.java
index a3cea066..91f755ad 100644
--- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeComponent.java
@@ -24,18 +24,12 @@ default <T extends BasicFileAttributes> T attributes(Class<T> type) {
 		}
 	}
 
-	@Subcomponent.Builder
-	interface Builder {
+	@Subcomponent.Factory
+	interface Factory {
 
-		@BindsInstance
-		Builder ciphertextPath(Path ciphertextPath);
+		AttributeComponent create(@BindsInstance Path ciphertextPath, //
+								  @BindsInstance CiphertextFileType ciphertextFileType, //
+								  @BindsInstance @Named("ciphertext") BasicFileAttributes ciphertextAttributes);
 
-		@BindsInstance
-		Builder ciphertextFileType(CiphertextFileType ciphertextFileType);
-
-		@BindsInstance
-		Builder ciphertextAttributes(@Named("ciphertext") BasicFileAttributes ciphertextAttributes);
-
-		AttributeComponent build();
 	}
 }
diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java
index 02e15459..b2195161 100644
--- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java
+++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java
@@ -16,7 +16,6 @@
 import org.cryptomator.cryptofs.common.CiphertextFileType;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
@@ -26,13 +25,13 @@
 @CryptoFileSystemScoped
 public class AttributeProvider {
 
-	private final Provider<AttributeComponent.Builder> attributeComponentBuilderProvider;
+	private final AttributeComponent.Factory attributeComponentFactory;
 	private final CryptoPathMapper pathMapper;
 	private final Symlinks symlinks;
 
 	@Inject
-	AttributeProvider(Provider<AttributeComponent.Builder> attributeComponentBuilderProvider, CryptoPathMapper pathMapper, Symlinks symlinks) {
-		this.attributeComponentBuilderProvider = attributeComponentBuilderProvider;
+	AttributeProvider(AttributeComponent.Factory attributeComponentFactory, CryptoPathMapper pathMapper, Symlinks symlinks) {
+		this.attributeComponentFactory = attributeComponentFactory;
 		this.pathMapper = pathMapper;
 		this.symlinks = symlinks;
 	}
@@ -45,13 +44,10 @@ public <A extends BasicFileAttributes> A readAttributes(CryptoPath cleartextPath
 		}
 		Path ciphertextPath = getCiphertextPath(cleartextPath, ciphertextFileType);
 		A ciphertextAttrs = Files.readAttributes(ciphertextPath, type);
-		AttributeComponent.Builder builder = attributeComponentBuilderProvider.get();
-		return builder  //
-				.ciphertextFileType(ciphertextFileType) //
-				.ciphertextPath(ciphertextPath) //
-				.ciphertextAttributes(ciphertextAttrs) //
-				.build() //
-				.attributes(type);
+		return attributeComponentFactory.create(ciphertextPath, //
+						ciphertextFileType, //
+						ciphertextAttrs) //
+				.attributes(type); //
 	}
 
 	private Path getCiphertextPath(CryptoPath path, CiphertextFileType type) throws IOException {
diff --git a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java
index 11616263..85dd5916 100644
--- a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java
@@ -20,7 +20,6 @@
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
-import javax.inject.Provider;
 import java.io.IOException;
 import java.nio.file.FileSystem;
 import java.nio.file.LinkOption;
@@ -29,12 +28,12 @@
 import java.nio.file.attribute.DosFileAttributes;
 import java.nio.file.attribute.PosixFileAttributes;
 import java.nio.file.spi.FileSystemProvider;
-import java.util.Optional;
+
+import static org.mockito.ArgumentMatchers.any;
 
 public class AttributeProviderTest {
 
-	private Provider<AttributeComponent.Builder> attributeComponentBuilderProvider;
-	private AttributeComponent.Builder attributeComponentBuilder;
+	private AttributeComponent.Factory attributeComponentFactory;
 	private AttributeComponent attributeComponent;
 	private CryptoPathMapper pathMapper;
 	private CryptoPath cleartextPath;
@@ -47,14 +46,9 @@ public class AttributeProviderTest {
 
 	@BeforeEach
 	public void setup() throws IOException {
-		attributeComponentBuilderProvider = Mockito.mock(Provider.class);
-		attributeComponentBuilder = Mockito.mock(AttributeComponent.Builder.class);
+		attributeComponentFactory = Mockito.mock(AttributeComponent.Factory.class);
 		attributeComponent = Mockito.mock(AttributeComponent.class);
-		Mockito.when(attributeComponentBuilderProvider.get()).thenReturn(attributeComponentBuilder);
-		Mockito.when(attributeComponentBuilder.ciphertextFileType(Mockito.any())).thenReturn(attributeComponentBuilder);
-		Mockito.when(attributeComponentBuilder.ciphertextPath(Mockito.any())).thenReturn(attributeComponentBuilder);
-		Mockito.when(attributeComponentBuilder.ciphertextAttributes(Mockito.any())).thenReturn(attributeComponentBuilder);
-		Mockito.when(attributeComponentBuilder.build()).thenReturn(attributeComponent);
+		Mockito.when(attributeComponentFactory.create(any(), any(), any())).thenReturn(attributeComponent);
 
 		pathMapper = Mockito.mock(CryptoPathMapper.class);
 		cleartextPath = Mockito.mock(CryptoPath.class, "cleartextPath");
@@ -68,9 +62,9 @@ public void setup() throws IOException {
 		ciphertextBasicAttr = Mockito.mock(BasicFileAttributes.class);
 		ciphertextPosixAttr = Mockito.mock(PosixFileAttributes.class);
 		ciphertextDosAttr = Mockito.mock(DosFileAttributes.class);
-		Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(BasicFileAttributes.class), Mockito.any())).thenReturn(ciphertextBasicAttr);
-		Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(PosixFileAttributes.class), Mockito.any())).thenReturn(ciphertextPosixAttr);
-		Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(DosFileAttributes.class), Mockito.any())).thenReturn(ciphertextDosAttr);
+		Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(BasicFileAttributes.class), any())).thenReturn(ciphertextBasicAttr);
+		Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(PosixFileAttributes.class), any())).thenReturn(ciphertextPosixAttr);
+		Mockito.when(provider.readAttributes(Mockito.same(ciphertextRawPath), Mockito.same(DosFileAttributes.class), any())).thenReturn(ciphertextDosAttr);
 
 		Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
 		Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
@@ -92,7 +86,7 @@ public void setup() throws IOException {
 			Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.FILE);
 			Mockito.when(pathMapper.getCiphertextFilePath(cleartextPath)).thenReturn(ciphertextPath);
 
-			prov = new AttributeProvider(attributeComponentBuilderProvider, pathMapper, symlinks);
+			prov = new AttributeProvider(attributeComponentFactory, pathMapper, symlinks);
 		}
 
 		@Test
@@ -101,9 +95,7 @@ public void testReadBasicAttributes() throws IOException {
 
 			BasicFileAttributes attr = prov.readAttributes(cleartextPath, BasicFileAttributes.class);
 
-			Mockito.verify(attributeComponentBuilder).ciphertextPath(ciphertextRawPath);
-			Mockito.verify(attributeComponentBuilder).ciphertextFileType(CiphertextFileType.FILE);
-			Mockito.verify(attributeComponentBuilder).ciphertextAttributes(ciphertextBasicAttr);
+			Mockito.verify(attributeComponentFactory, Mockito.times(1)).create(ciphertextRawPath, CiphertextFileType.FILE, ciphertextBasicAttr);
 			Assertions.assertEquals(ciphertextBasicAttr, attr);
 		}
 
@@ -113,9 +105,7 @@ public void testReadPosixAttributes() throws IOException {
 
 			PosixFileAttributes attr = prov.readAttributes(cleartextPath, PosixFileAttributes.class);
 
-			Mockito.verify(attributeComponentBuilder).ciphertextPath(ciphertextRawPath);
-			Mockito.verify(attributeComponentBuilder).ciphertextFileType(CiphertextFileType.FILE);
-			Mockito.verify(attributeComponentBuilder).ciphertextAttributes(ciphertextPosixAttr);
+			Mockito.verify(attributeComponentFactory, Mockito.times(1)).create(ciphertextRawPath, CiphertextFileType.FILE, ciphertextPosixAttr);
 			Assertions.assertEquals(ciphertextPosixAttr, attr);
 		}
 
@@ -125,9 +115,7 @@ public void testReadDosAttributes() throws IOException {
 
 			DosFileAttributes attr = prov.readAttributes(cleartextPath, DosFileAttributes.class);
 
-			Mockito.verify(attributeComponentBuilder).ciphertextPath(ciphertextRawPath);
-			Mockito.verify(attributeComponentBuilder).ciphertextFileType(CiphertextFileType.FILE);
-			Mockito.verify(attributeComponentBuilder).ciphertextAttributes(ciphertextDosAttr);
+			Mockito.verify(attributeComponentFactory, Mockito.times(1)).create(ciphertextRawPath, CiphertextFileType.FILE, ciphertextDosAttr);
 			Assertions.assertEquals(ciphertextDosAttr, attr);
 		}
 
@@ -138,8 +126,7 @@ public void testReadUnsupportedAttributes() {
 			Assertions.assertThrows(UnsupportedOperationException.class, () -> {
 				prov.readAttributes(cleartextPath, UnsupportedAttributes.class);
 			});
-			Mockito.verify(attributeComponentBuilder).ciphertextPath(ciphertextRawPath);
-			Mockito.verify(attributeComponentBuilder).ciphertextFileType(CiphertextFileType.FILE);
+			Mockito.verify(attributeComponentFactory, Mockito.times(1)).create(Mockito.eq(ciphertextRawPath), Mockito.eq(CiphertextFileType.FILE), any());
 		}
 
 	}
@@ -155,7 +142,7 @@ public void setup() throws IOException {
 			Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.DIRECTORY);
 			Mockito.when(pathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextRawPath));
 
-			prov = new AttributeProvider(attributeComponentBuilderProvider, pathMapper, symlinks);
+			prov = new AttributeProvider(attributeComponentFactory, pathMapper, symlinks);
 		}
 
 		@Test
@@ -164,9 +151,7 @@ public void testReadBasicAttributes() throws IOException {
 
 			BasicFileAttributes attr = prov.readAttributes(cleartextPath, BasicFileAttributes.class);
 
-			Mockito.verify(attributeComponentBuilder).ciphertextPath(ciphertextRawPath);
-			Mockito.verify(attributeComponentBuilder).ciphertextFileType(CiphertextFileType.DIRECTORY);
-			Mockito.verify(attributeComponentBuilder).ciphertextAttributes(ciphertextBasicAttr);
+			Mockito.verify(attributeComponentFactory, Mockito.times(1)).create(ciphertextRawPath, CiphertextFileType.DIRECTORY, ciphertextBasicAttr);
 			Assertions.assertEquals(ciphertextBasicAttr, attr);
 		}
 
@@ -181,7 +166,7 @@ public class SymbolicLinks {
 		public void setup() throws IOException {
 			Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.SYMLINK);
 
-			prov = new AttributeProvider(attributeComponentBuilderProvider, pathMapper, symlinks);
+			prov = new AttributeProvider(attributeComponentFactory, pathMapper, symlinks);
 		}
 
 		@Test
@@ -192,9 +177,7 @@ public void testReadBasicAttributesNoFollow() throws IOException {
 
 			BasicFileAttributes attr = prov.readAttributes(cleartextPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
 
-			Mockito.verify(attributeComponentBuilder).ciphertextPath(ciphertextRawPath);
-			Mockito.verify(attributeComponentBuilder).ciphertextFileType(CiphertextFileType.SYMLINK);
-			Mockito.verify(attributeComponentBuilder).ciphertextAttributes(ciphertextBasicAttr);
+			Mockito.verify(attributeComponentFactory, Mockito.times(1)).create(ciphertextRawPath, CiphertextFileType.SYMLINK, ciphertextBasicAttr);
 			Assertions.assertEquals(ciphertextBasicAttr, attr);
 		}
 
@@ -208,9 +191,7 @@ public void testReadBasicAttributesOfTarget() throws IOException {
 
 			BasicFileAttributes attr = prov.readAttributes(cleartextPath, BasicFileAttributes.class);
 
-			Mockito.verify(attributeComponentBuilder).ciphertextPath(ciphertextRawPath);
-			Mockito.verify(attributeComponentBuilder).ciphertextFileType(CiphertextFileType.FILE);
-			Mockito.verify(attributeComponentBuilder).ciphertextAttributes(ciphertextBasicAttr);
+			Mockito.verify(attributeComponentFactory, Mockito.times(1)).create(ciphertextRawPath, CiphertextFileType.FILE, ciphertextBasicAttr);
 			Assertions.assertEquals(ciphertextBasicAttr, attr);
 		}
 

From 8e62d5104b9c9cef4d509c6ce50ef0174ad404e3 Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 10:36:44 +0100
Subject: [PATCH 06/12] replace builder with factory for AttributeViewComponent

---
 .../cryptofs/attr/AttributeViewComponent.java   | 14 +++-----------
 .../cryptofs/attr/AttributeViewProvider.java    | 17 +++++------------
 2 files changed, 8 insertions(+), 23 deletions(-)

diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewComponent.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewComponent.java
index 7374be6a..d009cb56 100644
--- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewComponent.java
@@ -14,19 +14,11 @@ public interface AttributeViewComponent {
 
 	Optional<FileAttributeView> attributeView();
 
-	@Subcomponent.Builder
-	interface Builder {
+	@Subcomponent.Factory
+	interface Factory {
 
-		@BindsInstance
-		Builder cleartextPath(CryptoPath cleartextPath);
+		AttributeViewComponent create(@BindsInstance CryptoPath cleartextPath, @BindsInstance Class<? extends FileAttributeView> type, @BindsInstance LinkOption[] linkOptions);
 
-		@BindsInstance
-		Builder viewType(Class<? extends FileAttributeView> type);
-
-		@BindsInstance
-		Builder linkOptions(LinkOption[] linkOptions);
-
-		AttributeViewComponent build();
 	}
 
 }
diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewProvider.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewProvider.java
index c3d753e2..470deae6 100644
--- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewProvider.java
+++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeViewProvider.java
@@ -14,7 +14,6 @@
 import org.slf4j.LoggerFactory;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.attribute.FileAttributeView;
@@ -25,27 +24,21 @@ public class AttributeViewProvider {
 
 	private static final Logger LOG = LoggerFactory.getLogger(AttributeViewProvider.class);
 
-	private final Provider<AttributeViewComponent.Builder> attrViewComponentBuilderProvider;
+	private final AttributeViewComponent.Factory attrViewComponentFactory;
 
 	@Inject
-	AttributeViewProvider(Provider<AttributeViewComponent.Builder> attrViewComponentBuilderProvider) {
-		this.attrViewComponentBuilderProvider = attrViewComponentBuilderProvider;
+	AttributeViewProvider(AttributeViewComponent.Factory attrViewComponentFactory) {
+		this.attrViewComponentFactory = attrViewComponentFactory;
 	}
 
 	/**
 	 * @param cleartextPath the unencrypted path to the file
-	 * @param type          the Class object corresponding to the file attribute view
+	 * @param type the Class object corresponding to the file attribute view
 	 * @return a file attribute view of the specified type, or <code>null</code> if the attribute view type is not available
 	 * @see Files#getFileAttributeView(java.nio.file.Path, Class, java.nio.file.LinkOption...)
 	 */
 	public <A extends FileAttributeView> A getAttributeView(CryptoPath cleartextPath, Class<A> type, LinkOption... options) {
-		AttributeViewComponent.Builder builder = attrViewComponentBuilderProvider.get();
-		Optional<FileAttributeView> view = builder //
-				.cleartextPath(cleartextPath) //
-				.viewType(type) //
-				.linkOptions(options) //
-				.build() //
-				.attributeView();
+		Optional<FileAttributeView> view = attrViewComponentFactory.create(cleartextPath, type, options).attributeView();
 		if (view.isPresent() && type.isInstance(view.get())) {
 			return type.cast(view.get());
 		} else {

From a5ad978589506494a3ce55906e9c809694492382 Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 10:44:41 +0100
Subject: [PATCH 07/12] replace builder with factory for DirectoryStreamFactory

---
 .../dir/DirectoryStreamComponent.java         | 27 ++++++-------------
 .../cryptofs/dir/DirectoryStreamFactory.java  | 16 ++++-------
 .../dir/DirectoryStreamFactoryTest.java       | 11 +++-----
 3 files changed, 16 insertions(+), 38 deletions(-)

diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamComponent.java b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamComponent.java
index 4eac6698..a1b55695 100644
--- a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamComponent.java
@@ -14,25 +14,14 @@ public interface DirectoryStreamComponent {
 
 	CryptoDirectoryStream directoryStream();
 
-	@Subcomponent.Builder
-	interface Builder {
-
-		@BindsInstance
-		Builder cleartextPath(@Named("cleartextPath") Path cleartextPath);
-
-		@BindsInstance
-		Builder dirId(@Named("dirId") String dirId);
-
-		@BindsInstance
-		Builder ciphertextDirectoryStream(DirectoryStream<Path> ciphertextDirectoryStream);
-
-		@BindsInstance
-		Builder filter(DirectoryStream.Filter<? super Path> filter);
-
-		@BindsInstance
-		Builder onClose(Consumer<CryptoDirectoryStream> onClose);
-
-		DirectoryStreamComponent build();
+	@Subcomponent.Factory
+	interface Factory {
+
+		DirectoryStreamComponent create(@BindsInstance @Named("cleartextPath") Path cleartextPath, //
+										@BindsInstance @Named("dirId") String dirId, //
+										@BindsInstance DirectoryStream<Path> ciphertextDirectoryStream, //
+										@BindsInstance DirectoryStream.Filter<? super Path> filter, //
+										@BindsInstance Consumer<CryptoDirectoryStream> onClose);
 	}
 
 }
diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java
index 9aae97fd..651d7dff 100644
--- a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java
+++ b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java
@@ -20,31 +20,25 @@
 public class DirectoryStreamFactory {
 
 	private final CryptoPathMapper cryptoPathMapper;
-	private final DirectoryStreamComponent.Builder directoryStreamComponentBuilder; // sharing reusable builder via synchronized
+	private final DirectoryStreamComponent.Factory directoryStreamComponentFactory;
 	private final Map<CryptoDirectoryStream, DirectoryStream<Path>> streams = new HashMap<>();
 
 	private volatile boolean closed = false;
 
 	@Inject
-	public DirectoryStreamFactory(CryptoPathMapper cryptoPathMapper, DirectoryStreamComponent.Builder directoryStreamComponentBuilder) {
+	public DirectoryStreamFactory(CryptoPathMapper cryptoPathMapper, DirectoryStreamComponent.Factory directoryStreamComponentFactory) {
 		this.cryptoPathMapper = cryptoPathMapper;
-		this.directoryStreamComponentBuilder = directoryStreamComponentBuilder;
+		this.directoryStreamComponentFactory = directoryStreamComponentFactory;
 	}
 
+	//TODO: is synchronized still needed? One reason was, that a dagger builder was used (replaced by thread safe factory)
 	public synchronized CryptoDirectoryStream newDirectoryStream(CryptoPath cleartextDir, Filter<? super Path> filter) throws IOException {
 		if (closed) {
 			throw new ClosedFileSystemException();
 		}
 		CiphertextDirectory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir);
 		DirectoryStream<Path> ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.path, this::matchesEncryptedContentPattern);
-		CryptoDirectoryStream cleartextDirStream = directoryStreamComponentBuilder //
-				.dirId(ciphertextDir.dirId) //
-				.ciphertextDirectoryStream(ciphertextDirStream) //
-				.cleartextPath(cleartextDir) //
-				.filter(filter) //
-				.onClose(streams::remove) //
-				.build() //
-				.directoryStream();
+		var cleartextDirStream = directoryStreamComponentFactory.create(cleartextDir, ciphertextDir.dirId, ciphertextDirStream, filter, streams::remove).directoryStream();
 		streams.put(cleartextDirStream, ciphertextDirStream);
 		return cleartextDirStream;
 	}
diff --git a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
index 981a885d..c4361278 100644
--- a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java
@@ -34,20 +34,15 @@ public class DirectoryStreamFactoryTest {
 	private final FileSystemProvider provider = mock(FileSystemProvider.class, "provider");
 	private final CryptoPathMapper cryptoPathMapper = mock(CryptoPathMapper.class);
 	private final DirectoryStreamComponent directoryStreamComp = mock(DirectoryStreamComponent.class);
-	private final DirectoryStreamComponent.Builder directoryStreamBuilder = mock(DirectoryStreamComponent.Builder.class);
+	private final DirectoryStreamComponent.Factory directoryStreamFactory = mock(DirectoryStreamComponent.Factory.class);
 
-	private final DirectoryStreamFactory inTest = new DirectoryStreamFactory(cryptoPathMapper, directoryStreamBuilder);
+	private final DirectoryStreamFactory inTest = new DirectoryStreamFactory(cryptoPathMapper, directoryStreamFactory);
 
 	@SuppressWarnings("unchecked")
 
 	@BeforeEach
 	public void setup() throws IOException {
-		when(directoryStreamBuilder.cleartextPath(Mockito.any())).thenReturn(directoryStreamBuilder);
-		when(directoryStreamBuilder.dirId(Mockito.any())).thenReturn(directoryStreamBuilder);
-		when(directoryStreamBuilder.ciphertextDirectoryStream(Mockito.any())).thenReturn(directoryStreamBuilder);
-		when(directoryStreamBuilder.filter(Mockito.any())).thenReturn(directoryStreamBuilder);
-		when(directoryStreamBuilder.onClose(Mockito.any())).thenReturn(directoryStreamBuilder);
-		when(directoryStreamBuilder.build()).thenReturn(directoryStreamComp);
+		when(directoryStreamFactory.create(any(),any(),any(),any(),any())).thenReturn(directoryStreamComp);
 		when(directoryStreamComp.directoryStream()).then(invocation -> mock(CryptoDirectoryStream.class));
 		when(fileSystem.provider()).thenReturn(provider);
 	}

From f5e6b7bb528b7892dd1b1e11d0488b9058c50ebe Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 10:55:26 +0100
Subject: [PATCH 08/12] replace builder by factory for ChannelComponent

---
 .../cryptofs/ch/ChannelComponent.java         | 27 ++++++-------------
 .../cryptofs/fh/OpenCryptoFile.java           | 12 +++------
 .../cryptofs/fh/OpenCryptoFileComponent.java  |  2 +-
 .../cryptofs/fh/OpenCryptoFileTest.java       | 18 ++++---------
 4 files changed, 17 insertions(+), 42 deletions(-)

diff --git a/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java b/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java
index a3f419da..706a9185 100644
--- a/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/ch/ChannelComponent.java
@@ -13,25 +13,14 @@ public interface ChannelComponent {
 
 	CleartextFileChannel channel();
 
-	@Subcomponent.Builder
-	interface Builder {
-
-		@BindsInstance
-		Builder openOptions(EffectiveOpenOptions options);
-
-		@BindsInstance
-		Builder onClose(ChannelCloseListener listener);
-
-		@BindsInstance
-		Builder ciphertextChannel(FileChannel ciphertextChannel);
-
-		@BindsInstance
-		Builder mustWriteHeader(@MustWriteHeader boolean mustWriteHeader);
-
-		@BindsInstance
-		Builder fileHeader(FileHeader fileHeader);
-
-		ChannelComponent build();
+	@Subcomponent.Factory
+	interface Factory {
+
+		ChannelComponent create(@BindsInstance FileChannel ciphertextChannel, //
+								@BindsInstance FileHeader fileHeader, //
+								@BindsInstance @MustWriteHeader boolean mustWriteHeader, //
+								@BindsInstance EffectiveOpenOptions options, //
+								@BindsInstance ChannelCloseListener listener); //
 	}
 
 }
diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
index b41e35fe..24d03020 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFile.java
@@ -9,7 +9,6 @@
 package org.cryptomator.cryptofs.fh;
 
 import org.cryptomator.cryptofs.EffectiveOpenOptions;
-import org.cryptomator.cryptofs.ch.ChannelComponent;
 import org.cryptomator.cryptofs.ch.CleartextFileChannel;
 import org.cryptomator.cryptolib.api.Cryptor;
 import org.cryptomator.cryptolib.api.FileHeader;
@@ -87,14 +86,9 @@ public synchronized FileChannel newFileChannel(EffectiveOpenOptions options, Fil
 				isNewHeader = false;
 			}
 			initFileSize(ciphertextFileChannel);
-			ChannelComponent channelComponent = component.newChannelComponent() //
-					.ciphertextChannel(ciphertextFileChannel) //
-					.openOptions(options) //
-					.onClose(this::channelClosed) //
-					.mustWriteHeader(isNewHeader) //
-					.fileHeader(header) //
-					.build();
-			cleartextFileChannel = channelComponent.channel();
+			cleartextFileChannel = component.newChannelComponent() //
+					.create(ciphertextFileChannel, header, isNewHeader, options, this::channelClosed) //
+					.channel();
 		} finally {
 			if (cleartextFileChannel == null) { // i.e. something didn't work
 				closeQuietly(ciphertextFileChannel);
diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java
index dd3ee6a5..af23314e 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java
@@ -12,7 +12,7 @@ public interface OpenCryptoFileComponent {
 
 	OpenCryptoFile openCryptoFile();
 
-	ChannelComponent.Builder newChannelComponent();
+	ChannelComponent.Factory newChannelComponent();
 
 	@Subcomponent.Builder
 	interface Builder {
diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
index 343aa2bd..afd5616d 100644
--- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFileTest.java
@@ -1,7 +1,6 @@
 package org.cryptomator.cryptofs.fh;
 
 import com.google.common.jimfs.Configuration;
-import com.google.common.jimfs.Feature;
 import com.google.common.jimfs.Jimfs;
 import org.cryptomator.cryptofs.EffectiveOpenOptions;
 import org.cryptomator.cryptofs.ReadonlyFlag;
@@ -53,7 +52,7 @@ public class OpenCryptoFileTest {
 	private AtomicLong fileSize = new AtomicLong(-1l);
 	private AtomicReference<Instant> lastModified = new AtomicReference(Instant.ofEpochMilli(0));
 	private OpenCryptoFileComponent openCryptoFileComponent = mock(OpenCryptoFileComponent.class);
-	private ChannelComponent.Builder channelComponentBuilder = mock(ChannelComponent.Builder.class);
+	private ChannelComponent.Factory channelComponentFactory = mock(ChannelComponent.Factory.class);
 	private ChannelComponent channelComponent = mock(ChannelComponent.class);
 
 	@BeforeAll
@@ -107,19 +106,12 @@ public void setup() throws IOException {
 			listener = new AtomicReference<>();
 			ciphertextChannel = new AtomicReference<>();
 
-			Mockito.when(openCryptoFileComponent.newChannelComponent()).thenReturn(channelComponentBuilder);
-			Mockito.when(channelComponentBuilder.ciphertextChannel(Mockito.any())).thenAnswer(invocation -> {
+			Mockito.when(openCryptoFileComponent.newChannelComponent()).thenReturn(channelComponentFactory);
+			Mockito.when(channelComponentFactory.create(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any())).thenAnswer(invocation -> {
 				ciphertextChannel.set(invocation.getArgument(0));
-				return channelComponentBuilder;
+				listener.set(invocation.getArgument(4));
+				return channelComponent;
 			});
-			Mockito.when(channelComponentBuilder.openOptions(Mockito.any())).thenReturn(channelComponentBuilder);
-			Mockito.when(channelComponentBuilder.onClose(Mockito.any())).thenAnswer(invocation -> {
-				listener.set(invocation.getArgument(0));
-				return channelComponentBuilder;
-			});
-			Mockito.when(channelComponentBuilder.fileHeader(Mockito.any())).thenReturn(channelComponentBuilder);
-			Mockito.when(channelComponentBuilder.mustWriteHeader(Mockito.anyBoolean())).thenReturn(channelComponentBuilder);
-			Mockito.when(channelComponentBuilder.build()).thenReturn(channelComponent);
 			Mockito.when(channelComponent.channel()).thenReturn(cleartextFileChannel);
 		}
 

From 6a9a1efa53185ba47d2f10f9b0b894dae3f16fcf Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 11:18:13 +0100
Subject: [PATCH 09/12] replace builder with factory of OpenCryptoFileComponent

---
 .../cryptofs/fh/OpenCryptoFileComponent.java  | 13 ++++------
 .../cryptofs/fh/OpenCryptoFiles.java          | 18 ++++----------
 .../cryptofs/fh/OpenCryptoFilesTest.java      | 24 ++++++-------------
 3 files changed, 15 insertions(+), 40 deletions(-)

diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java
index af23314e..9db16024 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFileComponent.java
@@ -14,16 +14,11 @@ public interface OpenCryptoFileComponent {
 
 	ChannelComponent.Factory newChannelComponent();
 
-	@Subcomponent.Builder
-	interface Builder {
+	@Subcomponent.Factory
+	interface Factory {
 
-		@BindsInstance
-		Builder path(@OriginalOpenFilePath Path path);
-
-		@BindsInstance
-		Builder onClose(FileCloseListener listener);
-
-		OpenCryptoFileComponent build();
+		OpenCryptoFileComponent create(@BindsInstance @OriginalOpenFilePath Path path, //
+									   @BindsInstance FileCloseListener onCloseListener);
 	}
 
 }
diff --git a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java
index 7dd4ecee..1fa916d7 100644
--- a/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java
+++ b/src/main/java/org/cryptomator/cryptofs/fh/OpenCryptoFiles.java
@@ -12,7 +12,6 @@
 import org.cryptomator.cryptofs.EffectiveOpenOptions;
 
 import javax.inject.Inject;
-import javax.inject.Provider;
 import java.io.Closeable;
 import java.io.IOException;
 import java.nio.BufferUnderflowException;
@@ -30,12 +29,12 @@
 @CryptoFileSystemScoped
 public class OpenCryptoFiles implements Closeable {
 
-	private final Provider<OpenCryptoFileComponent.Builder> openCryptoFileComponentBuilderProvider;
+	private final OpenCryptoFileComponent.Factory openCryptoFileComponentFactory;
 	private final ConcurrentMap<Path, OpenCryptoFile> openCryptoFiles = new ConcurrentHashMap<>();
 
 	@Inject
-	OpenCryptoFiles(Provider<OpenCryptoFileComponent.Builder> openCryptoFileComponentBuilderProvider) {
-		this.openCryptoFileComponentBuilderProvider = openCryptoFileComponentBuilderProvider;
+	OpenCryptoFiles(OpenCryptoFileComponent.Factory openCryptoFileComponentFactory) {
+		this.openCryptoFileComponentFactory = openCryptoFileComponentFactory;
 	}
 
 	/**
@@ -61,16 +60,7 @@ public Optional<OpenCryptoFile> get(Path ciphertextPath) {
 	 */
 	public OpenCryptoFile getOrCreate(Path ciphertextPath) {
 		Path normalizedPath = ciphertextPath.toAbsolutePath().normalize();
-		return openCryptoFiles.computeIfAbsent(normalizedPath, this::create); // computeIfAbsent is atomic, "create" is called at most once
-	}
-
-	private OpenCryptoFile create(Path normalizedPath) {
-		OpenCryptoFileComponent.Builder builder = openCryptoFileComponentBuilderProvider.get();
-		OpenCryptoFileComponent openCryptoFileComponent = builder //
-				.path(normalizedPath) //
-				.onClose(openCryptoFiles::remove) //
-				.build();
-		return openCryptoFileComponent.openCryptoFile();
+		return openCryptoFiles.computeIfAbsent(normalizedPath, p -> openCryptoFileComponentFactory.create(p, openCryptoFiles::remove).openCryptoFile()); // computeIfAbsent is atomic, "create" is called at most once
 	}
 
 	public void writeCiphertextFile(Path ciphertextPath, EffectiveOpenOptions openOptions, ByteBuffer contents) throws IOException {
diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
index ea71bd91..c492df00 100644
--- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
@@ -16,12 +16,12 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 
 public class OpenCryptoFilesTest {
 
-	private final Provider<OpenCryptoFileComponent.Builder> openCryptoFileComponentBuilderProvider = mock(Provider.class);
-	private final OpenCryptoFileComponent.Builder openCryptoFileComponentBuilder = mock(OpenCryptoFileComponent.Builder.class);
+	private final OpenCryptoFileComponent.Factory openCryptoFileComponentFactory = mock(OpenCryptoFileComponent.Factory.class);
 	private final OpenCryptoFile file = mock(OpenCryptoFile.class, "file");
 	private final FileChannel ciphertextFileChannel = Mockito.mock(FileChannel.class);
 
@@ -32,14 +32,10 @@ public void setup() throws IOException, ReflectiveOperationException {
 		OpenCryptoFileComponent subComponent = mock(OpenCryptoFileComponent.class);
 		Mockito.when(subComponent.openCryptoFile()).thenReturn(file);
 
-		Mockito.when(openCryptoFileComponentBuilderProvider.get()).thenReturn(openCryptoFileComponentBuilder);
-		Mockito.when(openCryptoFileComponentBuilder.path(Mockito.any())).thenReturn(openCryptoFileComponentBuilder);
-		Mockito.when(openCryptoFileComponentBuilder.onClose(Mockito.any())).thenReturn(openCryptoFileComponentBuilder);
-		Mockito.when(openCryptoFileComponentBuilder.build()).thenReturn(subComponent);
-
+		Mockito.when(openCryptoFileComponentFactory.create(Mockito.any(), Mockito.any())).thenReturn(subComponent);
 		Mockito.when(file.newFileChannel(Mockito.any())).thenReturn(ciphertextFileChannel);
 
-		inTest = new OpenCryptoFiles(openCryptoFileComponentBuilderProvider);
+		inTest = new OpenCryptoFiles(openCryptoFileComponentFactory);
 	}
 
 	@Test
@@ -52,7 +48,7 @@ public void testGetOrCreate() {
 		OpenCryptoFile file2 = mock(OpenCryptoFile.class);
 		Mockito.when(subComponent2.openCryptoFile()).thenReturn(file2);
 
-		Mockito.when(openCryptoFileComponentBuilder.build()).thenReturn(subComponent1, subComponent2);
+		Mockito.when(openCryptoFileComponentFactory.create(Mockito.any(), Mockito.any())).thenReturn(subComponent1, subComponent2);
 
 		Path p1 = Paths.get("/foo");
 		Path p2 = Paths.get("/bar");
@@ -140,24 +136,18 @@ public void testCloseClosesRemainingOpenFiles() {
 		Path path1 = Mockito.mock(Path.class, "/file1");
 		Mockito.when(path1.toAbsolutePath()).thenReturn(path1);
 		Mockito.when(path1.normalize()).thenReturn(path1);
-		OpenCryptoFileComponent.Builder subComponentBuilder1 = mock(OpenCryptoFileComponent.Builder.class);
 		OpenCryptoFileComponent subComponent1 = mock(OpenCryptoFileComponent.class);
 		OpenCryptoFile file1 = mock(OpenCryptoFile.class, "file1");
-		Mockito.when(openCryptoFileComponentBuilder.path(path1)).thenReturn(subComponentBuilder1);
-		Mockito.when(subComponentBuilder1.onClose(Mockito.any())).thenReturn(subComponentBuilder1);
-		Mockito.when(subComponentBuilder1.build()).thenReturn(subComponent1);
+		Mockito.when(openCryptoFileComponentFactory.create(Mockito.eq(path1), Mockito.any())).thenReturn(subComponent1);
 		Mockito.when(subComponent1.openCryptoFile()).thenReturn(file1);
 		Mockito.when(file1.getCurrentFilePath()).thenReturn(path1);
 
 		Path path2 = Mockito.mock(Path.class, "/file2");
 		Mockito.when(path2.toAbsolutePath()).thenReturn(path2);
 		Mockito.when(path2.normalize()).thenReturn(path2);
-		OpenCryptoFileComponent.Builder subComponentBuilder2 = mock(OpenCryptoFileComponent.Builder.class);
 		OpenCryptoFileComponent subComponent2 = mock(OpenCryptoFileComponent.class);
 		OpenCryptoFile file2 = mock(OpenCryptoFile.class, "file2");
-		Mockito.when(openCryptoFileComponentBuilder.path(path2)).thenReturn(subComponentBuilder2);
-		Mockito.when(subComponentBuilder2.onClose(Mockito.any())).thenReturn(subComponentBuilder2);
-		Mockito.when(subComponentBuilder2.build()).thenReturn(subComponent2);
+		Mockito.when(openCryptoFileComponentFactory.create(Mockito.eq(path2), Mockito.any())).thenReturn(subComponent2);
 		Mockito.when(subComponent2.openCryptoFile()).thenReturn(file2);
 		Mockito.when(file2.getCurrentFilePath()).thenReturn(path2);
 

From 62d7e9edf0c25d784983ce84a798c01369b84674 Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 11:31:15 +0100
Subject: [PATCH 10/12] Apply suggestions from code review

Co-authored-by: Sebastian Stenzel <overheadhunter@users.noreply.github.com>
---
 .../java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java    | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
index c492df00..fa01dbfb 100644
--- a/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/fh/OpenCryptoFilesTest.java
@@ -16,7 +16,6 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 
 public class OpenCryptoFilesTest {

From 5bd60dd6d6a6ec5b6a01a27f9a95b777e1dadf9e Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 12:01:15 +0100
Subject: [PATCH 11/12] Closes #158

---
 .../java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java
index b1c9039a..436dde78 100644
--- a/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java
+++ b/src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java
@@ -93,7 +93,7 @@ protected boolean isReadable() {
 	}
 
 	@Override
-	protected int readLocked(ByteBuffer dst, long position) throws IOException {
+	protected synchronized int readLocked(ByteBuffer dst, long position) throws IOException {
 		int origLimit = dst.limit();
 		long limitConsideringEof = fileSize.get() - position;
 		if (limitConsideringEof < 1) {

From fe1dbd5b7ac269a90c7098e43c97b05002a67786 Mon Sep 17 00:00:00 2001
From: Armin Schrenk <armin.schrenk@skymatic.de>
Date: Wed, 15 Mar 2023 12:23:35 +0100
Subject: [PATCH 12/12] prepare 2.6.2

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 4a539e05..9edc1126 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>org.cryptomator</groupId>
 	<artifactId>cryptofs</artifactId>
-	<version>2.7.0-SNAPSHOT</version>
+	<version>2.6.2</version>
 	<name>Cryptomator Crypto Filesystem</name>
 	<description>This library provides the Java filesystem provider used by Cryptomator.</description>
 	<url>https://github.com/cryptomator/cryptofs</url>