InMemoryAccountRepository.java

package de.japrost.jabudget.repository.inmemory;

import java.util.*;
import java.util.stream.Collectors;

import de.japrost.jabudget.domain.DomainException;
import de.japrost.jabudget.domain.DomainFailure;
import de.japrost.jabudget.domain.account.Account;
import de.japrost.jabudget.domain.account.Entry;
import de.japrost.jabudget.domain.account.EntryBuilder;
import de.japrost.jabudget.repository.AccountRepository;

/**
 * {@link AccountRepository} storing in memory without persistence. Mostly for test and development.<br>
 * This implementation is not thread safe.
 */
public class InMemoryAccountRepository implements AccountRepository {

	private final Map<String, Account> storage = new HashMap<>();
	private final Map<String, Collection<Entry>> entryStorage = new HashMap<>();

	/**
	 * {@inheritDoc}
	 * <p>
	 * <strong>This implementation</strong> stores and gives defensive copies of the given account.
	 */
	@Override
	public Account create(final Account account) throws DomainException {
		if (storage.containsKey(account.getId())) {
			throw new DomainException(DomainFailure.DUPLICATE_ENTITY);
		}
		return putInStoreDefensively(account);
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * <strong>This implementation</strong> stores and gives defensive copies of the given account.
	 */
	@Override
	public Account update(final Account account) throws DomainException {
		if (!storage.containsKey(account.getId())) {
			throw new DomainException(DomainFailure.MISSING_ENTITY);
		}
		return putInStoreDefensively(account);

	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * <strong>This implementation</strong> clears existing and adds defensive copies to store.
	 */
	@Override
	public void replaceAll(final Set<Account> accounts) {
		storage.clear();
		entryStorage.clear();
		accounts.forEach(this::putInStoreDefensively);
	}

	private Account putInStoreDefensively(final Account account) {
		final Account accountToStore = new Account.Builder(account).build();
		storage.put(account.getId(), accountToStore);
		if (!entryStorage.containsKey(account.getId())) {
			entryStorage.put(account.getId(), new ArrayList<Entry>());
		}
		return new Account.Builder(account).build();
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * <strong>This implementation</strong> gives a defensive copy of the stored accounts.
	 */
	@Override
	public Optional<Account> findById(final String id) {
		return Account.Builder.builder(storage.get(id)).buildOptional();
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * <strong>This implementation</strong> gives defensive copies of all stored accounts.
	 */
	@Override
	public Set<Account> findAll() {
		return storage.values().stream().map(account -> new Account.Builder(account).build()).collect(Collectors.toSet());
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * <strong>This implementation</strong> returns {@link Boolean#TRUE} always.
	 */
	@Override
	public Boolean delete(final String accountId) {
		storage.remove(accountId);
		entryStorage.remove(accountId);
		return Boolean.TRUE;
	}

	@Override
	public Entry create(final Entry entry) throws DomainException {
		Collection<Entry> entries = entryStorage.get(entry.getAccountId());
		if (Objects.isNull(entries)) {
			throw new DomainException(DomainFailure.MISSING_ENTITY_REFERENCE);
		}
		if (entries.contains(entry)) {
			throw new DomainException(DomainFailure.DUPLICATE_ENTITY);
		}
		return putInStoreDefensively(entries, entry);
	}

	private Entry putInStoreDefensively(final Collection<Entry> entries, final Entry value) {
		final Entry toStore = EntryBuilder.builder(value).build();
		entries.add(toStore);
		return EntryBuilder.builder(toStore).build();
	}

}