text: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  
  package org.netbeans.modules.bugtracking.ui.repository;
  
  import org.netbeans.modules.bugtracking.BugtrackingOwnerSupport;
  import org.openide.filesystems.FileObject;
  import java.awt.Component;
  import java.awt.EventQueue;
  import java.awt.event.HierarchyEvent;
  import java.awt.event.HierarchyListener;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
  import java.io.File;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javax.swing.ComboBoxModel;
  import javax.swing.JComboBox;
  import javax.swing.JComponent;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  import javax.swing.event.PopupMenuEvent;
  import javax.swing.event.PopupMenuListener;
  import org.netbeans.modules.bugtracking.BugtrackingManager;
  import org.openide.util.RequestProcessor;
  import static java.awt.event.HierarchyEvent.DISPLAYABILITY_CHANGED;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.List;
  import static java.util.logging.Level.FINEST;
  import org.netbeans.api.queries.VersioningQuery;
  import org.netbeans.modules.bugtracking.RepositoryImpl;
  import org.netbeans.modules.bugtracking.RepositoryRegistry;
  import org.netbeans.modules.bugtracking.api.IssueQuickSearch.RepositoryFilter;
  import org.netbeans.modules.bugtracking.api.Repository;
  import org.netbeans.modules.bugtracking.util.BugtrackingUtil;
  import org.netbeans.modules.bugtracking.commons.NBBugzillaUtils;
  
  /**
   * Loads the list of repositories and determines the default one off the AWT
   * thread. Once the results are ready, updates the UI (the combo-box).
   * It is activated by method {@code hierarchyChanged} when the component
   * containing the repository ComboBox is displayed. At this moment, a routine
   * for finding the known repositories  and for determination the default
   * repository is started in a separated thread. As soon as the list of known
   * repositories is ready, the repositories combo-box is updated. When the
   * default repository is determined, it is pre-selected in the combo-box,
   * unless the user had already selected some repository.
   *
   * @author  Marian Petras
   * @author  Tomas Stupka
   */
  public final class RepositoryComboSupport implements ItemListener, Runnable {
  
      static final String LOADING_REPOSITORIES = "loading";               //NOI18N
      static final String NO_REPOSITORIES = "no repositories";            //NOI18N
      static final String SELECT_REPOSITORY = "select";                   //NOI18N
      private RepositoryFilter filter;
      
      private static final Logger LOG = Logger.getLogger(RepositoryComboSupport.class.getName());
  
      private final JComboBox comboBox;
      private final FileObject refFile;
      private final boolean preselectSingleRepo;
      private RepositoryComboModel comboBoxModel;
      private DisplayabilityListener displayabilityListener;
      private boolean shutdown;
      private boolean repositoriesDisplayed = false;
      private boolean defaultRepoSelected = false;
      private volatile FileObject[] selectedFiles;
      private volatile Repository[] repositories;
      private volatile boolean defaultRepoComputationPending;
      private volatile Repository defaultRepo;
      private boolean hideLocalRepository;
  
      /**
       * Setups the given repository with RepositoryComboRenderer. As soon as the component holding the combo
       * is displayed a list of all known repositories is retrieved and the first in the list is selected
       *
       * @param component component with the combobox
       * @param comboBox repository combobox
       * @param selectRepoIfSingle  if {@code true} and there is only one repository
       *                        known, then the (only) repository is preselected;
       *                        otherwise no repository is selected in the combo
       * @return
       */
      public static RepositoryComboSupport setup(JComponent component, JComboBox comboBox, boolean selectRepoIfSingle) {
          return setup(component, comboBox, RepositoryFilter.ALL, selectRepoIfSingle);
      }
  
      /**
       * Setups the given repository with RepositoryComboRenderer. As soon as the component holding the combo
       * is displayed a list of all known repositories is retrieved and either the default repository will be
       * selected or nothing if it's not between the known repositories
       *
       * @param component component with the combobox
       * @param comboBox repository combobox
       * @param defaultRepo the repository to be selected
       * @return
       */
      public static RepositoryComboSupport setup(JComponent component, JComboBox comboBox, Repository defaultRepo) {
          return setup(component, comboBox, RepositoryFilter.ALL, defaultRepo);
      }
      
  /**
       * Setups the given repository with RepositoryComboRenderer. As soon as the component holding the combo
       * is displayed a list of all known repositories is retrieved and either the repository associated with
       * referenceFile will be selected or nothing if there is no such repository
       *
       * @param component component with the combobox
       * @param comboBox repository combobox
       * @param referenceFile file associated with a repository
       * @return
       */
      public static RepositoryComboSupport setup(JComponent component, JComboBox comboBox, FileObject referenceFile) {
          return setup(component, comboBox, RepositoryFilter.ALL, referenceFile);
      }
      
      /**
       * Setups the given repository with RepositoryComboRenderer. As soon as the component holding the combo
       * is displayed a list of all known repositories is retrieved and the first in the list is selected
       *
       * @param component component with the combobox
       * @param comboBox repository combobo
       * @param mode for what kind of functionality the presented list of repositories should be filtered
       * @param selectRepoIfSingle  if {@code true} and there is only one repository
       *                        known, then the (only) repository is preselected;
       *                        otherwise no repository is selected in the combo
       * @return
       */
      public static RepositoryComboSupport setup(JComponent component, JComboBox comboBox, RepositoryFilter mode, boolean selectRepoIfSingle) {
          RepositoryComboSupport repositoryComboSupport
                  = new RepositoryComboSupport(comboBox, (Repository) null,
                                                         (FileObject) null,
                                                         selectRepoIfSingle);
          if (component != null) {
              repositoryComboSupport.setupDisplayabilityTrigger(component);
          }
          repositoryComboSupport.filter = mode;
          return repositoryComboSupport;
      }
  
      /**
       * Setups the given repository with RepositoryComboRenderer. As soon as the component holding the combo
       * is displayed a list of all known repositories is retrieved and either the default repository will be
       * selected or nothing if it's not between the known repositories
       *
       * @param component component with the combobox
       * @param comboBox repository combobox
       * @param mode for what kind of functionality the presented list of repositories should be filtered
       * @param defaultRepo the repository to be selected
       * @return
       */
      public static RepositoryComboSupport setup(JComponent component, JComboBox comboBox, RepositoryFilter mode, Repository defaultRepo) {
          if (defaultRepo == null) {
              throw new IllegalArgumentException("default repository must be specified"); //NOI18N
          }
          RepositoryComboSupport repositoryComboSupport
                  = new RepositoryComboSupport(comboBox, defaultRepo,
                                                         (FileObject) null,
                                                         false);
          if (component != null) {
              repositoryComboSupport.setupDisplayabilityTrigger(component);
          }
          repositoryComboSupport.filter = mode;
          return repositoryComboSupport;
      }
  
      /**
       * Setups the given repository with RepositoryComboRenderer. As soon as the component holding the combo
       * is displayed a list of all known repositories is retrieved and either the repository associated with
       * referenceFile will be selected or nothing if there is no such repository
       *
       * @param component component with the combobox
       * @param comboBox repository combobox
       * @param filter for what kind of functionality the presented list of repositories should be filtered
       * @param referenceFile file associated with a repository
       * @return
       */
      public static RepositoryComboSupport setup(JComponent component, JComboBox comboBox, RepositoryFilter filter, FileObject referenceFile) {
          if (referenceFile == null) {
              throw new IllegalArgumentException("reference file must be specified"); //NOI18N
          }
  
          RepositoryComboSupport repositoryComboSupport
                  = new RepositoryComboSupport(comboBox, (Repository) null,
                                                         referenceFile,
                                                         false);
          if (component != null) {
              repositoryComboSupport.setupDisplayabilityTrigger(component);
          }
          repositoryComboSupport.filter = filter;
          return repositoryComboSupport;
      }
      private final RequestProcessor rp;
  
      private RepositoryComboSupport(JComboBox comboBox, Repository defaultRepo,
                                                         FileObject refFile,
                                                         boolean preselectSingleRepo) {
          if (comboBox == null) {
              throw new IllegalArgumentException("combo-box must be specified"); //NOI18N
          }
  
          checkAtMostOneSpecified(defaultRepo, refFile);
  
          this.comboBox = comboBox;
          this.defaultRepo = defaultRepo;
          this.refFile = refFile;
          this.preselectSingleRepo = preselectSingleRepo;
  
          this.comboBox.setModel(comboBoxModel = new RepositoryComboModel());
          this.comboBox.setRenderer(new RepositoryComboRenderer());
          
          defaultRepoComputationPending = (defaultRepo == null);
  
          progress.set(Progress.INITIALIZED);
          rp = BugtrackingManager.getInstance().getRequestProcessor();
      }
  
      private void checkOldComboBoxModel(JComboBox comboBox) {
          ComboBoxModel oldModel = comboBox.getModel();
          if ((oldModel != null) && (oldModel.getSize() != 0)) {
              throw new IllegalStateException("The combo-box must be empty."); //NOI18N
          }
      }
  
      private void setComboBoxData(Object[] data) {
          comboBox.setPopupVisible(false);
          comboBoxModel.setData(data);
      }
  
      private void checkAtMostOneSpecified(Object... items) {
          boolean oneSpecifed = false;
          for (Object item : items) {
              if (item == null) {
                  continue;
              }
              if (oneSpecifed) {
                  throw new IllegalArgumentException("At most one item may be specified."); //NOI18N
              }
              oneSpecifed = true;
          }
      }
  
      void start() {
          assert EventQueue.isDispatchThread();
          LOG.finer("start()");                                           //NOI18N
  
          /* This is the right time to get information about selected nodes: */
          if ((defaultRepo == null) && (refFile == null)) {
              FileObject[] currFiles = BugtrackingUtil.getCurrentSelection();
              if (currFiles == null) {
                  currFiles = new FileObject[0];
              }
              this.selectedFiles = currFiles;
          }
  
          /*
           * As this method should be running in the event-dispatching thread,
           * this is also the right time for manipulation with GUI:
           */
          checkOldComboBoxModel(comboBox);
          this.comboBox.setModel(comboBoxModel = new RepositoryComboModel());        
          setComboBoxData(new Object[] {LOADING_REPOSITORIES});
  
          updateProgress(Progress.STARTED);
  
          rp.post(this);
      }
  
      private void shutdown() {
          assert EventQueue.isDispatchThread();
          LOG.finer("shutdown()");                                        //NOI18N
  
          shutdownDisplayabilityTrigger();
          comboBox.removeItemListener(this);
  
          shutdown = true;
      }
  
      /**
       * Activates the mechanism of the bugtracking repository combo-box.
       * If a non-null component is passed as an argument, a trigger is set up
       * such that loading of bugtracking repositories is started as soon as
       * the given component becomes displayable.
       *
       * @param  triggerComponent  component whose displayability should activate
       *                           the combo-box
       * @exception  java.lang.IllegalStateException
       *             if the given component is already displayable
       */
      private void setupDisplayabilityTrigger(final Component triggerComponent) {
          assert EventQueue.isDispatchThread();
          LOG.finer("setupDisplayabilityTrigger(Component)");             //NOI18N
  
          if (triggerComponent.isDisplayable()) {
              throw new IllegalStateException(
                      "The trigger component must not be initially displayable.");//NOI18N
          }
  
          displayabilityListener = new DisplayabilityListener(triggerComponent);
          triggerComponent.addHierarchyListener(displayabilityListener);
      }
  
      private void shutdownDisplayabilityTrigger() {
          assert EventQueue.isDispatchThread();
          LOG.finer("shutdownDisplayabilityTrigger()");                   //NOI18N
  
          if (displayabilityListener != null) {
              displayabilityListener.dispose();
              displayabilityListener = null;
          }
      }
  
      public void setLocalRepositoryHidden (boolean hidden) {
          this.hideLocalRepository = hidden;
      }
  
      private final class DisplayabilityListener implements HierarchyListener {
          private final Component triggerComponent;
          private DisplayabilityListener(Component triggerComponent) {
              this.triggerComponent = triggerComponent;
          }
          @Override
          public void hierarchyChanged(HierarchyEvent e) {
              if ((e.getChangeFlags() & DISPLAYABILITY_CHANGED) == 0) {
                  return;
              }
  
              assert e.getChanged() == triggerComponent;
  
              if (triggerComponent.isDisplayable()) {
                  LOG.finer("trigger component became displayable");      //NOI18N
                  RepositoryComboSupport.this.start();
              } else {
                  LOG.finer("trigger component became non-displayable");  //NOI18N
                  RepositoryComboSupport.this.shutdown();
              }
          }
          private void dispose() {
              triggerComponent.removeHierarchyListener(this);
          }
      }
  
      /**
       * This is called when an issue tracker is selected/deselected in the
       * Issue Tracker combo-box (at the top of the Issue Window).
       * This method causes that if item "Select Issue Tracker" is selected
       * in the combo-box and the user selects a real issue tracker (repository),
       * the item "Select Issue Tracker" is removed.
       * 
       * @param  e  {@code ItemEvent} that holds information about the
       *            (de)selection event
       */
      @Override
      public void itemStateChanged(ItemEvent e) {
          if ((e.getStateChange() == ItemEvent.DESELECTED)
                  && (e.getItem() == SELECT_REPOSITORY)
                  && (comboBox.getItemAt(0) == SELECT_REPOSITORY)) {
              if (comboBox.isPopupVisible()) {
  
                  /*
                   * We do not want to remove item "Select repository..."
                   * while the popup is visible. Only remove it once the user
                   * closes the popup with another item selected.
                   */
  
                  /*
                   * Do not respond to item selection changes while the popup
                   * is visible:
                   */
                  comboBox.removeItemListener(this);
  
                  comboBox.addPopupMenuListener(new PopupMenuListener() {
                      @Override
                      public void popupMenuCanceled(PopupMenuEvent e) {
                          comboBox.removePopupMenuListener(this);
  
                          /* Restore the item selection listener: */
                          comboBox.addItemListener(RepositoryComboSupport.this);
                      }
                      @Override
                      public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                          comboBox.removePopupMenuListener(this);
  
                          /* Restore the item selection listener: */
                          comboBox.addItemListener(RepositoryComboSupport.this);
                      }
                      @Override
                      public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                          comboBox.removePopupMenuListener(this);
                          if (comboBox.getSelectedItem() != SELECT_REPOSITORY) {
                              comboBoxModel.removeElementAt(0);
                          } else {
                              /* Restore the item selection listener: */
                              comboBox.addItemListener(RepositoryComboSupport.this);
                          }
                      }
                  });
              } else {
                  comboBoxModel.removeElementAt(0);
                  comboBox.removeItemListener(this);
              }
          }
      }
  
      @Override
      public void run() {
          if (rp.isRequestProcessorThread()) {
              loadRepositories();
  
              if (defaultRepoComputationPending) {
                  if (repositories.length == 0) {
                      defaultRepoComputationPending = false;
                  } else if ((repositories.length == 1) && preselectSingleRepo) {
                      defaultRepo = repositories[0];
                      defaultRepoComputationPending = false;
                  }
              }
  
              /* schedule display of list of repositories */
              updateProgress(Progress.WILL_SCHEDULE_DISPLAY_OF_REPOS);
              EventQueue.invokeLater(this);
              updateProgress(Progress.SCHEDULED_DISPLAY_OF_REPOS);
  
              if (defaultRepoComputationPending) {
                  try {
                      findDefaultRepository();
                  } finally {
                      defaultRepoComputationPending = false;
                  }
                  if (defaultRepo != null) {
                      /* schedule selection of default repository (if any) */
                      updateProgress(Progress.WILL_SCHEDULE_SELECTION_OF_DEFAULT_REPO);
                      EventQueue.invokeLater(this);
                      updateProgress(Progress.SCHEDULED_SELECTION_OF_DEFAULT_REPO);
                  }
              }
  
              notifyProgressDone();
          } else {
              doGuiJob();
          }
      }
  
      private void doGuiJob() {
          assert EventQueue.isDispatchThread();
  
          if (repositoriesDisplayed && defaultRepoSelected) {
              /*
               * The default repository selection was performed during the
               * previous invocation of this method (in one shot with displaying
               * the list of available repositories).
               */
              LOG.finest("run() called from AWT - nothing to do - all work already done"); //NOI18N
              return;
          }
  
          if (shutdown) {
              LOG.finest(" - too late - the component has been already closed"); //NOI18N
              return;
          }
  
          shutdownDisplayabilityTrigger();
  
          if (!repositoriesDisplayed) {
              displayRepositories();
          } else {
              selectDefaultRepository();
          }
      }
  
      private void displayRepositories() {
          updateProgress(Progress.WILL_DISPLAY_REPOS);
  
          boolean computationPending = defaultRepoComputationPending;
          Repository knownDefaultRepo = computationPending ? null : defaultRepo;
  
          LOG.finest("going to display the list of repositories");        //NOI18N
          if ((knownDefaultRepo != null) && (LOG.isLoggable(FINEST))) {
              LOG.log(FINEST, "  - default repository: {0}", knownDefaultRepo.getDisplayName());
          }
          try {
              setRepositories(repositories, knownDefaultRepo);
          } finally {
              repositoriesDisplayed = true;
              defaultRepoSelected = !computationPending;
              updateProgress(Progress.DISPLAYED_REPOS);
          }
      }
  
      private void selectDefaultRepository() {
          assert (!defaultRepoComputationPending) && (defaultRepo != null);
          /*
           * We are going to preselect the default repository.
           */
          LOG.finest("going to select the default repository");           //NOI18N
          updateProgress(Progress.WILL_SELECT_DEFAULT_REPO);
          try {
              if ((comboBox.getSelectedItem() instanceof Repository)
                      && !comboBox.isPopupVisible()) {
                  /*
                   * the user has already selected some item - do not override it
                   */
              } else {
                  preselectRepository(defaultRepo);
              }
          } finally {
              defaultRepoSelected = true;
              updateProgress(Progress.SELECTED_DEFAULT_REPO);
          }
      }
  
      /**
       * Selects the given repository in the combo-box if no repository has been
       * selected yet by the user.
       * If the user had already selected some repository before this method
       * was called, this method does nothing. If this method is called at
       * the moment the popup of the combo-box is opened, the operation of
       * pre-selecting the repository is deferred until the popup is closed. If
       * the popup had been displayed at the moment this method was called
       * and the user selects some repository during the period since the
       * call of this method until the deferred selection takes place, the
       * deferred selection operation is cancelled.
       *
       * @param  repoToPreselect  repository to preselect
       */
      private void preselectRepository(final Repository repoToPreselect) {
          assert EventQueue.isDispatchThread();
  
          if (LOG.isLoggable(Level.FINER)) {
              LOG.log(Level.FINER, "preselectRepository({0})", repoToPreselect.getDisplayName()); //NOI18N
          }
  
          if (comboBox.isPopupVisible()) {
              LOG.finest(" - the popup is visible - deferred");           //NOI18N
              comboBox.addPopupMenuListener(new PopupMenuListener() {
                  @Override
                  public void popupMenuWillBecomeVisible(PopupMenuEvent e) { }
                  @Override
                  public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                      LOG.finer("popupMenuWillBecomeInvisible()");        //NOI18N
                      comboBox.removePopupMenuListener(this);
                  }
                  @Override
                  public void popupMenuCanceled(PopupMenuEvent e) {
                      LOG.finer("popupMenuCanceled()");                   //NOI18N
                      comboBox.removePopupMenuListener(this);
                      LOG.finest(" - processing deferred selection");     //NOI18N
                      preselectItemUnconditionally(repoToPreselect);
                  }
              });
          } else {
              preselectItemUnconditionally(repoToPreselect);
          }
      }
  
      private void preselectItemUnconditionally(Object item) {
          if (LOG.isLoggable(Level.FINER)) {
              LOG.log(Level.FINER, "preselectItemUnconditionally({0})", getItemName(item)); //NOI18N
          }
  
          comboBox.setSelectedItem(item);
      }
  
      private static String getItemName(Object item) {
          if (item instanceof Repository) {
              return ((Repository) item).getDisplayName();
          } else{
              return item.toString();
          }
      }
  
      private void setRepositories(Repository[] repos,
                                   Repository knownDefaultRepository) {
          assert EventQueue.isDispatchThread();
  
          int reposCount = (repos != null) ? repos.length : 0;
          Object[] comboData;
          if(repos != null) {
              Arrays.sort(repos, new RepositoryComparator());
          }
          int startIndex = 0;
          if (reposCount == 0) {
              comboData = new Object[] {NO_REPOSITORIES};
          } else if (knownDefaultRepository == null) {
              comboData = new Object[reposCount + 1];
              comboData[startIndex++] = SELECT_REPOSITORY;
          } else {
              comboData = new Object[reposCount];
          }
          if (reposCount != 0) {
              System.arraycopy(repos, 0, comboData, startIndex, reposCount);
          }
  
          setComboBoxData(comboData);
          if (knownDefaultRepository != null) {
              comboBox.setSelectedItem(knownDefaultRepository);
          } else if (reposCount != 0) {
              assert (comboBox.getSelectedItem() == SELECT_REPOSITORY);
              comboBox.addItemListener(this);
          }
      }
  
      private void refreshComboBoxData(Repository[] repos) {
          setComboBoxData((repos.length == 0) ? new Object[] {NO_REPOSITORIES}
                                              : repos);
      }
  
      public void refreshRepositoryModel() {
          LOG.finer("refreshRepositoryModel()");                          //NOI18N
          rp.post(new Runnable() {
              @Override
              public void run() {
                  if (rp.isRequestProcessorThread()) {
                      loadRepositories();
                      EventQueue.invokeLater(this);
                  } else {
                      assert EventQueue.isDispatchThread();
                      refreshComboBoxData(repositories);
                  }
              }
          });
      }
  
      private void loadRepositories() {
          assert rp.isRequestProcessorThread();
          LOG.finer("loadRepositories()");                                //NOI18N
          updateProgress(Progress.WILL_LOAD_REPOS);
  
          long startTimeMillis = System.currentTimeMillis();
  
          if(refFile != null) {
              pingNBRepository(refFile);
          }
          Collection<RepositoryImpl> repoImpls = RepositoryRegistry.getInstance().getKnownRepositories(true);
          List<Repository> repos = new ArrayList(repoImpls.size());
          for (RepositoryImpl impl : repoImpls) {
              boolean hidden = hideLocalRepository && BugtrackingManager.isLocalConnectorID(impl.getConnectorId());
              if ( !hidden && 
                   ( filter == RepositoryFilter.ALL || 
                   ( filter == RepositoryFilter.ATTACH_FILE && impl.canAttachFiles()))) 
              {
                  repos.add(impl.getRepository());
              }
          }
          repositories = repos.toArray(new Repository[repos.size()]);
  
          long endTimeMillis = System.currentTimeMillis();
          if (LOG.isLoggable(FINEST)) {
              LOG.log(FINEST, "BugtrackingUtil.getKnownRepositories() took {0} ms.", (endTimeMillis - startTimeMillis));   //NOI18N
          }
          updateProgress(Progress.LOADED_REPOS);
      }
  
      private void findDefaultRepository() {
          assert rp.isRequestProcessorThread();
          LOG.finer("findDefaultRepository()");                           //NOI18N
          updateProgress(Progress.WILL_DETERMINE_DEFAULT_REPO);
  
          long startTimeMillis, endTimeMillis;
          RepositoryImpl result;
  
          startTimeMillis = System.currentTimeMillis();
  
          assert (refFile == null) ^ (selectedFiles == null);
  
          if (refFile != null) {
              result = BugtrackingOwnerSupport.getInstance().getRepository(refFile,
                                                                           false);
          } else {
              assert (selectedFiles != null);
              result = BugtrackingOwnerSupport.getInstance().getRepository(selectedFiles);
          }
  
          endTimeMillis = System.currentTimeMillis();
  
          if (LOG.isLoggable(FINEST)) {
              LOG.log(FINEST, "BugtrackingOwnerSupport.getRepository(...) took {0} ms.", (endTimeMillis - startTimeMillis));   //NOI18N
          }
  
          if (result != null) {
              if (LOG.isLoggable(FINEST)) {
                  LOG.log(FINEST, " - default repository: {0}", result.getDisplayName()); //NOI18N
              }
              defaultRepo = result.getRepository();
          } else {
              LOG.finest(" - default repository: <null>");                //NOI18N
          }
  
          updateProgress(Progress.DETERMINED_DEFAULT_REPO);
      }
  
      private void pingNBRepository(FileObject referenceFile) {
          String url = VersioningQuery.getRemoteLocation(referenceFile.toURI());
          if (url != null) {
              if(NBBugzillaUtils.isNbRepository(url)) {
                  BugtrackingUtil.findNBRepository(); // ensure repository exists 
              }
          }
      }
          
      //--------------- infrastructure for unit tests -----------------
  
      enum Progress {
          INITIALIZED,
          STARTED,
          WILL_LOAD_REPOS,
          LOADED_REPOS,
          WILL_SCHEDULE_DISPLAY_OF_REPOS,
          SCHEDULED_DISPLAY_OF_REPOS,
          WILL_DISPLAY_REPOS,
          DISPLAYED_REPOS,
          WILL_DETERMINE_DEFAULT_REPO,
          DETERMINED_DEFAULT_REPO,
          WILL_SCHEDULE_SELECTION_OF_DEFAULT_REPO,
          SCHEDULED_SELECTION_OF_DEFAULT_REPO,
          WILL_SELECT_DEFAULT_REPO,
          SELECTED_DEFAULT_REPO,
          WILL_DISPLAY_REPOS_AND_SELECT_DEFAULT,
          DISPLAYED_REPOS_AND_SELECTED_DEFAULT,
          THREAD_PROGRESS_DONE;
      }
  
      private final ThreadLocal<Progress> progress = new ThreadLocal<Progress>();
      private volatile ChangeListener progressListener;
  
      private void notifyProgressDone() {
          if (progressListener != null) {
              assert !EventQueue.isDispatchThread();
  
              Runnable doneNotifier = new Runnable() {
                  @Override
                  public void run() {
                      updateProgress(Progress.THREAD_PROGRESS_DONE);
                  }
              };
              doneNotifier.run();
              EventQueue.invokeLater(doneNotifier);
          }
      }
  
      private void updateProgress(Progress progress) {
          this.progress.set(progress);
          fireProgressChange();
      }
  
      private void fireProgressChange() {
          if (progressListener != null) {
              progressListener.stateChanged(new ChangeEvent(this));
          }
      }
  
      void setProgressListener(ChangeListener l) {
          progressListener = l;
      }
  
      Progress getProgress() {
          return progress.get();
      }
  
  }
  