@SuppressWarnings("serial")
public class DialogNotificationImpl extends JDialog implements INotification {

  private final JPanel contentPanel = new JPanel();
  private JLabel messageLabel;
  private JProgressBar progressBar;

  private boolean interrupted = false;
  private Exception exception;

  public DialogNotificationImpl(Component parent) {
    init();
    pack();
    setLocationRelativeTo(parent);
  }

  @Override
  public void addAbortListener() {
  }

  @Override
  public final Exception getException() {
    return exception;
  }

  private void init() {
    setPreferredSize(new Dimension(400, 100));
    setFocusTraversalKeysEnabled(false);
    setFocusCycleRoot(false);
    setFocusableWindowState(false);
    setFocusable(false);
    setName("notificationDialog");
    setSize(new Dimension(400, 100));
    setUndecorated(true);
    setMinimumSize(new Dimension(400, 100));
    setAlwaysOnTop(true);
    setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    setModalityType(ModalityType.DOCUMENT_MODAL);
    setBounds(100, 100, 450, 300);
    getContentPane().setLayout(new BorderLayout());
    contentPanel.setBackground(Color.WHITE);
    contentPanel.setBorder(new CompoundBorder(new LineBorder(new Color(0, 0, 0)), new EmptyBorder(
        5, 5, 5, 5)));
    getContentPane().add(contentPanel, BorderLayout.CENTER);
    GridBagLayout gbl_contentPanel = new GridBagLayout();
    gbl_contentPanel.columnWidths = new int[] { 0, 0 };
    gbl_contentPanel.rowHeights = new int[] { 0, 0, 0, 0, 0 };
    gbl_contentPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
    gbl_contentPanel.rowWeights = new double[] { 1.0, 0.0, 0.0, 1.0, Double.MIN_VALUE };
    contentPanel.setLayout(gbl_contentPanel);

    messageLabel = new JLabel("MESSAGE");
    messageLabel.setFont(new Font("Dialog", Font.PLAIN, 9));
    GridBagConstraints gbc_messageLabel = new GridBagConstraints();
    gbc_messageLabel.insets = new Insets(0, 0, 5, 0);
    gbc_messageLabel.gridx = 0;
    gbc_messageLabel.gridy = 1;
    contentPanel.add(messageLabel, gbc_messageLabel);

    progressBar = new JProgressBar();
    progressBar.setIndeterminate(true);
    GridBagConstraints gbc_progressBar = new GridBagConstraints();
    gbc_progressBar.insets = new Insets(0, 0, 5, 0);
    gbc_progressBar.gridx = 0;
    gbc_progressBar.gridy = 2;
    contentPanel.add(progressBar, gbc_progressBar);
  }

  @Override
  public final boolean isInterrupted() {
    return interrupted;
  }

  @Override
  public final void message(final String message) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        messageLabel.setText(message);
      }
    });
  }

  @Override
  public final void setException(Exception exception) {
    this.exception = exception;
  }

  @Override
  public final void setInterrupted(boolean interrupted) {
    this.interrupted = interrupted;
  }

} public interface INotification {

  void addAbortListener();

  Exception getException();

  boolean isInterrupted();

  void message(String message);

  void setException(Exception exception);

  void setInterrupted(boolean interrupted);

} public interface IScanService {

  void configure(SaneDevice saneDevice, Scanner scanner) throws IOException;

  Scanner create(SaneDevice saneDevice, ServiceInfo serviceInfo, String hostAddress)
      throws IOException, SaneException;

  Scanner create(SaneDevice saneDevice, String hostAddress, int portNumber, String description)
      throws IOException, SaneException;

  SanePasswordProvider getPasswordProvider();

  SaneServiceIdentity getSaneServiceIdentity();

  void setPasswordProvider(SanePasswordProvider passwordProvider);

  void setSaneServiceIdentity(SaneServiceIdentity saneService);

  void setScannerOptions(SaneDevice saneDevice, Scanner scanner) throws IOException, SaneException;

} public class ScanJob {

  /**
   * Log4J logger.
   */
  private static final Logger LOG = Logger.getLogger(ScanJob.class);

  /**
   * Event Listener List for Scan Events.
   */
  private EventListenerList listenerList = new EventListenerList();

  /**
   * Scan Event.
   */
  private ScanEvent scanEvent;

  /**
   * Status notification with console output as default implementation.
   */
  private INotification notification = new ConsoleNotificationImpl();

  private Scanner scanner;

  private SwingWorker<Void, Void> worker;

  private int pagesToScan = Integer.MAX_VALUE;

  private IScanService scanService;

  private boolean useADF;

  private String batchPrefix;

  public ScanJob(IScanService scanServiceImpl, Scanner scanner) {
    scanService = scanServiceImpl;
    this.scanner = scanner;
  }

  public final void acquire(final ScanListener rateLimitedScanListener) {

    worker = new SwingWorker<Void, Void>() {

      @Override
      protected Void doInBackground() throws Exception {

        getNotificaiton().message(Localizer.localize("ScanStartingMessage"));

        String hostAddrStr = scanner.getRemoteAddress();
        int port = scanner.getRemotePortNumber();

        SaneSession session = null;
        SaneDevice device = null;

        try {
          session = SaneSession.withRemoteSane(InetAddress.getByName(hostAddrStr), port);
          session.setPasswordProvider(getPasswordProvider());
          device = session.getDevice(scanner.getName());

          try {
            if (!(device.isOpen())) {
              device.open();
            }
          } catch (IOException e) {
            getNotificaiton().message(e.getLocalizedMessage());
            LOG.warn(e, e);
            return null;
          }

          try {
            scanService.configure(device, scanner);
          } catch (IOException ex) {
            getNotificaiton().message(ex.getLocalizedMessage());
            LOG.warn(ex, ex);
            // continue if we can't set a setting.
          }

          int pageCount = 1;
          while (!isCancelled()) {
            try {
              BufferedImage image = device.acquireImage(rateLimitedScanListener);
              getNotificaiton().message(
                  String.format(Localizer.localize("ScannedPageMessage"), pageCount));
              fireScanEvent(image, pageCount);
            } catch (SaneException e) {
              if (e.getStatus() == SaneStatus.STATUS_NO_DOCS) {
                break;
              } else {
                getNotificaiton().message(e.getLocalizedMessage());
                LOG.warn(e, e);
                break;
              }
            } catch (IOException e) {
              getNotificaiton().message(e.getLocalizedMessage());
              LOG.warn(e, e);
              break;
            }
            if (!useADF && (pageCount >= pagesToScan)) {
              break;
            }
            pageCount++;
          }
        } catch (UnknownHostException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
          return null;
        } catch (IOException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
          return null;
        } finally {
          try {
            if ((device != null) && device.isOpen()) {
              device.close();
            }
          } catch (IOException e) {
            getNotificaiton().message(e.getLocalizedMessage());
            LOG.warn(e, e);
          }
          if (session != null) {
            session.close();
          }
        }
        return null;
      }

      @Override
      protected void done() {

        try {
          get();
          getNotificaiton().message(Localizer.localize("ScanCompleteMessage"));
        } catch (InterruptedException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
        } catch (ExecutionException e) {
          getNotificaiton().message(e.getLocalizedMessage());
          LOG.warn(e, e);
        } catch (CancellationException e) {
          getNotificaiton().message(Localizer.localize("ScanCancelledMessage"));
        }

        fireScanEvent(null, 0);

      }
    };

    worker.execute();

  }

  public final void addScanListener(final ScanEventListener listener) {
    listenerList.add(ScanEventListener.class, listener);
  }

  public final void cancel() {
    worker.cancel(true);
  }

  public final void checkOptions() throws Exception {

    String hostAddr = scanner.getRemoteAddress();
    int port = scanner.getRemotePortNumber();
    SaneSession session = null;
    SaneDevice device = null;

    try {
      session = SaneSession.withRemoteSane(InetAddress.getByName(hostAddr), port);
      session.setPasswordProvider(getPasswordProvider());
      device = session.getDevice(scanner.getName());
      if (!(device.isOpen())) {
        device.open();
      }
      scanService.configure(device, scanner);
      scanService.setScannerOptions(device, scanner);
    } catch (UnknownHostException e) {
      LOG.warn(e, e);
      throw e;
    } catch (SaneException e) {
      LOG.warn(e, e);
      throw e;
    } catch (IOException e) {
      LOG.warn(e, e);
      throw e;
    } finally {
      try {
        if ((device != null) && device.isOpen()) {
          device.close();
        }
      } catch (IOException e) {
        LOG.warn(e, e);
      }
      if (session != null) {
        try {
          session.close();
        } catch (IOException e) {
          LOG.warn(e, e);
        }
      }
    }

  }

  private void fireScanEvent(final BufferedImage bufferedImage, int pageNumber) {

    Object[] listeners = listenerList.getListenerList();

    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == ScanEventListener.class) {
        if (scanEvent == null) {
          scanEvent = new ScanEvent(this);
        }
        scanEvent.setAcquiredImage(bufferedImage);
        scanEvent.setPagesToScan(pagesToScan);
        scanEvent.setPageNumber(pageNumber);
        scanEvent.setBatchPrefix(getBatchPrefix());
        ((ScanEventListener) listeners[i + 1]).eventOccurred(scanEvent);
      }
    }

  }

  private String getBatchPrefix() {
    return batchPrefix;
  }

  /**
   * Returns an instance of INotification if set.
   *
   * @return an instance of INotification if set.
   */
  public final INotification getNotificaiton() {
    return notification;
  }

  private SanePasswordProvider getPasswordProvider() {
    return scanService.getPasswordProvider();
  }

  public final boolean isActive() {
    return !(worker.isDone());
  }

  /**
   * @param listener
   *          a scan listener
   */
  public final void removeScanEventListener(final ScanEventListener listener) {
    listenerList.remove(ScanEventListener.class, listener);
  }

  public final void setBatchPrefix(String batchPrefix) {
    this.batchPrefix = batchPrefix;
  }

  public final void setButtonValue(String key) {
    String hostAddr = scanner.getRemoteAddress();
    int port = scanner.getRemotePortNumber();
    SaneSession session = null;
    SaneDevice device = null;

    try {
      session = SaneSession.withRemoteSane(InetAddress.getByName(hostAddr), port);
      session.setPasswordProvider(getPasswordProvider());
      device = session.getDevice(scanner.getName());

      if (!(device.isOpen())) {
        device.open();
      }

      SaneOption saneOption = device.getOption(key);
      saneOption.setButtonValue(); // TODO: BUG??? doesn't do anything???

    } catch (UnknownHostException e) {
      LOG.warn(e, e);
    } catch (SaneException e) {
      LOG.warn(e, e);
    } catch (IOException e) {
      LOG.warn(e, e);
    } finally {
      try {
        if ((device != null) && device.isOpen()) {
          device.close();
        }
      } catch (IOException e) {
        LOG.warn(e, e);
      }
      if (session != null) {
        try {
          session.close();
        } catch (IOException e) {
          LOG.warn(e, e);
        }
      }
    }

  }

  /**
   * Sets an instance of INotification.
   *
   * @param notificationImpl
   *          an instance of INotification
   */
  public final void setNotificaiton(final INotification notificationImpl) {
    notification = notificationImpl;
  }

  public final void setPagesToScan(int pagesToScan) {
    this.pagesToScan = pagesToScan;
  }

  public final void setUseADF(boolean useADF) {
    this.useADF = useADF;
  }

} public class JTableButtonMouseListener implements MouseListener {

  private JTable jTable;

  public JTableButtonMouseListener(JTable table) {
    jTable = table;
  }

  private void forwardEventToButton(MouseEvent e) {

    TableColumnModel columnModel = jTable.getColumnModel();
    int column = columnModel.getColumnIndexAtX(e.getX());
    int row = e.getY() / jTable.getRowHeight();
    Object value;
    JButton button;
    MouseEvent buttonEvent;

    if ((row >= jTable.getRowCount()) || (row < 0) || (column >= jTable.getColumnCount())
        || (column < 0)) {
      return;
    }

    value = jTable.getValueAt(row, column);

    if (!(value instanceof JButton)) {
      return;
    }

    button = (JButton) value;

    buttonEvent = SwingUtilities.convertMouseEvent(jTable, e, button);
    button.dispatchEvent(buttonEvent);
    jTable.repaint();

  }

  @Override
  public final void mouseClicked(MouseEvent e) {
    forwardEventToButton(e);
  }

  @Override
  public final void mouseEntered(MouseEvent e) {
    forwardEventToButton(e);
  }

  @Override
  public final void mouseExited(MouseEvent e) {
    forwardEventToButton(e);
  }

  @Override
  public final void mousePressed(MouseEvent e) {
    forwardEventToButton(e);
  }

  @Override
  public final void mouseReleased(MouseEvent e) {
    forwardEventToButton(e);
  }

} public final class Localizer {

  /**
   * Returns the current locale.
   *
   * @return the current locale
   */
  public static Locale getCurrentLocale() {
    if (currentLocale == null) {
      setCurrentLocale(Locale.getDefault());
    }
    return currentLocale;
  }

  /**
   * Returns a translation for the specified key in the current locale.
   *
   * @param key
   *          a translation key listed in the messages resource file
   * @return a translation for the specified key in the current locale
   */
  public static String localize(String key) {
    try {
      return BUNDLE.getString(key);
    } catch (Exception ex) {
      LOG.warn(ex.getLocalizedMessage(), ex);
      LOG.warn("Missing translation: " + key);
      return key;
    }
  }

  /**
   * Sets the current locale and attempts to load the messages resource file for the locale.
   *
   * @param currentLocale
   *          a locale that has a messages resource
   */
  public static void setCurrentLocale(Locale currentLocale) {
    Localizer.currentLocale = currentLocale;
    try {
      ResourceBundle.getBundle(MESSAGES_RESOURCE, currentLocale);
    } catch (Exception ex) {
      ResourceBundle.getBundle(MESSAGES_RESOURCE, DEFAULT_LOCALE);
      currentLocale = DEFAULT_LOCALE;
    }
  }

  private static final String MESSAGES_RESOURCE = "com.swingsane.i18n.messages";

  private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;

  private static Locale currentLocale = DEFAULT_LOCALE;

  private static final Logger LOG = Logger.getLogger(Localizer.class);

  private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(MESSAGES_RESOURCE,
      getCurrentLocale());

  private Localizer() {
  }

} public final class PreferencesUtils {

  private static Constraints copy(Constraints c1) {
    if (c1 == null) {
      return null;
    }
    Constraints c2 = new Constraints();
    c2.setFixedList(copyDoubleList(c1.getFixedList()));
    c2.setIntegerList(copyIntegerList(c1.getIntegerList()));
    c2.setMaximumFixed(copyDouble(c1.getMaximumFixed()));
    c2.setMaximumInteger(copyInteger(c1.getMaximumInteger()));
    c2.setMinimumFixed(copyDouble(c1.getMinimumFixed()));
    c2.setMinimumInteger(copyInteger(c1.getMinimumInteger()));
    c2.setQuantumFixed(copyDouble(c1.getQuantumFixed()));
    c2.setQuantumInteger(copyInteger(c1.getQuantumInteger()));
    c2.setStringList(copyStringList(c1.getStringList()));
    return c2;
  }

  public static HashMap<String, Login> copy(HashMap<String, Login> sl1) {
    HashMap<String, Login> sl2 = new HashMap<String, Login>();
    for (String resource : sl1.keySet()) {
      Login login = new Login();
      login.setUsername(sl1.get(resource).getUsername());
      login.setPassword(sl1.get(resource).getPassword());
      sl2.put(resource, login);
    }
    return sl2;
  }

  public static Option copy(Option o1) {

    Option o2 = null;

    OptionsOrderValuePair vp1 = o1.getOptionsOrderValuePair();
    OptionsOrderValuePair vp2 = new OptionsOrderValuePair();

    vp2.setActive(vp1.isActive());
    vp2.setSaneOptionType(vp1.getSaneOptionType());
    vp2.setKey(vp1.getKey());

    switch (vp1.getSaneOptionType()) {

    case STRING:
      StringOption so1 = (StringOption) o1;
      StringOption so2 = new StringOption();
      so2.setName(so1.getName());
      so2.setDescription(so1.getDescription());
      so2.setConstraints(copy(so1.getConstraints()));
      so2.setConstraintType(so1.getConstraintType());
      so2.setValue(so1.getValue());
      so2.setOptionsOrderValuePair(vp2);
      return so2;

    case INTEGER:
      IntegerOption io1 = (IntegerOption) o1;
      IntegerOption io2 = new IntegerOption();
      io2.setName(io1.getName());
      io2.setDescription(io1.getDescription());
      io2.setConstraints(copy(io1.getConstraints()));
      io2.setConstraintType(io1.getConstraintType());
      io2.setValue(io1.getValue());

      // cast to ArrayList - SaneOptions calls newArrayList
      io2.setValueList(copyIntegerList(io1.getValueList()));

      io2.setOptionsOrderValuePair(vp2);
      return io2;

    case BOOLEAN:
      BooleanOption bo1 = (BooleanOption) o1;
      BooleanOption bo2 = new BooleanOption();
      bo2.setName(bo1.getName());
      bo2.setDescription(bo1.getDescription());
      bo2.setConstraints(copy(bo1.getConstraints()));
      bo2.setConstraintType(bo1.getConstraintType());
      bo2.setValue(bo1.getValue());
      bo2.setOptionsOrderValuePair(vp2);
      return bo2;

    case FIXED:
      FixedOption fo1 = (FixedOption) o1;
      FixedOption fo2 = new FixedOption();
      fo2.setName(fo1.getName());
      fo2.setDescription(fo1.getDescription());
      fo2.setConstraints(copy(fo1.getConstraints()));
      fo2.setConstraintType(fo1.getConstraintType());
      fo2.setValue(fo1.getValue());

      // cast to ArrayList - SaneOptions calls newArrayList
      fo2.setValueList(copyDoubleList(fo1.getValueList()));

      fo2.setOptionsOrderValuePair(vp2);
      return fo2;

    case BUTTON:
      ButtonOption bto1 = (ButtonOption) o1;
      ButtonOption bto2 = new ButtonOption();
      copyBooleanOptions(bto1.getBooleanOptions(), bto2.getBooleanOptions());
      copyIntegerOptions(bto1.getIntegerOptions(), bto2.getIntegerOptions());
      copyStringOptions(bto1.getStringOptions(), bto2.getStringOptions());
      copyFixedOptions(bto1.getFixedOptions(), bto2.getFixedOptions());
      copyButtonOptions(bto1.getButtonOptions(), bto2.getButtonOptions());
      copyGroupOptions(bto1.getGroupOptions(), bto2.getGroupOptions());
      bto2.setName(bto1.getName());
      bto2.setDescription(bto1.getDescription());
      bto2.setOptionsOrderValuePair(vp2);
      bto2.setOptionOrdering(null); // TODO!!!!
      return bto2;

    case GROUP:
      GroupOption go1 = (GroupOption) o1;
      GroupOption go2 = new GroupOption();
      copyBooleanOptions(go1.getBooleanOptions(), go2.getBooleanOptions());
      copyIntegerOptions(go1.getIntegerOptions(), go2.getIntegerOptions());
      copyStringOptions(go1.getStringOptions(), go2.getStringOptions());
      copyFixedOptions(go1.getFixedOptions(), go2.getFixedOptions());
      copyGroupOptions(go1.getGroupOptions(), go2.getGroupOptions());
      copyGroupOptions(go1.getGroupOptions(), go2.getGroupOptions());
      go2.setName(go1.getName());
      go2.setDescription(go1.getDescription());
      go2.setOptionsOrderValuePair(vp2);
      go2.setOptionOrdering(null); // TODO!!!!
      return go2;

    default:
      break;

    }

    return o2;

  }

  public static Scanner copy(Scanner s1) {

    Scanner s2 = new Scanner();

    s2.setGuid(s1.getGuid());
    s2.setDescription(s1.getDescription());
    s2.setBatchPrefix(s1.getBatchPrefix());
    s2.setModel(s1.getModel());
    s2.setName(s1.getName());
    s2.setPagesToScan(s1.getPagesToScan());
    s2.setRemoteAddress(s1.getRemoteAddress());
    s2.setRemotePortNumber(s1.getRemotePortNumber());
    s2.setServiceName(s1.getServiceName());
    s2.setType(s1.getType());
    s2.setUsingCustomOptions(s1.isUsingCustomOptions());
    s2.setVendor(s1.getVendor());

    // deep copy the option maps
    copyBooleanOptions(s1.getBooleanOptions(), s2.getBooleanOptions());
    copyIntegerOptions(s1.getIntegerOptions(), s2.getIntegerOptions());
    copyFixedOptions(s1.getFixedOptions(), s2.getFixedOptions());
    copyStringOptions(s1.getStringOptions(), s2.getStringOptions());
    copyButtonOptions(s1.getButtonOptions(), s2.getButtonOptions());
    copyGroupOptions(s1.getGroupOptions(), s2.getGroupOptions());

    // reconnect the circular referencing
    copyOptionOrdering(s1.getOptionOrdering(), s2.getOptionOrdering(), s1.getBooleanOptions(),
        s1.getIntegerOptions(), s1.getFixedOptions(), s1.getStringOptions(), s1.getButtonOptions(),
        s1.getGroupOptions());

    return s2;

  }

  private static void copyBooleanOptions(HashMap<String, BooleanOption> bo1,
      HashMap<String, BooleanOption> bo2) {
    for (String key : bo1.keySet()) {
      bo2.put(key, (BooleanOption) copy(bo1.get(key)));
    }
  }

  private static void copyButtonOptions(HashMap<String, ButtonOption> bo1,
      HashMap<String, ButtonOption> bo2) {
    for (String key : bo1.keySet()) {
      bo2.put(key, (ButtonOption) copy(bo1.get(key)));
    }
  }

  private static Double copyDouble(Double d1) {
    if (d1 == null) {
      return null;
    }
    return new Double(d1.doubleValue());
  }

  public static ArrayList<Double> copyDoubleList(List<Double> l1) {
    if (l1 == null) {
      return null;
    }
    ArrayList<Double> l2 = new ArrayList<Double>(l1.size());
    for (Double i : l1) {
      l2.add(i.doubleValue());
    }
    return l2;
  }

  private static void copyFixedOptions(HashMap<String, FixedOption> fo1,
      HashMap<String, FixedOption> fo2) {
    for (String key : fo1.keySet()) {
      fo2.put(key, (FixedOption) copy(fo1.get(key)));
    }
  }

  private static void copyGroupOptions(HashMap<String, GroupOption> go1,
      HashMap<String, GroupOption> go2) {
    for (String key : go1.keySet()) {
      go2.put(key, (GroupOption) copy(go1.get(key)));
    }
  }

  private static Integer copyInteger(Integer i1) {
    if (i1 == null) {
      return null;
    }
    return new Integer(i1.intValue());
  }

  public static ArrayList<Integer> copyIntegerList(List<Integer> l1) {
    if (l1 == null) {
      return null;
    }
    ArrayList<Integer> l2 = new ArrayList<Integer>(l1.size());
    for (Integer i : l1) {
      l2.add(i.intValue());
    }
    return l2;
  }

  private static void copyIntegerOptions(HashMap<String, IntegerOption> io1,
      HashMap<String, IntegerOption> io2) {
    for (String key : io1.keySet()) {
      io2.put(key, (IntegerOption) copy(io1.get(key)));
    }
  }

  public static void copyOptionOrdering(ArrayList<OptionsOrderValuePair> oo1,
      ArrayList<OptionsOrderValuePair> oo2, HashMap<String, BooleanOption> booleanOptions,
      HashMap<String, IntegerOption> integerOptions, HashMap<String, FixedOption> fixedOptions,
      HashMap<String, StringOption> stringOptions, HashMap<String, ButtonOption> buttonOptions,
      HashMap<String, GroupOption> groupOptions) {

    for (OptionsOrderValuePair vp1 : oo1) {
      switch (vp1.getSaneOptionType()) {
      case STRING:
        oo2.add(stringOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case INTEGER:
        oo2.add(integerOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case FIXED:
        oo2.add(fixedOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case BOOLEAN:
        oo2.add(booleanOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case BUTTON:
        oo2.add(buttonOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      case GROUP:
        oo2.add(groupOptions.get(vp1.getKey()).getOptionsOrderValuePair());
        break;
      default:
        break;
      }
    }

  }

  private static ArrayList<String> copyStringList(List<String> l1) {
    if (l1 == null) {
      return null;
    }
    ArrayList<String> l2 = new ArrayList<String>(l1.size());
    for (String str : l1) {
      l2.add(str);
    }
    return l2;
  }

  private static void copyStringOptions(HashMap<String, StringOption> so1,
      HashMap<String, StringOption> so2) {
    for (String key : so1.keySet()) {
      so2.put(key, (StringOption) copy(so1.get(key)));
    }
  }

  public static void exportScannerXML(XStream xstream, Scanner scanner, File file)
      throws IOException {
    String rootNodeName = "swingsane";
    BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),
        "UTF-8"));
    out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    ObjectOutputStream oos;
    oos = xstream.createObjectOutputStream(out, rootNodeName);
    oos.writeObject(scanner);
    oos.close();
  }

  public static Scanner importScannerXML(XStream xstream, File file) throws IOException,
  ClassNotFoundException {
    ObjectInputStream s = null;
    FileInputStream fis = null;
    Scanner scanner = null;
    try {
      fis = new FileInputStream(file);
      s = xstream.createObjectInputStream(new InputStreamReader(fis, "UTF-8"));
      scanner = ((Scanner) s.readObject());
      restoreCircularReferences(scanner);
      return scanner;
    } catch (IOException ex) {
      throw ex;
    } finally {
      if (s != null) {
        s.close();
      }
      if (fis != null) {
        fis.close();
      }
    }
  }

  static void restoreCircularReferences(Scanner scanner) {
    ArrayList<OptionsOrderValuePair> ordering = new ArrayList<OptionsOrderValuePair>();
    for (OptionsOrderValuePair vp : scanner.getOptionOrdering()) {
      switch (vp.getSaneOptionType()) {
      case STRING:
        ordering.add(scanner.getStringOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case INTEGER:
        ordering.add(scanner.getIntegerOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case FIXED:
        ordering.add(scanner.getFixedOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case BOOLEAN:
        ordering.add(scanner.getBooleanOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case BUTTON:
        ordering.add(scanner.getButtonOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      case GROUP:
        ordering.add(scanner.getGroupOptions().get(vp.getKey()).getOptionsOrderValuePair());
        break;
      default:
        break;
      }
    }
    scanner.setOptionOrdering(ordering);
  }

  public static void update(HashMap<String, Login> sl1, HashMap<String, Login> sl2) {
    sl1.clear();
    sl1.putAll(sl2);
  }

  public static void update(Scanner s1, Scanner s2) {
    s1.setDescription(s2.getDescription());
    s1.setBatchPrefix(s2.getBatchPrefix());
    s1.setBooleanOptions(s2.getBooleanOptions());
    s1.setButtonOptions(s2.getButtonOptions());
    s1.setFixedOptions(s2.getFixedOptions());
    s1.setGroupOptions(s2.getGroupOptions());
    s1.setIntegerOptions(s2.getIntegerOptions());
    s1.setModel(s2.getModel());
    s1.setName(s2.getName());
    s1.setOptionOrdering(s2.getOptionOrdering());
    s1.setPagesToScan(s2.getPagesToScan());
    s1.setRemoteAddress(s2.getRemoteAddress());
    s1.setRemotePortNumber(s2.getRemotePortNumber());
    s1.setServiceName(s2.getServiceName());
    s1.setStringOptions(s2.getStringOptions());
    s1.setType(s2.getType());
    s1.setUsingCustomOptions(s2.isUsingCustomOptions());
    s1.setVendor(s2.getVendor());
  }

  private PreferencesUtils() {
  }

} @XStreamAlias("booleanOption")
public class BooleanOption extends BaseOptions {

  private boolean value;

  public final boolean getValue() {
    return value;
  }

  public final void setValue(boolean value) {
    this.value = value;
  }

} @XStreamAlias("buttonOption")
public class ButtonOption extends GroupOptions {

} public class Constraints {

  private Integer minimumInteger;

  private Integer maximumInteger;

  private Integer quantumInteger;

  private Double minimumFixed;

  private Double maximumFixed;

  private Double quantumFixed;

  private List<Integer> integerList;

  private List<String> stringList;

  private List<Double> fixedList;

  public final List<Double> getFixedList() {
    return fixedList;
  }

  public final List<Integer> getIntegerList() {
    return integerList;
  }

  public final Double getMaximumFixed() {
    return maximumFixed;
  }

  public final Integer getMaximumInteger() {
    return maximumInteger;
  }

  public final Double getMinimumFixed() {
    return minimumFixed;
  }

  public final Integer getMinimumInteger() {
    return minimumInteger;
  }

  public final Double getQuantumFixed() {
    return quantumFixed;
  }

  public final Integer getQuantumInteger() {
    return quantumInteger;
  }

  public final List<String> getStringList() {
    return stringList;
  }

  public final void setFixedList(List<Double> fixedList) {
    this.fixedList = fixedList;
  }

  public final void setIntegerList(List<Integer> integerList) {
    this.integerList = integerList;
  }

  public final void setMaximumFixed(Double maximumFixed) {
    this.maximumFixed = maximumFixed;
  }

  public final void setMaximumInteger(Integer maximumInteger) {
    this.maximumInteger = maximumInteger;
  }

  public final void setMinimumFixed(Double minimumFixed) {
    this.minimumFixed = minimumFixed;
  }

  public final void setMinimumInteger(Integer minimumInteger) {
    this.minimumInteger = minimumInteger;
  }

  public final void setQuantumFixed(Double quantumFixed) {
    this.quantumFixed = quantumFixed;
  }

  public final void setQuantumInteger(Integer quantumInteger) {
    this.quantumInteger = quantumInteger;
  }

  public final void setStringList(List<String> stringList) {
    this.stringList = stringList;
  }

} @XStreamAlias("fixedOption")
public class FixedOption extends BaseOptions {

  private double value;

  private List<Double> valueList;

  public final double getValue() {
    return value;
  }

  public final List<Double> getValueList() {
    return valueList;
  }

  public final void setValue(double value) {
    this.value = value;
  }

  public final void setValueList(List<Double> valueList) {
    this.valueList = valueList;
  }

} @XStreamAlias("integerOption")
public class IntegerOption extends BaseOptions {

  private int value;

  private List<Integer> valueList;

  public final int getValue() {
    return value;
  }

  public final List<Integer> getValueList() {
    return valueList;
  }

  public final void setValue(int value) {
    this.value = value;
  }

  public final void setValueList(List<Integer> valueList) {
    this.valueList = valueList;
  }

} @XStreamAlias("optionsOrderValuePair")
public class OptionsOrderValuePair {

  public enum SaneOptionType {
    STRING, BOOLEAN, INTEGER, FIXED, GROUP, BUTTON
  };

  private String key;

  private SaneOptionType saneOptionType;

  private boolean active = true;

  public final String getKey() {
    return key;
  }

  public final SaneOptionType getSaneOptionType() {
    return saneOptionType;
  }

  private String getStatus() {
    if (active) {
      return " (" + Localizer.localize("OptionActiveStatusText") + ")";
    } else {
      return " (" + Localizer.localize("OptionInactiveStatusText") + ")";
    }
  }

  public final boolean isActive() {
    return active;
  }

  public final void setActive(boolean active) {
    this.active = active;
  }

  public final void setKey(String key) {
    this.key = key;
  }

  public final void setSaneOptionType(SaneOptionType saneOptionType) {
    this.saneOptionType = saneOptionType;
  }

  @Override
  public final String toString() {
    return key + getStatus();
  }

} @XStreamAlias("scanner")
public class Scanner extends ScannerOptions {

  private String guid;
  private String description;
  private String serviceName;
  private String remoteAddress;
  private int remotePortNumber;
  private String vendor;
  private String model;
  private String name;
  private String type;
  private int pagesToScan;
  private boolean usingCustomOptions;
  private String batchPrefix = Localizer.localize("ScanFileNamePrefix") + "_"
      + Localizer.localize("TimeStampToken") + "_" + Localizer.localize("PageNumberToken") + "_"
      + Localizer.localize("PageCountToken");

  public Scanner() {
    setGuid((new RandomGUID()).toString());
  }

  public final String getBatchPrefix() {
    return batchPrefix;
  }

  public final String getDescription() {
    return description;
  }

  public final String getGuid() {
    return guid;
  }

  public final String getModel() {
    return model;
  }

  public final String getName() {
    return name;
  }

  public final int getPagesToScan() {
    return pagesToScan;
  }

  public final String getRemoteAddress() {
    return remoteAddress;
  }

  public final int getRemotePortNumber() {
    return remotePortNumber;
  }

  public final String getServiceName() {
    return serviceName;
  }

  public final String getType() {
    return type;
  }

  public final String getVendor() {
    return vendor;
  }

  public final boolean isUsingCustomOptions() {
    return usingCustomOptions;
  }

  public final void setBatchPrefix(String batchPrefix) {
    this.batchPrefix = batchPrefix;
  }

  public final void setDescription(String description) {
    this.description = description;
  }

  public void setGuid(String guid) {
    this.guid = guid;
  }

  public final void setModel(String model) {
    this.model = model;
  }

  public final void setName(String name) {
    this.name = name;
  }

  public final void setPagesToScan(int pagesToScan) {
    this.pagesToScan = pagesToScan;
  }

  public final void setRemoteAddress(String remoteAddress) {
    this.remoteAddress = remoteAddress;
  }

  public final void setRemotePortNumber(int remotePortNumber) {
    this.remotePortNumber = remotePortNumber;
  }

  public final void setServiceName(String serviceName) {
    this.serviceName = serviceName;
  }

  public final void setType(String type) {
    this.type = type;
  }

  public final void setUsingCustomOptions(boolean usingCustomOptions) {
    this.usingCustomOptions = usingCustomOptions;
  }

  public final void setVendor(String vendor) {
    this.vendor = vendor;
  }

} @XStreamAlias("stringOption")
public class StringOption extends BaseOptions {

  private String value;

  public final String getValue() {
    return value;
  }

  public final void setValue(String value) {
    this.value = value;
  }

} public class FilenameExtensionFilter implements FilenameFilter {

  private ArrayList<String> extensions = new ArrayList<String>();

  @Override
  public final boolean accept(File dir, String name) {
    if (new File(dir, name).isDirectory()) {
      return false;
    }
    name = name.toLowerCase();
    return extensions.contains(getExtension(name));
  }

  public final void addExtension(String extension) {
    extensions.add(extension);
  }

  private String getExtension(String name) {
    if (name != null) {
      int i = name.lastIndexOf('.');
      if ((i > 0) && (i < (name.length() - 1))) {
        return name.substring(i + 1).toLowerCase();
      }
    }
    return null;
  }

} public final class Misc {

  public static void openURL(String url) {
    String osName = System.getProperty("os.name");

    try {
      if (osName.startsWith("Mac OS")) {
        Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
        Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] { String.class });
        openURL.invoke(null, new Object[] { url });
      } else if (osName.startsWith("Windows")) {
        Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
      } else { // assume Unix or Linux
        String[] browsers = { "chromium-browser", "google-chrome", "google-chrome-stable",
            "chrome", "/opt/google/chrome/chrome", "firefox", "opera", "konqueror", "epiphany",
            "mozilla", "netscape" };
        String browser = null;
        for (int count = 0; (count < browsers.length) && (browser == null); count++) {
          if (Runtime.getRuntime().exec(new String[] { "which", browsers[count] }).waitFor() == 0) {
            browser = browsers[count];
          }
        }
        if (browser == null) {
          throw new Exception(Localizer.localize("LaunchWebBrowserNotFound"));
        } else {
          Runtime.getRuntime().exec(new String[] { browser, url });
        }
      }

    } catch (Exception e) {
      JOptionPane.showMessageDialog(null, e.getMessage());
    }

  }

  public static boolean showConfirmDialog(final Component parent, final String title,
      final String message, final String confirmButtonText, final String cancelButtonText) {

    String options[] = new String[] { confirmButtonText, cancelButtonText };

    int result = JOptionPane.showOptionDialog(parent, message, title, JOptionPane.DEFAULT_OPTION,
        JOptionPane.WARNING_MESSAGE, null, options, options[0]);

    return result == 0;

  }

  public static void showErrorMsg(final Component parent, final String message) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        JOptionPane.showMessageDialog(parent, message, Localizer.localize("ErrorMessageTitle"),
            JOptionPane.ERROR_MESSAGE);
      }
    });
  }

  public static void showSuccessMsg(final Component parent, final String message) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        JOptionPane.showMessageDialog(parent, message, Localizer.localize("SuccessMessageTitle"),
            JOptionPane.INFORMATION_MESSAGE);
      }
    });
  }

  private Misc() {
  }

}
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import org.apache.log4j.Logger;
import com.swingsane.business.notification.DialogNotificationImpl;
import com.swingsane.business.notification.INotification;
import com.swingsane.business.scanning.IScanService;
import com.swingsane.business.scanning.ScanJob;
import com.swingsane.gui.component.JTableButtonMouseListener;
import com.swingsane.i18n.Localizer;
import com.swingsane.preferences.PreferencesUtils;
import com.swingsane.preferences.model.BooleanOption;
import com.swingsane.preferences.model.ButtonOption;
import com.swingsane.preferences.model.Constraints;
import com.swingsane.preferences.model.FixedOption;
import com.swingsane.preferences.model.IntegerOption;
import com.swingsane.preferences.model.OptionsOrderValuePair;
import com.swingsane.preferences.model.Scanner;
import com.swingsane.preferences.model.StringOption;
import com.swingsane.util.FilenameExtensionFilter;
import com.swingsane.util.Misc;
import com.thoughtworks.xstream.XStream;

  private void cancelActionPerformed(ActionEvent e) {
    dispose();
  }

  private void cellButtonClicked(String key) {
    ScanJob scanJob = new ScanJob(scanService, scanner);
    scanJob.setButtonValue(key);
  }

  private void checkOptionsActionPerformed(ActionEvent e) {

    final INotification notificaiton = new DialogNotificationImpl(getRootPane()
        .getTopLevelAncestor());

    SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

      @Override
      protected Void doInBackground() throws Exception {
        notificaiton.message(Localizer.localize("CheckingOptionsText"));
        ScanJob scanJob = new ScanJob(scanService, scanner);
        try {
          scanJob.checkOptions();
        } catch (Exception ex) {
          showCheckOptionsErrorMessage(ex);
        }
        updateTableModel();
        optionNameTable.revalidate();
        return null;
      }

      @Override
      protected void done() {
        try {
          get();
          ((JDialog) notificaiton).setVisible(false);
        } catch (Exception ex) {
          LOG.error(ex, ex);
        } finally {
          ((JDialog) notificaiton).dispose();
        }
      }
    };

    worker.execute();

    ((JDialog) notificaiton).setModal(true);
    ((JDialog) notificaiton).setVisible(true);

  }

  private void exportActionPerformed(ActionEvent e) {

    final File file = getSaveFile("scanner", "xml");

    if (file == null) {
      return;
    }

    final INotification notificaiton = new DialogNotificationImpl(getRootPane()
        .getTopLevelAncestor());

    SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

      @Override
      protected Void doInBackground() throws Exception {
        notificaiton.message(Localizer.localize("SavingScannerPreferencesMessageText"));
        PreferencesUtils.exportScannerXML(xstream, scanner, file);
        return null;
      }

      @Override
      protected void done() {
        try {
          get();
          showSaveSuccessMessage();
          ((JDialog) notificaiton).setVisible(false);
        } catch (Exception ex) {
          LOG.error(ex, ex);
          showSaveErrorMessage(ex);
        } finally {
          ((JDialog) notificaiton).dispose();
        }
      }
    };

    worker.execute();

    ((JDialog) notificaiton).setModal(true);
    ((JDialog) notificaiton).setVisible(true);

  }

  public final int getDialogResult() {
    return dialogResult;
  }

  private File getLoadFile(String filename, String extension) {
    FileDialog fd = new FileDialog((JDialog) getRootPane().getTopLevelAncestor(),
        Localizer.localize("LoadScannerOptionsFromFileTitle"), FileDialog.LOAD);
    fd.setDirectory(".");
    FilenameExtensionFilter filter = new FilenameExtensionFilter();
    filter.addExtension("xml");
    fd.setFilenameFilter(filter);
    fd.setModal(true);
    fd.setVisible(true);
    if (fd.getFile() == null) {
      return null;
    }
    return new File(fd.getDirectory() + File.separator + fd.getFile());
  }

  private Component getOptionComponent(OptionsOrderValuePair vp) {

    // TODO: finish adding the constraints code.

    switch (vp.getSaneOptionType()) {

    case STRING:

      StringOption stringOption = scanner.getStringOptions().get(vp.getKey());