gt:         if(rowIndex < mTuners.size())
context: /*
   *
   *  * ******************************************************************************
   *  * Copyright (C) 2014-2020 Dennis Sheirer
   *  *
   *  * This program is free software: you can redistribute it and/or modify
   *  * it under the terms of the GNU General Public License as published by
   *  * the Free Software Foundation, either version 3 of the License, or
   *  * (at your option) any later version.
   *  *
   *  * This program is distributed in the hope that it will be useful,
   *  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  * GNU General Public License for more details.
   *  *
   *  * You should have received a copy of the GNU General Public License
   *  * along with this program.  If not, see <http://www.gnu.org/licenses/>
   *  * *****************************************************************************
   *
   *
   */
  package io.github.dsheirer.source.tuner;
  
  import io.github.dsheirer.properties.SystemProperties;
  import io.github.dsheirer.sample.Listener;
  import io.github.dsheirer.source.Source;
  import io.github.dsheirer.source.SourceException;
  import io.github.dsheirer.source.tuner.TunerEvent.Event;
  import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
  import io.github.dsheirer.source.tuner.channel.TunerChannel;
  import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
  import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
  import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
  import io.github.dsheirer.source.tuner.recording.RecordingTuner;
  import io.github.dsheirer.spectrum.SpectralDisplayPanel;
  import io.github.dsheirer.util.ThreadPool;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import javax.swing.table.AbstractTableModel;
  import java.text.DecimalFormat;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  
  public class TunerModel extends AbstractTableModel implements Listener<TunerEvent>
  {
      private static final long serialVersionUID = 1L;
  
      private final static Logger mLog = LoggerFactory.getLogger(TunerModel.class);
  
      public static final int TUNER_TYPE = 0;
      public static final int TUNER_ID = 1;
      public static final int SAMPLE_RATE = 2;
      public static final int FREQUENCY = 3;
      public static final int CHANNEL_COUNT = 4;
      public static final int FREQUENCY_ERROR = 5;
      public static final int MEASURED_FREQUENCY_ERROR = 6;
      public static final int SPECTRAL_DISPLAY_NEW = 7;
      private static final int MAXIMUM_RECORDING_TUNERS_ALLOWED = 1;
      private static final String MHZ = " MHz";
      private static final String[] COLUMNS = {"Tuner", "ID", "Sample Rate", "Frequency", "Channels", "Current PPM",
          "Measured Error", "Display"};
  
      private List<Tuner> mTuners = new ArrayList<>();
      private List<Listener<TunerEvent>> mTunerEventListeners = new ArrayList<>();
      private DecimalFormat mFrequencyFormat = new DecimalFormat("0.00000");
      private DecimalFormat mSampleRateFormat = new DecimalFormat("0.000");
      private DecimalFormat mFrequencyErrorPPMFormat = new DecimalFormat("0.0");
  
      private TunerConfigurationModel mTunerConfigurationModel;
  
      public TunerModel(TunerConfigurationModel tunerConfigurationModel)
      {
          mTunerConfigurationModel = tunerConfigurationModel;
      }
  
      public TunerConfigurationModel getTunerConfigurationModel()
      {
          return mTunerConfigurationModel;
      }
  
      /**
       * List of Tuners currently in the model
       */
      public List<Tuner> getTuners()
      {
          return mTuners;
      }
  
      public Tuner getTuner(int index)
      {
          if(index < mTuners.size())
          {
              return mTuners.get(index);
          }
  
          return null;
      }
  
      /**
       * Find a tuner that matches the name argument
       *
       * @param name of the tuner
       * @return named tuner or null
       */
      public Tuner getTuner(String name)
      {
          if(name != null)
          {
              for(Tuner tuner : mTuners)
              {
                  if(!tuner.hasError() && tuner.getName().equalsIgnoreCase(name))
                  {
                      return tuner;
                  }
              }
          }
  
          return null;
      }
  
      public void addTuners(List<Tuner> Tuners)
      {
          for(Tuner tuner : Tuners)
          {
              addTuner(tuner);
          }
      }
  
      /**
       * Adds the Tuner to this model
       */
      public void addTuner(Tuner tuner)
      {
          if(!mTuners.contains(tuner))
          {
              //Get the tuner configuration and apply it to the tuner - this
              //call should always produce a tuner configuration
              TunerConfiguration config = mTunerConfigurationModel
                  .getTunerConfiguration(tuner.getTunerType(), tuner.getUniqueID());
  
              try
              {
                  tuner.getTunerController().apply(config);
  
                  mTuners.add(tuner);
  
                  int index = mTuners.indexOf(tuner);
  
                  fireTableRowsInserted(index, index);
  
                  tuner.addTunerChangeListener(this);
              }
              catch(SourceException se)
              {
                  mLog.error("Couldn't apply tuner configuration to tuner - ["
                      + tuner.getTunerType().name() + "] with id ["
                      + tuner.getUniqueID() + "] - tuner will not be included");
              }
  
          }
      }
  
      /**
       * Removes the Tuner from this model
       */
      public void removeTuner(Tuner tuner)
      {
          if(mTuners.contains(tuner))
          {
              tuner.removeTunerChangeListener(this);
  
              int index = mTuners.indexOf(tuner);
  
              mTuners.remove(tuner);
  
              fireTableRowsDeleted(index, index);
          }
      }
  
      /**
       * Indicates if a recording tuner can be added to the set of tuners.  This is constrained by the maximum
       * allowed limit.
       * @return true if a recording tuner can be added.
       */
      public boolean canAddRecordingTuner()
      {
          int count = 0;
  
          for(Tuner tuner: mTuners)
          {
              if(tuner instanceof RecordingTuner)
              {
                  count++;
              }
          }
  
          return count < MAXIMUM_RECORDING_TUNERS_ALLOWED;
      }
  
      public void addListener(Listener<TunerEvent> listener)
      {
          mTunerEventListeners.add(listener);
      }
  
      public void removeListener(Listener<TunerEvent> listener)
      {
          mTunerEventListeners.remove(listener);
      }
  
      public void broadcast(TunerEvent event)
      {
          for(Listener<TunerEvent> listener : mTunerEventListeners)
          {
              listener.receive(event);
          }
      }
  
      /**
       * Requests to display the first tuner in this model.  Invoke this method
       * after all listeners have registered and tuners have been added to this
       * model, in order to inform the primary display to use the first tuner.
       */
      public void requestFirstTunerDisplay()
      {
          SystemProperties properties = SystemProperties.getInstance();
          boolean enabled = properties.get(SpectralDisplayPanel.SPECTRAL_DISPLAY_ENABLED, true);
  
          if(enabled && mTuners.size() > 0)
          {
              //Hack: the airspy tuner would lockup aperiodically and refuse to produce
              //transfer buffers ... delaying registering for buffers for 500 ms seems
              //to allow the airspy to stabilize before we start asking for samples.
              ThreadPool.SCHEDULED.schedule(new Runnable()
              {
                  @Override
                  public void run()
                  {
                      broadcast(new TunerEvent(mTuners.get(0), Event.REQUEST_MAIN_SPECTRAL_DISPLAY));
                  }
              }, 500, TimeUnit.MILLISECONDS);
          }
          else
          {
              broadcast(new TunerEvent(null, Event.CLEAR_MAIN_SPECTRAL_DISPLAY));
          }
      }
  
      @Override
      public void receive(TunerEvent event)
      {
          if(event.getTuner() != null)
          {
              int index = mTuners.indexOf(event.getTuner());
  
              if(index >= 0)
              {
                  switch(event.getEvent())
                  {
                      case CHANNEL_COUNT:
                          fireTableCellUpdated(index, CHANNEL_COUNT);
                          break;
                      case FREQUENCY_UPDATED:
                          fireTableCellUpdated(index, FREQUENCY);
                          mTunerConfigurationModel.tunerFrequencyChanged(event.getTuner());
                          break;
                      case FREQUENCY_ERROR_UPDATED:
                          fireTableCellUpdated(index, FREQUENCY_ERROR);
                          break;
                      case MEASURED_FREQUENCY_ERROR_UPDATED:
                          fireTableCellUpdated(index, MEASURED_FREQUENCY_ERROR);
                          break;
                      case SAMPLE_RATE_UPDATED:
                          fireTableCellUpdated(index, SAMPLE_RATE);
                          break;
                      case REQUEST_MAIN_SPECTRAL_DISPLAY:
                          fireTableCellUpdated(index, MEASURED_FREQUENCY_ERROR);
                          break;
                      case ERROR_STATE:
                          fireTableCellUpdated(index, CHANNEL_COUNT);
                          fireTableCellUpdated(index, FREQUENCY);
                          fireTableCellUpdated(index, FREQUENCY_ERROR);
                          fireTableCellUpdated(index, MEASURED_FREQUENCY_ERROR);
                          fireTableCellUpdated(index, SAMPLE_RATE);
                          break;
                      default:
                          break;
                  }
              }
          }
  
          broadcast(event);
      }
  
      @Override
      public int getRowCount()
      {
          return mTuners.size();
      }
  
      @Override
      public int getColumnCount()
      {
          return COLUMNS.length;
      }
  
      @Override
      public Object getValueAt(int rowIndex, int columnIndex)
      {
  