src/main/java/org/cryptomator/sanitizer/Passphrase.java
public class Passphrase implements CharSequence, AutoCloseable {

	private final char[] chars;

	public Passphrase(char[] chars) {
		this.chars = chars;
	}

	public Passphrase(CharBuffer chars) {
		this.chars = new char[chars.remaining()];
		chars.get(this.chars);
	}

	@Override
	public void close() {
		Arrays.fill(chars, ' ');
	}

	@Override
	public int length() {
		return chars.length;
	}

	@Override
	public char charAt(int index) {
		return chars[index];
	}

	@Override
	public Passphrase subSequence(int start, int end) {
		int len = end - start;
		char[] result = new char[len];
		System.arraycopy(chars, start, result, 0, len);
		return new Passphrase(result);
	}

	@Override
	public String toString() {
		return new String(chars);
	}

}
src/main/java/org/cryptomator/sanitizer/integrity/IntegrityCheck.java
public class IntegrityCheck {

	private final CryptorHolder cryptorHolder;

	public IntegrityCheck(CryptorHolder cryptorHolder) {
		this.cryptorHolder = cryptorHolder;
	}

	public Set<Problem> check(Path path, CharSequence passphrase, boolean checkFileIntegrity) throws AbortCheckException {
		Problems problems = new Problems(path);
		try {
			cryptorHolder.createCryptor(problems, path, passphrase).ifPresent(cryptor -> {
				try {
					vaultFormatChecks(cryptor, path, checkFileIntegrity).check(problems, path);
				} finally {
					cryptor.destroy();
				}
			});
		} catch (AbortCheckException e) {
			throw e;
		} catch (Exception e) {
			problems.reportException(e);
		}
		return problems.asSet();
	}

	private Check vaultFormatChecks(Cryptor cryptor, Path pathToVault, boolean checkContentIntegrity) {
		Check referencedDirectoryExists = referencedDirectoryExists(cryptor, pathToVault);
		HasCorrespondingDirectoryFileCheck hasCorrespondingDirectoryFileCheck = hasCorrespondingDirectoryFile(cryptor, pathToVault);
		Check decryptedNameCheckForRegularFiles = decryptedNameCheck(cryptor, hasCorrespondingDirectoryFileCheck);
		HasCorrespondingDFileCheck hasCorrespondingDFile = hasCorrespondingDFileIn(pathToVault);
		Check decryptedNameCheckForLongFiles = Checks.decryptedNameCheck(cryptor, hasCorrespondingDirectoryFileCheck, hasCorrespondingDFile);
		Check emptyEncryptedFileIfEmpty = emptyEncryptedFileIfEmpty();
		return dir().containing( //
				dir().that(hasName("d")).validate(nameDoesNotContainUppercaseChars()).containing( //
						dir().that(hasName("[A-Z2-7]{2}")).validate(nameDoesNotContainLowercaseChars()).containing( //
								dir().that(hasName("[A-Z2-7]{30}")) //
										.validate(nameDoesNotContainLowercaseChars()).validate(hasCorrespondingDirectoryFileCheck) //
										.reportAs(rootDirectoryIfMachting(cryptor)) //
										.containing( //
												file().that(hasName("0([A-Z2-7]{8})*[A-Z2-7=]{8}")) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasSize(36).and(containsUuid()).and(referencedDirectoryExists)) //
														.validate(decryptedNameCheckForRegularFiles), //
												file().that(hasName("([A-Z2-7]{8})*[A-Z2-7=]{8}")) //
														.reportAs(emptyEncryptedFileIfEmpty) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasMinSize(88).and(isAuthentic(cryptor, checkContentIntegrity))) //
														.validate(decryptedNameCheckForRegularFiles), //
												file().that(hasName("[A-Z2-7]{32}\\.lng").and(hasCorrespondingMFileIn(pathToVault).that(containsValidFileName()))) //
														.reportAs(emptyEncryptedFileIfEmpty) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasMinSize(88).and(isAuthentic(cryptor, checkContentIntegrity))), //
												file().that(hasName("[A-Z2-7]{32}\\.lng").and(hasCorrespondingMFileIn(pathToVault).that(containsValidDirectoryFileName()))) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasSize(36).and(containsUuid()).and(referencedDirectoryExists)), //
												file().that(hasName("[A-Z2-7]{32}\\.lng")) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasCorrespondingMFileIn(pathToVault)), //
												file().that(hasName("0?([A-Z2-7]{8})*[A-Z2-7=]{1,7}")) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasSize(36).and(containsUuid()).and(referencedDirectoryExists)) //
														.validate(decryptedNameCheckForRegularFiles) //
														.reportAs(aFileWithMissingEqualsSign()), //
												file().that(hasName("0([A-Z2-7]{8})*[A-Z2-7=]{8}.+")) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasSize(36).and(containsUuid()).and(referencedDirectoryExists)) //
														.validate(decryptedNameCheckForRegularFiles) //
														.reportAs(aConflict()), //
												file().that(hasName("([A-Z2-7]{8})*[A-Z2-7=]{8}.+")) //
														.reportAs(emptyEncryptedFileIfEmpty) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasMinSize(88).and(isAuthentic(cryptor, checkContentIntegrity))) //
														.validate(decryptedNameCheckForRegularFiles) //
														.reportAs(aConflict()), //
												file().that(hasName("[A-Z2-7]{32}.+\\.lng")) //
														.validate(nameDoesNotContainLowercaseChars()) //
														.validate(hasCorrespondingMFileIn(pathToVault)) //
														.reportAs(aConflict())))), //
				dir().that(hasName("m")).validate(nameDoesNotContainUppercaseChars()).containing( //
						dir().that(hasName("[A-Z2-7]{2}")).validate(nameDoesNotContainLowercaseChars()).containing( //
								dir().that(hasName("[A-Z2-7]{2}")).validate(nameDoesNotContainLowercaseChars()).containing( //
										file().that(hasName("[A-Z2-7]{32}\\.lng")) //
												.validate(nameDoesNotContainLowercaseChars()) //
												.validate(hasCorrespondingDFile.and(containsValidName()))
												.validate(decryptedNameCheckForLongFiles), //
										file().that(hasName("[A-Z2-7]{32}.+\\.lng")) //
												.validate(nameDoesNotContainLowercaseChars()) //
												.validate(hasCorrespondingDFile.and(containsValidName())) //
												.validate(decryptedNameCheckForLongFiles)
												.reportAs(aConflict())))), //
				file().that(hasName("masterkey.cryptomator")).validate(nameDoesNotContainUppercaseChars()), // do not validate contents because this already happened when creating the Cryptor
				file().that(hasName("masterkey.cryptomator.bkup")).validate(isMasterkeyBackupFile()));
	}

}
src/main/java/org/cryptomator/sanitizer/integrity/problems/SolutionContext.java
public interface SolutionContext {

	public static SolutionContext simulatePrintingTo(Path vaultLocation, Cryptor cryptor, PrintStream out) {
		return new PrintStreamSolutionContext(vaultLocation, cryptor, out, true);
	}

	public static SolutionContext executePrintingTo(Path vaultLocation, Cryptor cryptor, PrintStream out) {
		return new PrintStreamSolutionContext(vaultLocation, cryptor, out, false);
	}

	void start(String format, Object... args);

	void finish();

	void fail(String format, Object... args);

	void fail(Throwable reason);

	boolean dryRun();

	Path vaultLocation();

	Cryptor cryptor();

}
src/main/java/org/cryptomator/sanitizer/Sanitizer.java
public static void printNoNewline(String line) {
	printNoNewline(line, System.out);
}
src/main/java/org/cryptomator/sanitizer/integrity/problems/Severity.java
public enum Severity {

	/**
	 * Issues which prevent the integrity check from operating correctly.
	 */
	FATAL,

	/**
	 * Issues which are guaranteed to cause problems while using the vault.
	 */
	ERROR,

	/**
	 * Issues which are not guaranteed to cause problems while using the vault.
	 */
	WARN,

	/**
	 * Issues which occur during normal operation of the vault.
	 */
	INFO

}
src/main/java/org/cryptomator/sanitizer/integrity/problems/Problem.java
public interface Problem {

	Severity severity();

	default Optional<Solution> solution() {
		return Optional.empty();
	}

	default String name() {
		return "notSolvable";
	}

}
src/main/java/org/cryptomator/sanitizer/CryptorHolder.java
public class CryptorHolder implements AutoCloseable {

	private static final int VAULT_VERSION = 6;

	private static final int VAULT_VERSION_INTRODUCING_PASSWORD_NORMALIZATION = 6;

	private Optional<Cryptor> cryptor = Optional.empty();

	public Optional<Cryptor> optionalCryptor() {
		return cryptor;
	}

	public Optional<Cryptor> createCryptor(Problems problems, Path path, CharSequence passphrase) throws IOException, AbortCheckException {
		destroyCryptor();
		Path masterkeyFile = path.resolve("masterkey.cryptomator");
		try {
			if (!isRegularFile(masterkeyFile)) {
				problems.reportMissingMasterkeyFile(masterkeyFile);
				return Optional.empty();
			}
			KeyFile keyFile = KeyFile.parse(readAllBytes(masterkeyFile));
			if (keyFile.getVersion() != VAULT_VERSION) {
				throw new AbortCheckException(format("Vault version mismatch. Expected: %d Actual: %d", VAULT_VERSION, keyFile.getVersion()));
			}
			cryptor = Optional.of(bestGuessCryptorProvider(keyFile).createFromKeyFile(keyFile, normalizePassphrase(keyFile, passphrase), keyFile.getVersion()));
			return cryptor;
		} catch (InvalidPassphraseException e) {
			throw new AbortCheckException("Invalid passphrase");
		} catch (IllegalArgumentException e) {
			if (e.getCause() instanceof InvalidKeyException) {
				throw new AbortCheckException("JCE files seem to be missing. Download from \n" //
						+ "http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html.\n" //
						+ "and install according to instructions in README.txt");
			} else {
				problems.reportInvalidMasterkeyFile(masterkeyFile);
			}
		}
		cryptor = Optional.empty();
		return cryptor;
	}

	public void destroyCryptor() {
		cryptor.ifPresent(Cryptor::destroy);
	}

	@Override
	public void close() {
		destroyCryptor();
	}

	public static CharSequence normalizePassphrase(KeyFile keyFile, CharSequence passphrase) {
		if (keyFile.getVersion() < VAULT_VERSION_INTRODUCING_PASSWORD_NORMALIZATION) {
			return passphrase;
		}
		return Normalizer.normalize(passphrase, Form.NFC);
	}

	public static CryptorProvider bestGuessCryptorProvider(KeyFile keyFile) {
		switch (keyFile.getVersion()) {
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
		case 6:
			return Cryptors.version1(strongSecureRandom());
		default:
			throw new IllegalArgumentException("Unsupported vault version " + keyFile.getVersion());
		}
	}

	private static SecureRandom strongSecureRandom() {
		try {
			return SecureRandom.getInstanceStrong();
		} catch (NoSuchAlgorithmException e) {
			throw new IllegalStateException("Java platform is required to support a strong SecureRandom.", e);
		}
	}

}
src/main/java/org/cryptomator/sanitizer/integrity/AbortCheckException.java
public class AbortCheckException extends Exception {

	public AbortCheckException(String message) {
		super(message);
	}

}
src/main/java/org/cryptomator/sanitizer/Sanitizer.java
public static void print() {
	print(System.out);
}
src/main/java/org/cryptomator/sanitizer/utils/Counter.java
public class Counter {

	private long count = 0;

	public void increment() {
		count++;
	}

	public long get() {
		return count;
	}

}


Question: package org.cryptomator.sanitizer.commands;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.isDirectory;
import static java.nio.file.Files.isRegularFile;
import static java.nio.file.Files.newBufferedWriter;
import static java.nio.file.Files.size;
import static java.nio.file.Files.walk;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.Collections.sort;
import static java.util.stream.Collectors.toList;
import static org.cryptomator.sanitizer.Sanitizer.print;
import static org.cryptomator.sanitizer.Sanitizer.printNoNewline;
import static org.cryptomator.sanitizer.integrity.problems.Severity.INFO;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.cryptomator.sanitizer.CryptorHolder;
import org.cryptomator.sanitizer.Passphrase;
import org.cryptomator.sanitizer.integrity.AbortCheckException;
import org.cryptomator.sanitizer.integrity.IntegrityCheck;
import org.cryptomator.sanitizer.integrity.problems.Problem;
import org.cryptomator.sanitizer.integrity.problems.Severity;
import org.cryptomator.sanitizer.integrity.problems.SolutionContext;
import org.cryptomator.sanitizer.utils.Counter;




class CheckRunner implements Runnable {

	private static final long KIBI = 1024;
	private static final String[] KIBI_POWERS = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"};

	private final CheckCommand args;

	public CheckRunner(CheckCommand args) {
		this.args = args;
	}

	@Override
	public void run() {
		try (CryptorHolder cryptorHolder = new CryptorHolder(); //
				Passphrase passphrase = args.passphrase()) {
			IntegrityCheck integrityCheck = new IntegrityCheck(cryptorHolder);
Answer: 			print("Scanning vault structure may take some time. Be patient...");