com.microsoft.tfs.client.common.repository.RepositoryManager.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.client.common.repository.RepositoryManager.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.client.common.repository;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.microsoft.tfs.core.clients.versioncontrol.WorkspaceKey;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Workspace;
import com.microsoft.tfs.core.clients.versioncontrol.workspacecache.WorkspaceInfo;
import com.microsoft.tfs.util.Check;
import com.microsoft.tfs.util.TypesafeEnum;
import com.microsoft.tfs.util.listeners.SingleListenerFacade;

public class RepositoryManager {
    private static final Log log = LogFactory.getLog(RepositoryManager.class);

    /*
     * This lock arbitrates all the repository fields in this class.
     */
    private final Object lock = new Object();

    /**
     * A list of all {@link TFSRepository}s held by this manager. A map of
     * {@link WorkspaceKey} to {@link TFSRepository} items.
     */
    private final List<TFSRepository> repositoryList = new ArrayList<TFSRepository>();
    private final Map<WorkspaceKey, TFSRepository> repositoryMap = new HashMap<WorkspaceKey, TFSRepository>();
    private TFSRepository defaultRepository = null;

    private final SingleListenerFacade listeners = new SingleListenerFacade(RepositoryManagerListener.class);

    public RepositoryManager() {
        log.debug("RepositoryManager started"); //$NON-NLS-1$
    }

    public final void addListener(final RepositoryManagerListener listener) {
        listeners.addListener(listener);
    }

    public final void removeListener(final RepositoryManagerListener listener) {
        listeners.removeListener(listener);
    }

    public final TFSRepository[] getRepositories() {
        synchronized (lock) {
            return repositoryList.toArray(new TFSRepository[repositoryList.size()]);
        }
    }

    /**
     * Convenience method to get the current repositories and add a listener for
     * new ones. Exists so that you can add a repository in the repository
     * manager lock, meaning that you will be guaranteed not to have a race
     * between getting the list and adding a listener.
     *
     */
    public final TFSRepository[] getRepositoriesAndAddListener(final RepositoryManagerListener listener) {
        synchronized (lock) {
            listeners.addListener(listener);

            return getRepositories();
        }
    }

    public final TFSRepository setDefaultRepository(final Workspace workspace) {
        Check.notNull(workspace, "workspace"); //$NON-NLS-1$

        synchronized (lock) {
            TFSRepository repository = getRepository(workspace);

            if (repository == null) {
                repository = new TFSRepository(workspace);
            }

            return setDefaultRepository(repository);
        }
    }

    public final TFSRepository setDefaultRepository(final TFSRepository repository) {
        Check.notNull(repository, "repository"); //$NON-NLS-1$

        final WorkspaceKey key = new WorkspaceKey(repository.getWorkspace());

        synchronized (lock) {
            /* See if this repository is already being managed */
            final TFSRepository existingRepository = repositoryMap.get(key);

            /*
             * An equivalent but not identical repository is being set, we need
             * to replace
             */
            if (existingRepository == null || existingRepository != repository) {
                /*
                 * TODO: this is to work around the existing UI code that
                 * expects one repository. We keep only a single repository in
                 * repository manager. So if we're adding a new repository, make
                 * sure to eliminate the existing one.
                 */
                if (defaultRepository != null) {
                    defaultRepository.close();
                    removeRepository(defaultRepository);
                }

                final RepositoryReplaceResults results = addRepositoryInternal(repository);

                /* Already the default, don't do any more */
                if (results.isDefaultRepository()) {
                    return repository;
                }
            }

            defaultRepository = repository;
            getListener().onDefaultRepositoryChanged(new RepositoryManagerEvent(this, repository));

            return repository;
        }
    }

    public final TFSRepository getDefaultRepository() {
        synchronized (lock) {
            return defaultRepository;
        }
    }

    /**
     * Convenience method to get the current repositories and add a listener for
     * new ones. Exists so that you can add a repository in the repository
     * manager lock, meaning that you will be guaranteed not to have a race
     * between getting the list and adding a listener.
     *
     */
    public TFSRepository getDefaultRepositoryAndAddListener(final RepositoryManagerListener listener) {
        synchronized (lock) {
            listeners.addListener(listener);

        }
        return defaultRepository;
    }

    public final TFSRepository addRepository(final Workspace workspace) {
        Check.notNull(workspace, "workspace"); //$NON-NLS-1$

        final TFSRepository repository = new TFSRepository(workspace);
        try {
            addRepository(repository);
        } catch (final RepositoryConflictException e) {
            // Ensure the repository we created gets closed
            repository.close();
            throw e;
        }

        return repository;
    }

    /**
     * @throws RepositoryExistsException
     *         If the given repository already is managed. This is so that
     *         callers do not fire added listeners erroneously. Make sure to
     *         call {@link TFSRepository#close()} if the repository object will
     *         no longer be used.
     */
    public final void addRepository(final TFSRepository repository) {
        Check.notNull(repository, "repository"); //$NON-NLS-1$

        addRepositoryInternal(repository);
    }

    /**
     * @throws RepositoryConflictException
     *         If the given repository already is managed. This is so that
     *         callers do not fire added listeners erroneously. Make sure to
     *         call {@link TFSRepository#close()} if the repository object will
     *         no longer be used.
     */
    private final RepositoryReplaceResults addRepositoryInternal(final TFSRepository repository) {
        Check.notNull(repository, "repository"); //$NON-NLS-1$

        RepositoryReplaceResults results;

        results = replaceRepositoryInternal(repository);

        if (results.isIdenticalRepository()) {
            return results;
        }

        final TFSRepository existingRepository = results.getExistingRepository();

        if (existingRepository != null) {
            getListener().onRepositoryRemoved(new RepositoryManagerEvent(this, existingRepository));
        }

        getListener().onRepositoryAdded(new RepositoryManagerEvent(this, repository));

        if (results.isDefaultRepository() && existingRepository != null) {
            getListener().onDefaultRepositoryChanged(new RepositoryManagerEvent(this, repository));
        }

        return results;
    }

    /**
     * This will begin managing the existing TFS Repository. If there is already
     * a TFS Repository being managed for the given workspace, it will be
     * removed and the new one will be added, unless the given TFS Repository is
     * equal to the existing TFS Repository, in which case an exception is
     * thrown.
     *
     * @param repository
     *        The TFS Repository to begin managing
     * @return The existing repository for this workspace
     * @throws RepositoryExistsException
     *         If the given repository already is managed. This is so that
     *         callers do not fire added listeners erroneously. Make sure to
     *         call {@link TFSRepository#close()} if the repository object will
     *         no longer be used.
     */
    private final RepositoryReplaceResults replaceRepositoryInternal(final TFSRepository repository) {
        Check.notNull(repository, "repository"); //$NON-NLS-1$

        final WorkspaceKey key = new WorkspaceKey(repository.getWorkspace());

        boolean isDefaultRepository = false;
        TFSRepository existingRepository;

        synchronized (lock) {
            /*
             * TODO: there's bunches of legacy UI code that assumes that we have
             * a single connection with a single (TFS) workspace. This exists
             * here to ensure that we don't actually have multiple TFS
             * Repositories before the UI code is actually capable of handling
             * it.
             */
            if (repositoryList.size() > 0) {
                throw new RepositoryConflictException();
            }

            final String messageFormat = "replaceRepositoryInternal: creating new repository, key=[{0}]"; //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, key);
            log.trace(message);

            existingRepository = repositoryMap.get(key);

            /*
             * If we were asked to add an existing repository, throw an
             * exception for the callers to deal with. This prevents us from
             * firing removed/added events for the same repository.
             */
            if (existingRepository != null && existingRepository == repository) {
                return new ExistingRepositoryReplaceResults(repository);
            } else if (existingRepository != null) {
                repositoryList.remove(existingRepository);
            }

            repositoryList.add(repository);
            repositoryMap.put(key, repository);

            if (repositoryList.size() == 1) {
                isDefaultRepository = true;
                defaultRepository = repository;
            }
        }

        getListener().onDefaultRepositoryChanged(new RepositoryManagerEvent(this, repository));

        return new RepositoryReplaceResults(isDefaultRepository, existingRepository);
    }

    public final TFSRepository getRepository(final Workspace workspace) {
        Check.notNull(workspace, "workspace"); //$NON-NLS-1$

        final WorkspaceKey key = new WorkspaceKey(workspace);

        synchronized (lock) {
            final TFSRepository repository = repositoryMap.get(key);
            return repository;
        }
    }

    public final TFSRepository getRepository(final WorkspaceInfo cachedWorkspace) {
        Check.notNull(cachedWorkspace, "cachedWorkspace"); //$NON-NLS-1$

        final WorkspaceKey key = new WorkspaceKey(cachedWorkspace);

        synchronized (lock) {
            final TFSRepository repository = repositoryMap.get(key);

            final String messageFormat = "tryGetRepository, key=[{0}], result=[{1}]"; //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, key, repository);
            log.trace(message);
            return repository;
        }
    }

    public final TFSRepository getOrCreateRepository(final Workspace workspace) {
        return getOrCreateRepository(workspace, null);
    }

    public final TFSRepository getOrCreateRepository(final Workspace workspace,
            final RepositoryStatusContainer statusContainer) {
        Check.notNull(workspace, "workspace"); //$NON-NLS-1$

        final WorkspaceKey key = new WorkspaceKey(workspace);
        TFSRepository repository;

        synchronized (lock) {
            if ((repository = repositoryMap.get(key)) != null) {
                if (statusContainer != null) {
                    statusContainer.setRepositoryStatus(RepositoryStatus.EXISTING);
                }

                return repository;
            }

            repository = new TFSRepository(workspace);

            final RepositoryReplaceResults results;
            try {
                results = replaceRepositoryInternal(repository);
            } catch (final RepositoryConflictException e) {
                // Ensure the repository we created gets closed
                repository.close();
                throw e;
            }

            if (results.isIdenticalRepository()) {
                /*
                 * Can't happen unless code erroneously adds a repository
                 * without locking
                 */
                throw new ConcurrentModificationException("The RepositoryManager was modified outside of a lock"); //$NON-NLS-1$
            }
        }

        getListener().onRepositoryAdded(new RepositoryManagerEvent(this, repository));

        if (statusContainer != null) {
            statusContainer.setRepositoryStatus(RepositoryStatus.CREATED);
        }

        return repository;
    }

    public final void removeAllRepositories() {
        final List<TFSRepository> removedRepositories;

        synchronized (lock) {
            removedRepositories = new ArrayList<TFSRepository>(repositoryList);

            for (final Entry<WorkspaceKey, TFSRepository> repositoryEntry : repositoryMap.entrySet()) {
                repositoryMap.remove(repositoryEntry.getKey());
                repositoryList.remove(repositoryEntry.getValue());
            }

            defaultRepository = null;
        }

        for (final TFSRepository repository : removedRepositories) {
            getListener().onRepositoryRemoved(new RepositoryManagerEvent(this, repository));
        }

        getListener().onDefaultRepositoryChanged(new RepositoryManagerEvent(this, null));
    }

    public final void removeRepository(final TFSRepository repository) {
        Check.notNull(repository, "repository"); //$NON-NLS-1$

        final WorkspaceKey key = new WorkspaceKey(repository.getWorkspace());

        boolean wasDefaultRepository = false;
        TFSRepository newDefaultRepository = null;

        synchronized (lock) {
            final TFSRepository testRepository = repositoryMap.get(key);

            /*
             * Sanity check: two repositories can have the same WorkspaceKey but
             * in fact be different
             */
            if (testRepository == null || testRepository != repository) {
                throw new IllegalArgumentException("The specified repository is not in the RepositoryManager"); //$NON-NLS-1$
            }

            repositoryMap.remove(key);
            repositoryList.remove(testRepository);

            if (repository.equals(defaultRepository)) {
                defaultRepository = repositoryList.size() == 0 ? null : repositoryList.get(0);

                wasDefaultRepository = true;
                newDefaultRepository = defaultRepository;
            }
        }

        getListener().onRepositoryRemoved(new RepositoryManagerEvent(this, repository));

        if (wasDefaultRepository) {
            getListener().onDefaultRepositoryChanged(new RepositoryManagerEvent(this, newDefaultRepository));
        }
    }

    private RepositoryManagerListener getListener() {
        return (RepositoryManagerListener) listeners.getListener();
    }

    public static class RepositoryStatusContainer {
        private RepositoryStatus status;

        private void setRepositoryStatus(final RepositoryStatus status) {
            this.status = status;
        }

        public RepositoryStatus getRepositoryStatus() {
            return status;
        }
    }

    public static class RepositoryStatus extends TypesafeEnum {
        public static final RepositoryStatus CREATED = new RepositoryStatus(0);
        public static final RepositoryStatus EXISTING = new RepositoryStatus(1);

        private RepositoryStatus(final int value) {
            super(value);
        }
    }

    private class RepositoryReplaceResults {
        private final boolean isDefaultRepository;
        private final TFSRepository existingRepository;

        public RepositoryReplaceResults(final boolean isDefaultRepository, final TFSRepository existingRepository) {
            this.isDefaultRepository = isDefaultRepository;
            this.existingRepository = existingRepository;
        }

        public boolean isDefaultRepository() {
            return isDefaultRepository;
        }

        public TFSRepository getExistingRepository() {
            return existingRepository;
        }

        public boolean isIdenticalRepository() {
            return false;
        }
    }

    private class ExistingRepositoryReplaceResults extends RepositoryReplaceResults {
        public ExistingRepositoryReplaceResults(final TFSRepository repository) {
            super(false, repository);
        }

        @Override
        public boolean isIdenticalRepository() {
            return true;
        }
    }
}