<commit_before>package org.multibit.hd.ui.controllers;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.*;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Wallet;
import org.joda.time.DateTime;
import org.multibit.commons.concurrent.SafeExecutors;
import org.multibit.commons.utils.Dates;
import org.multibit.hd.core.atom.AscendingAtomEntryComparator;
import org.multibit.hd.core.atom.AtomEntry;
import org.multibit.hd.core.atom.AtomFeed;
import org.multibit.hd.core.atom.AtomFeeds;
import org.multibit.hd.core.config.BitcoinConfiguration;
import org.multibit.hd.core.config.Configurations;
import org.multibit.hd.core.dto.*;
import org.multibit.hd.core.error_reporting.ExceptionHandler;
import org.multibit.hd.core.events.*;
import org.multibit.hd.core.exceptions.PaymentsSaveException;
import org.multibit.hd.core.exchanges.ExchangeKey;
import org.multibit.hd.core.managers.BackupManager;
import org.multibit.hd.core.managers.InstallationManager;
import org.multibit.hd.core.managers.WalletManager;
import org.multibit.hd.core.services.*;
import org.multibit.hd.core.store.TransactionInfo;
import org.multibit.hd.hardware.core.HardwareWalletService;
import org.multibit.hd.hardware.core.events.HardwareWalletEvent;
import org.multibit.hd.hardware.core.events.HardwareWalletEventType;
import org.multibit.hd.hardware.core.events.HardwareWalletEvents;
import org.multibit.hd.hardware.core.messages.Features;
import org.multibit.hd.hardware.core.messages.HardwareWalletMessage;
import org.multibit.hd.ui.audio.Sounds;
import org.multibit.hd.ui.events.controller.ControllerEvents;
import org.multibit.hd.ui.events.view.ComponentChangedEvent;
import org.multibit.hd.ui.events.view.SwitchWalletEvent;
import org.multibit.hd.ui.events.view.ViewEvents;
import org.multibit.hd.ui.events.view.WizardHideEvent;
import org.multibit.hd.ui.languages.Languages;
import org.multibit.hd.ui.languages.MessageKey;
import org.multibit.hd.ui.models.AlertModel;
import org.multibit.hd.ui.models.Models;
import org.multibit.hd.ui.platform.listener.*;
import org.multibit.hd.ui.services.ExternalDataListeningService;
import org.multibit.hd.ui.utils.HtmlUtils;
import org.multibit.hd.ui.utils.SafeDesktop;
import org.multibit.hd.ui.views.MainView;
import org.multibit.hd.ui.views.ViewKey;
import org.multibit.hd.ui.views.components.Buttons;
import org.multibit.hd.ui.views.components.Panels;
import org.multibit.hd.ui.views.fonts.AwesomeIcon;
import org.multibit.hd.ui.views.screens.Screen;
import org.multibit.hd.ui.views.themes.Theme;
import org.multibit.hd.ui.views.themes.ThemeKey;
import org.multibit.hd.ui.views.themes.Themes;
import org.multibit.hd.ui.views.wizards.Wizards;
import org.multibit.hd.ui.views.wizards.credentials.CredentialsRequestType;
import org.multibit.hd.ui.views.wizards.credentials.CredentialsState;
import org.multibit.hd.ui.views.wizards.credentials.CredentialsWizard;
import org.multibit.hd.ui.views.wizards.edit_wallet.EditWalletState;
import org.multibit.hd.ui.views.wizards.edit_wallet.EditWalletWizardModel;
import org.multibit.hd.ui.views.wizards.exit.ExitState;
import org.multibit.hd.ui.views.wizards.use_hardware_wallet.UseHardwareWalletState;
import org.multibit.hd.ui.views.wizards.welcome.WelcomeWizard;
import org.multibit.hd.ui.views.wizards.welcome.WelcomeWizardState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.net.URI;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * <p>Controller for the main view </p>
 * <ul>
 * <li>Handles interaction between the model and the view</li>
 * </ul>
 * <p>To allow complete separation between Model, View and Controller all interactions are handled using application events</p>
 */
public class MainController extends AbstractController implements
  GenericOpenURIEventListener,
  GenericOpenFilesEventListener,
  GenericPreferencesEventListener,
  GenericAboutEventListener,
  GenericQuitEventListener {

  private static final Logger log = LoggerFactory.getLogger(MainController.class);

  private Optional<ExchangeTickerService> exchangeTickerService = Optional.absent();

  private final ListeningExecutorService handoverExecutorService = SafeExecutors.newSingleThreadExecutor("wizard-handover");

  // Keep a thread pool for transaction status checking
  private static final ListeningExecutorService transactionCheckingExecutorService = SafeExecutors.newFixedThreadPool(10, "transaction-checking");

  // Provide a separate executor service for wallet operations
  private static final ListeningExecutorService walletExecutorService = SafeExecutors.newFixedThreadPool(10, "wallet-services");

  private static final int NUMBER_OF_SECONDS_TO_WAIT_BEFORE_TRANSACTION_CHECKING = 60;

  // Keep track of other controllers for use after a preferences change
  private final HeaderController headerController;

  // Main view may be replaced during a soft shutdown
  private MainView mainView;

  // Start with the assumption that it is fine to avoid annoying "everything is OK" alert
  private RAGStatus lastExchangeSeverity = RAGStatus.GREEN;

  // Assume a password rather than a hardware wallet cipher key
  private CredentialsRequestType deferredCredentialsRequestType = CredentialsRequestType.PASSWORD;

  /**
   * The delay between a wipe and insertion of a new device
   */
  private static final int HARDWARE_WALLET_WIPE_TIME_THRESHOLD = 4;

  /**
   * The last time a Trezor device was wiped (or yesterday as the default)
   */
  private DateTime lastWipedHardwareWalletDateTime = Dates.nowUtc().minusDays(1);

  /**
   * Whether alerts should be fired when new transactions appear (true = fire alerts, false = suppress alerts)
   */
  private static boolean fireTransactionAlerts = true;

  /**
   * @param headerController The header controller
   */
  public MainController(HeaderController headerController) {

    super();

    // MainController must also subscribe to ViewEvents
    ViewEvents.subscribe(this);

    Preconditions.checkNotNull(headerController, "'headerController' must be present");

    this.headerController = headerController;

  }

  @Override
  public void unsubscribe() {
    super.unsubscribe();
    ViewEvents.unsubscribe(this);
  }

  @Subscribe
  public void onShutdownEvent(ShutdownEvent shutdownEvent) {

    log.info("Received shutdown: {}", shutdownEvent.getShutdownType());

    switch (shutdownEvent.getShutdownType()) {
      case HARD:
      case SOFT:

        // Unsubscribe views for events
        mainView.unsubscribe();

        // Unregister controllers for events
        headerController.unsubscribe();

        // Unregister this
        unsubscribe();

        shutdownCurrentWallet(shutdownEvent.getShutdownType());

        break;
      case SWITCH:
        // Do nothing - the wizard hide event triggers this process

    }

  }

  @Subscribe
  public void onWizardHideEvent(WizardHideEvent event) {

    Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "This event should be running on the EDT.");

    log.debug("Wizard hide: '{}' Exit/Cancel: {}", event.getPanelName(), event.isExitCancel());

    if (!event.isExitCancel()) {

      // Successful wizard interaction

      if (WelcomeWizardState.CREATE_WALLET_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.HARDWARE_CREATE_WALLET_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.RESTORE_WALLET_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.RESTORE_PASSWORD_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.WELCOME_SELECT_WALLET.name().equals(event.getPanelName())
        ) {<commit_msg>#789 Fix shut down NPE preventing stop<commit_after>package org.multibit.hd.ui.controllers;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.*;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Wallet;
import org.joda.time.DateTime;
import org.multibit.commons.concurrent.SafeExecutors;
import org.multibit.commons.utils.Dates;
import org.multibit.hd.core.atom.AscendingAtomEntryComparator;
import org.multibit.hd.core.atom.AtomEntry;
import org.multibit.hd.core.atom.AtomFeed;
import org.multibit.hd.core.atom.AtomFeeds;
import org.multibit.hd.core.config.BitcoinConfiguration;
import org.multibit.hd.core.config.Configurations;
import org.multibit.hd.core.dto.*;
import org.multibit.hd.core.error_reporting.ExceptionHandler;
import org.multibit.hd.core.events.*;
import org.multibit.hd.core.exceptions.PaymentsSaveException;
import org.multibit.hd.core.exchanges.ExchangeKey;
import org.multibit.hd.core.managers.BackupManager;
import org.multibit.hd.core.managers.InstallationManager;
import org.multibit.hd.core.managers.WalletManager;
import org.multibit.hd.core.services.*;
import org.multibit.hd.core.store.TransactionInfo;
import org.multibit.hd.hardware.core.HardwareWalletService;
import org.multibit.hd.hardware.core.events.HardwareWalletEvent;
import org.multibit.hd.hardware.core.events.HardwareWalletEventType;
import org.multibit.hd.hardware.core.events.HardwareWalletEvents;
import org.multibit.hd.hardware.core.messages.Features;
import org.multibit.hd.hardware.core.messages.HardwareWalletMessage;
import org.multibit.hd.ui.audio.Sounds;
import org.multibit.hd.ui.events.controller.ControllerEvents;
import org.multibit.hd.ui.events.view.ComponentChangedEvent;
import org.multibit.hd.ui.events.view.SwitchWalletEvent;
import org.multibit.hd.ui.events.view.ViewEvents;
import org.multibit.hd.ui.events.view.WizardHideEvent;
import org.multibit.hd.ui.languages.Languages;
import org.multibit.hd.ui.languages.MessageKey;
import org.multibit.hd.ui.models.AlertModel;
import org.multibit.hd.ui.models.Models;
import org.multibit.hd.ui.platform.listener.*;
import org.multibit.hd.ui.services.ExternalDataListeningService;
import org.multibit.hd.ui.utils.HtmlUtils;
import org.multibit.hd.ui.utils.SafeDesktop;
import org.multibit.hd.ui.views.MainView;
import org.multibit.hd.ui.views.ViewKey;
import org.multibit.hd.ui.views.components.Buttons;
import org.multibit.hd.ui.views.components.Panels;
import org.multibit.hd.ui.views.fonts.AwesomeIcon;
import org.multibit.hd.ui.views.screens.Screen;
import org.multibit.hd.ui.views.themes.Theme;
import org.multibit.hd.ui.views.themes.ThemeKey;
import org.multibit.hd.ui.views.themes.Themes;
import org.multibit.hd.ui.views.wizards.Wizards;
import org.multibit.hd.ui.views.wizards.credentials.CredentialsRequestType;
import org.multibit.hd.ui.views.wizards.credentials.CredentialsState;
import org.multibit.hd.ui.views.wizards.credentials.CredentialsWizard;
import org.multibit.hd.ui.views.wizards.edit_wallet.EditWalletState;
import org.multibit.hd.ui.views.wizards.edit_wallet.EditWalletWizardModel;
import org.multibit.hd.ui.views.wizards.exit.ExitState;
import org.multibit.hd.ui.views.wizards.use_hardware_wallet.UseHardwareWalletState;
import org.multibit.hd.ui.views.wizards.welcome.WelcomeWizard;
import org.multibit.hd.ui.views.wizards.welcome.WelcomeWizardState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.net.URI;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * <p>Controller for the main view </p>
 * <ul>
 * <li>Handles interaction between the model and the view</li>
 * </ul>
 * <p>To allow complete separation between Model, View and Controller all interactions are handled using application events</p>
 */
public class MainController extends AbstractController implements
  GenericOpenURIEventListener,
  GenericOpenFilesEventListener,
  GenericPreferencesEventListener,
  GenericAboutEventListener,
  GenericQuitEventListener {

  private static final Logger log = LoggerFactory.getLogger(MainController.class);

  private Optional<ExchangeTickerService> exchangeTickerService = Optional.absent();

  private final ListeningExecutorService handoverExecutorService = SafeExecutors.newSingleThreadExecutor("wizard-handover");

  // Keep a thread pool for transaction status checking
  private static final ListeningExecutorService transactionCheckingExecutorService = SafeExecutors.newFixedThreadPool(10, "transaction-checking");

  // Provide a separate executor service for wallet operations
  private static final ListeningExecutorService walletExecutorService = SafeExecutors.newFixedThreadPool(10, "wallet-services");

  private static final int NUMBER_OF_SECONDS_TO_WAIT_BEFORE_TRANSACTION_CHECKING = 60;

  // Keep track of other controllers for use after a preferences change
  private final HeaderController headerController;

  // Main view may be replaced during a soft shutdown
  private MainView mainView;

  // Start with the assumption that it is fine to avoid annoying "everything is OK" alert
  private RAGStatus lastExchangeSeverity = RAGStatus.GREEN;

  // Assume a password rather than a hardware wallet cipher key
  private CredentialsRequestType deferredCredentialsRequestType = CredentialsRequestType.PASSWORD;

  /**
   * The delay between a wipe and insertion of a new device
   */
  private static final int HARDWARE_WALLET_WIPE_TIME_THRESHOLD = 4;

  /**
   * The last time a Trezor device was wiped (or yesterday as the default)
   */
  private DateTime lastWipedHardwareWalletDateTime = Dates.nowUtc().minusDays(1);

  /**
   * Whether alerts should be fired when new transactions appear (true = fire alerts, false = suppress alerts)
   */
  private static boolean fireTransactionAlerts = true;

  /**
   * @param headerController The header controller
   */
  public MainController(HeaderController headerController) {

    super();

    // MainController must also subscribe to ViewEvents
    ViewEvents.subscribe(this);

    Preconditions.checkNotNull(headerController, "'headerController' must be present");

    this.headerController = headerController;

  }

  @Override
  public void unsubscribe() {
    super.unsubscribe();
    ViewEvents.unsubscribe(this);
  }

  @Subscribe
  public void onShutdownEvent(ShutdownEvent shutdownEvent) {

    log.info("Received shutdown: {}", shutdownEvent.getShutdownType());

    switch (shutdownEvent.getShutdownType()) {
      case HARD:
      case SOFT:

        // Unsubscribe views for events
        if (mainView != null) {
          mainView.unsubscribe();
        }

        // Unregister controllers for events
        if (headerController != null) {
          headerController.unsubscribe();
        }

        // Unregister this
        unsubscribe();

        shutdownCurrentWallet(shutdownEvent.getShutdownType());

        break;
      case SWITCH:
        // Do nothing - the wizard hide event triggers this process

    }

  }

  @Subscribe
  public void onWizardHideEvent(WizardHideEvent event) {

    Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "This event should be running on the EDT.");

    log.debug("Wizard hide: '{}' Exit/Cancel: {}", event.getPanelName(), event.isExitCancel());

    if (!event.isExitCancel()) {

      // Successful wizard interaction

      if (WelcomeWizardState.CREATE_WALLET_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.HARDWARE_CREATE_WALLET_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.RESTORE_WALLET_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.RESTORE_PASSWORD_REPORT.name().equals(event.getPanelName())
        || WelcomeWizardState.WELCOME_SELECT_WALLET.name().equals(event.getPanelName())
        ) {
{'fraction_non_alphanumeric': 0.10183286735010873, 'fraction_numerical': 0.0014290152221186703, 'mean_word_length': 4.893811790552911, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3958907', 'n_tokens_mistral': 5017, 'n_tokens_neox': 4875, 'n_words': 938}