/* * Copyright (c) 2005-2016 Flamingo Kirill Grouchnikov. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of Flamingo Kirill Grouchnikov nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.pushingpixels.flamingo.api.bcb.core; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.pushingpixels.flamingo.api.bcb.BreadcrumbBarCallBack; import org.pushingpixels.flamingo.api.bcb.BreadcrumbBarException; import org.pushingpixels.flamingo.api.bcb.BreadcrumbBarExceptionHandler; import org.pushingpixels.flamingo.api.bcb.BreadcrumbItem; import org.pushingpixels.flamingo.api.bcb.BreadcrumbPathEvent; import org.pushingpixels.flamingo.api.bcb.JBreadcrumbBar; import org.pushingpixels.flamingo.api.common.StringValuePair; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNURL; import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager; import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory; import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory; import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.SVNRepositoryFactory; import org.tmatesoft.svn.core.wc.SVNWCUtil; /** * Breadcrumb bar that allows browsing multiple local and remote SVN * repositories. The implementation uses * <a href="http://www.svnkit.com">SVNKit</a> library. Make sure to read the * <a href="http://www.svnkit.com/licensing/index.html">licensing terms</a> * before using this component in your application. * * @author Kirill Grouchnikov */ public class BreadcrumbMultiSvnSelector extends JBreadcrumbBar<String> { /** * Information on a single SVN repository. * * @author Kirill Grouchnikov */ public static class SvnRepositoryInfo { /** * Repository name. */ public String name; /** * Repository URL. */ public String url; /** * User name. */ public String user; /** * Password. */ public String password; /** * Creates a new info object. * * @param name * Repository name. * @param url * Repository URL. * @param user * User name. * @param password * Password. */ public SvnRepositoryInfo(String name, String url, String user, String password) { this.name = name; this.url = url; this.user = user; this.password = password; } } /** * SVN-specific implementation of the {@link BreadcrumbBarCallBack}. * * <p> * This is tricky. Unlike other implementations that connect in the setup * method, this implementation connects when the first path selection is * done. The code in * BasicBreadcrumbBar.PopupAction.actionPerformed(ActionEvent) adds two * items - the selected SVN repository and the matching choices. The first * addition requires connecting to the selected repository, which is done * off EDT (using {@link SwingWorker}). The second addition must wait until * the connection has been established since otherwise the repository is not * yet available. In order to make the second addition wait, we use a * {@link CountDownLatch}. * </p> * * <p> * It is {@link CountDownLatch#countDown()} in the * <code>SwingWorker.done()</code> that wraps the connection. The * {@link BreadcrumbBarCallBack#getPathChoices(BreadcrumbItem[])} and * {@link BreadcrumbBarCallBack#getLeafs(BreadcrumbItem[])} call * {@link CountDownLatch#await()} on the same latch that blocks until the * connection is done. Since both these methods should be wrapped off EDT in * a separate {@link SwingWorker}, this doesn't block the UI. * </p> * * @author Kirill Grouchnikov */ protected class PathCallback extends BreadcrumbBarCallBack<String> { /** * Information on all the configured SVN repositories. */ protected List<SvnRepositoryInfo> repositories; /** * The current SVN repository. */ private SVNRepository currRepository; /** * Latch to synchronize between connecting to a selected SVN repository * and fetching folder contents. */ private CountDownLatch connectionLatch; /** * Creates a new callback. * * @param repoList * List of all SVN repositories. */ public PathCallback(BreadcrumbMultiSvnSelector.SvnRepositoryInfo... repoList) { this.repositories = new ArrayList<SvnRepositoryInfo>(); if (repoList != null) { for (BreadcrumbMultiSvnSelector.SvnRepositoryInfo repository : repoList) { this.addSvnRepositoryInfo(repository); } } getModel().addPathListener((BreadcrumbPathEvent event) -> { final List<BreadcrumbItem<String>> newPath = getModel().getItems(); // If one element - an SVN repository has been // selected. Need to connect to it and update the // currRepository field. if (newPath.size() == 1) connectionLatch = new CountDownLatch(1); SwingUtilities.invokeLater(() -> { // If one element - an SVN repository has been // selected. Need to connect to it and update // the currRepository field. if (newPath.size() != 1) return; final String newSvnName = newPath.get(0).getData(); // System.out.println("Connecting to " + // newSvnName); // find the connection params for (final SvnRepositoryInfo repoInfo : repositories) { if (newSvnName.equals(repoInfo.name)) { // connect SwingWorker<SVNRepository, Void> worker = new SwingWorker<SVNRepository, Void>() { @Override protected SVNRepository doInBackground() throws Exception { try { SVNRepository repository = SVNRepositoryFactory .create(SVNURL.parseURIEncoded(repoInfo.url)); ISVNAuthenticationManager authManager = SVNWCUtil .createDefaultAuthenticationManager(repoInfo.user, repoInfo.password); repository.setAuthenticationManager(authManager); return repository; } catch (SVNException svne) { List<BreadcrumbBarExceptionHandler> handlers = getExceptionHandlers(); for (BreadcrumbBarExceptionHandler handler : handlers) { handler.onException(svne); } return null; } } @Override protected void done() { try { currRepository = get(); connectionLatch.countDown(); } catch (Exception exc) { } } }; worker.execute(); } } }); }); } /** * Adds information on a new SVN repository. * * @param repositoryInfo */ public void addSvnRepositoryInfo(SvnRepositoryInfo repositoryInfo) { this.repositories.add(repositoryInfo); } /* * (non-Javadoc) * * @see org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#setup() */ @Override public void setup() { DAVRepositoryFactory.setup(); SVNRepositoryFactoryImpl.setup(); FSRepositoryFactory.setup(); } /* * (non-Javadoc) * * @see * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getPathChoices(java. * util.List) */ @Override public List<StringValuePair<String>> getPathChoices(List<BreadcrumbItem<String>> path) throws BreadcrumbBarException { try { if (connectionLatch != null) connectionLatch.await(); } catch (InterruptedException ie) { } // System.out.println("Getting choices for " + path); if (path == null) { // root - list all the repositories. List<StringValuePair<String>> result = new ArrayList<StringValuePair<String>>(); for (SvnRepositoryInfo repoInfo : repositories) { StringValuePair<String> toAdd = new StringValuePair<String>(repoInfo.name, repoInfo.name); result.add(toAdd); } return result; } String lastPath = (path.size() == 1) ? "" : path.get(path.size() - 1).getData(); // System.out.println("Last path is " + lastPath + ", repo " // + this.currRepository); try { return BreadcrumbSvnSelector.getPathChoices(this.currRepository, lastPath); } catch (SVNException svne) { if (this.throwsExceptions) { throw new BreadcrumbBarException(svne); } return null; } } /* * (non-Javadoc) * * @see * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafs(java.util.List) */ @Override public List<StringValuePair<String>> getLeafs(List<BreadcrumbItem<String>> path) throws BreadcrumbBarException { if (path == null) { // root - no leafs, only repositories. return null; } try { if (connectionLatch != null) connectionLatch.await(); } catch (InterruptedException ie) { } String lastPath = (path.size() == 1) ? "" : path.get(path.size() - 1).getData(); try { return BreadcrumbSvnSelector.getLeafs(this.currRepository, lastPath); } catch (SVNException svne) { if (this.throwsExceptions) { throw new BreadcrumbBarException(svne); } return null; } } /* * (non-Javadoc) * * @see * org.jvnet.flamingo.bcb.BreadcrumbBarCallBack#getLeafContent(java. * lang.Object) */ @Override public InputStream getLeafContent(String leaf) throws BreadcrumbBarException { try { return BreadcrumbSvnSelector.getLeafContent(this.currRepository, leaf); } catch (SVNException svne) { if (this.throwsExceptions) { throw new BreadcrumbBarException(svne); } return null; } } } /** * Creates a new breadcrumb bar with the specified SVN repositories. * * @param repositories * List of all SVN repositories. */ public BreadcrumbMultiSvnSelector( BreadcrumbMultiSvnSelector.SvnRepositoryInfo... repositories) { super(null); this.callback = new PathCallback(repositories); this.callback.setup(); this.getModel().reset(); } }