public final class CategoryManager {
   /** The pinboard, which contains all notes. **/
   private PinBoard pinBoard = new PinBoard();

   /** Listeners, which will be added per default to new notes. **/
   private final Collection<NoteChangedEventListener> defaultNoteChangedEventListeners = new LinkedList<>();

   /** Listeners, which will be added per default to new categories. **/
   private final Collection<CategoryChangedEventListener> defaultCategoryChangedEventlisteners = new LinkedList<>();
   private final Collection<NoteAddedEventListener> defaultNoteAddedEventListeners = new LinkedList<>();
   private final Collection<NoteRemovedEventListener> defaultNoteRemovedEventListeners = new LinkedList<>();

   /** Listeners, which will be added per default to new pinboards. **/
   private final Collection<CategoryAddedEventListener> defaultCategoryAddedEventListeners = new LinkedList<>();
   private final Collection<CategoryRemovedEventListener> defaultCategoryRemovedEventListeners = new LinkedList<>();

   private static class Holder {
      private static final CategoryManager INSTANCE = new CategoryManager();
   }

   public static CategoryManager getInstance() {
      return Holder.INSTANCE;
   }

   private CategoryManager() {
      super();
   }

   public void addCategory(Category c) {
      addDefaultCategoryEventListeners(c);
      pinBoard.addCategory(c);
   }

   public void removeCategory(Category c) {
      pinBoard.removeCategory(c);
   }

   public void removeNote(Note note) {
      findCategoryForNote(note).removeNote(note);
   }

   public void hideAllNotes() {
      for (Category cat : pinBoard.getCategories()) {
         cat.hideAllNotes();
      }
   }

   public void unhideAllNotes() {
      for (Category cat : pinBoard.getCategories()) {
         cat.unhideAllNotes();
      }
   }

   public String[] getCategoryNames() {
      int n = getNumberOfCategories();
      String[] s = new String[n];
      int ni = 0;
      for (Category cat : pinBoard.getCategories()) {
         s[ni] = cat.getName();
         ni++;
      }
      return s;
   }

   public int getNumberOfCategories() {
      return pinBoard.getCategories().size();
   }

   public Category getDefaultCategory() {
      for (Category cat : pinBoard.getCategories()) {
         if (cat.isDefaultCategory()) {
            return cat;
         }
      }
      return null;
   }

   public void setDefaultCategory(Category c) {
      c.setDefault(true);
      pinBoard.getCategories().stream().filter(cat -> cat != c).forEach(cat -> {
         cat.setDefault(false);
      });
   }

   public void showOnlyNotesOfCategory(Category c) {
      c.unhideAllNotes();
      for (Category cat : pinBoard.getCategories()) {
         if (cat != c) {
            cat.hideAllNotes();
         }
      }
   }

   public void moveCategoryUp(Category c) {
      pinBoard.moveCategoryUp(c);
   }

   public void moveCategoryDown(Category c) {
      pinBoard.moveCategoryDown(c);
   }

   public Category getCategoryByNumber(int n) {
      return pinBoard.getCategoryByNumber(n);
   }

   public List<Category> getCategories() {
      return pinBoard.getCategories();
   }

   public void replaceWithNewCategories(List<Category> newCategories) {
      hideAllNotes();

      for (Category category : newCategories) {
         addDefaultCategoryEventListeners(category);

         for (Note note : category.getNotes()) {
            addDefaultNoteEventListeners(note);
         }
      }

      pinBoard = new PinBoard();
      addDefaultPinBoardEventListeners(pinBoard);

      for (Category category : newCategories) {
         pinBoard.addCategory(category);
      }
   }

   public void moveNoteToCategory(Note note, int catNumber) {
      Category newCat = getCategoryByNumber(catNumber);
      if (newCat != null) {
         // remove from old category
         Category oldCat = findCategoryForNote(note);
         if (oldCat != null) {
            oldCat.removeNote(note);
         }
         // add to new category
         newCat.addNote(note);
         // set note color to default color of the new category
         note.setColor(newCat.getDefaultNoteColor());
      }
   }

   public Category findCategoryForNote(Note note) {
      Category categoryForNote = null;
      for (Category category : pinBoard.getCategories()) {
         if (category.containsNote(note)) {
            categoryForNote = category;
            break;
         }
      }
      return categoryForNote;
   }

   public Note createNoteAndAddToDefaultCategory() {
      Category defCat = getDefaultCategory();
      Note newNote = createNoteWithDefaultUserSettings();
      defCat.addNote(newNote);
      newNote.setColor(defCat.getDefaultNoteColor());
      addDefaultNoteEventListeners(newNote);

      return newNote;
   }

   private Note createNoteWithDefaultUserSettings() {
      UserSettings userSettings = UserSettings.getInstance();
      Note newNote = new Note();
      newNote.setSize(userSettings.getDefaultWindowWidth(), userSettings.getDefaultWindowHeight());
      newNote.setPosition(userSettings.getDefaultWindowXPostition(), userSettings.getDefaultWindowYPostition());
      newNote.setFontSize(userSettings.getDefaultFontSize());
      newNote.setAlwaysOnTop(userSettings.getDefaultAlwaysOnTop());
      return newNote;
   }

   private void addDefaultNoteEventListeners(Note note) {
      for (NoteChangedEventListener listener : defaultNoteChangedEventListeners) {
         note.addNoteChangedEventListener(listener);
      }
   }

   private void addDefaultCategoryEventListeners(Category category) {
      for (CategoryChangedEventListener listener : defaultCategoryChangedEventlisteners) {
         category.addCategoryChangedEventListener(listener);
      }
      for (NoteAddedEventListener listener : defaultNoteAddedEventListeners) {
         category.addNoteAddedEventListener(listener);
      }
      for (NoteRemovedEventListener listener : defaultNoteRemovedEventListeners) {
         category.addNoteRemovedEventListener(listener);
      }
   }

   private void addDefaultPinBoardEventListeners(PinBoard pinBoard) {
      for (CategoryAddedEventListener listener : defaultCategoryAddedEventListeners) {
         pinBoard.addCategoryAddedEventListener(listener);
      }
      for (CategoryRemovedEventListener listener : defaultCategoryRemovedEventListeners) {
         pinBoard.addCategoryRemovedEventListener(listener);
      }
   }

   public void registerDefaultNoteChangedEventListener(NoteChangedEventListener listener) {
      defaultNoteChangedEventListeners.add(listener);
   }

   public void registerDefaultNoteAddedEventListener(NoteAddedEventListener listener) {
      defaultNoteAddedEventListeners.add(listener);
   }

   public void registerDefaultNoteRemovedEventListener(NoteRemovedEventListener listener) {
      defaultNoteRemovedEventListeners.add(listener);
   }

   public void registerDefaultCategoryChangedEventListener(CategoryChangedEventListener listener) {
      defaultCategoryChangedEventlisteners.add(listener);
   }

   public void registerDefaultCategoryAddedEventListener(CategoryAddedEventListener listener) {
      defaultCategoryAddedEventListeners.add(listener);
   }

   public void registerDefaultCategoryRemovedEventListener(CategoryRemovedEventListener listener) {
      defaultCategoryRemovedEventListeners.add(listener);
   }
} public final class I18N {
   public enum SupportedLocale {
      de_DE("de", "DE"),
      cs_CZ("cs", "CZ"),
      en_US("en", "US"),
      ru("ru"),
      uk_UA("uk", "UA");

      private final Locale locale;

      private SupportedLocale(String language) {
         locale = new Locale(language);
      }

      private SupportedLocale(String language, String country) {
         locale = new Locale(language, country);
      }

      public Locale getLocale() {
         return locale;
      }

      @Override
      public String toString() {
         return locale.getDisplayName();
      }

      public static SupportedLocale fromLocaleString(String localeString) {
         String[] localeParts = localeString.split("_");
         String language = localeParts[0];
         String country = "";
         if (localeParts.length > 1) {
            country = localeParts[1];
         }

         SupportedLocale locale = FALLBACK_LOCALE;
         for (SupportedLocale l : SupportedLocale.values()) {
            if (language.equals(l.getLocale().getLanguage()) && country.equals(l.getLocale().getCountry())) {
               locale = l;
               break;
            }
         }
         return locale;
      }
   }

   private static final SupportedLocale FALLBACK_LOCALE = SupportedLocale.en_US;

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

   private ResourceBundle res;

   private static class Holder {
      private static final I18N INSTANCE = new I18N();
   }

   public static I18N getInstance() {
      return Holder.INSTANCE;
   }

   public void setLocale(Locale locale) {
      res = ResourceBundle.getBundle("i18n.messages", locale);
      Locale.setDefault(locale);
   }

   private I18N() {
      res = ResourceBundle.getBundle("i18n.messages", FALLBACK_LOCALE.getLocale());
   }

   public String getString(String key) {
      String s;
      try {
         s = res.getString(key);
      } catch (MissingResourceException e) {
         s = key;
         LOG.error("The resource with the key '" + key + "' could not be found.", e);
      }
      return s;
   }

   public String getString(String key, Object... args) {
      String message = getString(key).replace("'", "''");
      return MessageFormat.format(message, args);
   }
} public class NotesFileWriter {
   private static final Logger LOG = LoggerFactory.getLogger(NotesFileWriter.class);

   private final NotesWriter notesWriter;

   private NotesFileWriterResultHandler notesFileWriterResultHandler;

   public void setNotesFileWriterResultHandler(NotesFileWriterResultHandler notesFileWriterResultHandler) {
      this.notesFileWriterResultHandler = notesFileWriterResultHandler;
   }

   public NotesFileWriter(NotesWriter notesWriter) {
      this.notesWriter = notesWriter;
   }

   public boolean writeCategoriesToFile(List<Category> categories, String filePath) {
      boolean writtenSuccessfully = false;

      LOG.debug("writing notes to file...");

      try (FileOutputStream f = new FileOutputStream(filePath)) {
         writtenSuccessfully = notesWriter.writeCategoriesToOutputStream(categories, f);
         if (notesFileWriterResultHandler != null) {
            notesFileWriterResultHandler.onFileWrittenSuccessfully();
         }
      } catch (IOException e) {
         LOG.error("Exception when trying to write the notes to a file. File was NOT written.");
         if (notesFileWriterResultHandler != null) {
            notesFileWriterResultHandler.onFileWriteError();
         }
      }

      return writtenSuccessfully;
   }
}
 public final class NotesSaveTrigger implements CategoryChangedEventListener, NoteChangedEventListener, NoteAddedEventListener,
      NoteRemovedEventListener, CategoryAddedEventListener, CategoryRemovedEventListener {
   private static final Logger LOG = LoggerFactory.getLogger(NotesSaveTrigger.class);

   private static final int SAVE_DELAY_MILLIS = 5000;

   private FileSaveThread fileSaveThread;
   private boolean disabled;

   private final NotesFileWriter notesFileWriter;

   public NotesSaveTrigger(NotesFileWriter notesFileWriter) {
      super();
      this.notesFileWriter = notesFileWriter;
   }

   @Override
   public void noteChanged(NoteChangedEvent event) {
      LOG.debug("Received NoteChangedEvent.");
      triggerSave();
   }

   @Override
   public void categoryChanged(CategoryChangedEvent event) {
      LOG.debug("Received CategoryChangedEvent.");
      triggerSave();
   }

   @Override
   public void categoryRemoved(CategoryRemovedEvent event) {
      LOG.debug("Received CategoryRemovedEvent.");
      triggerSave();
   }

   @Override
   public void categoryAdded(CategoryAddedEvent event) {
      LOG.debug("Received CategoryAddedEvent.");
      triggerSave();
   }

   @Override
   public void noteRemoved(NoteRemovedEvent event) {
      LOG.debug("Received NoteRemovedEvent.");
      triggerSave();
   }

   @Override
   public void noteAdded(NoteAddedEvent event) {
      LOG.debug("Received NoteAddedEvent.");
      triggerSave();
   }

   private synchronized void triggerSave() {
      if (!disabled && (fileSaveThread == null || !fileSaveThread.isAlive())) {
         fileSaveThread = new FileSaveThread(notesFileWriter);
         fileSaveThread.start();
      }
   }

   public synchronized boolean isDisabled() {
      return disabled;
   }

   public synchronized void setDisabled(boolean disabled) {
      if (fileSaveThread != null && fileSaveThread.isAlive()) {
         try {
            fileSaveThread.join();
         } catch (InterruptedException e) {
            LOG.error("Error while waiting for notesfile save thread.", e);
         }
      }
      this.disabled = disabled;
   }

   private static class FileSaveThread extends Thread {
      private final NotesFileWriter notesFileWriter;

      public FileSaveThread(NotesFileWriter notesFileWriter) {
         this.notesFileWriter = notesFileWriter;
      }

      @Override
      public void run() {
         try {
            Thread.sleep(SAVE_DELAY_MILLIS);
            notesFileWriter.writeCategoriesToFile(CategoryManager.getInstance().getCategories(),
                  UserSettings.getInstance().getNotesFile());
         } catch (InterruptedException e) {
            LOG.error("Error while waiting for notesfile save thread.", e);
         }
      }
   }
} public interface ServerCommunicationResultHandler {
   void onSslError();

   void onUploadSuccess();

   void onUploadFailure();

   void onDownloadSuccess();

   void onDownloadFailure();
} public class ServerThread extends Thread {
   public enum Direction {
      UPLOAD,
      DOWNLOAD
   }

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

   private final Direction direction;
   private final NotesReader notesReader;
   private final NotesWriter notesWriter;
   private final NotesFileWriter notesFileWriter;
   private final NotesSaveTrigger notesSaveTrigger;
   private final String serverPassword;

   private final ServerCommunicationResultHandler serverCommunicationResultHandler;

   public ServerThread(Direction direction, NotesReader notesReader, NotesWriter notesWriter, NotesFileWriter notesFileWriter, NotesSaveTrigger notesSaveTrigger,
         ServerCommunicationResultHandler serverCommunicationResultHandler, String serverPassword) {
      super("Server Up-/Download Thread");
      this.direction = direction;
      this.notesReader = notesReader;
      this.notesWriter = notesWriter;
      this.notesFileWriter = notesFileWriter;
      this.notesSaveTrigger = notesSaveTrigger;
      this.serverCommunicationResultHandler = serverCommunicationResultHandler;
      this.serverPassword = serverPassword;
      this.start();
   }

   public void run() {
      if (direction == Direction.UPLOAD) {
         boolean uploadSuccessful;
         try {
            uploadSuccessful = ServerConnectionFactory.createServerConnection(
                  UserSettings.getInstance().getServerType(), serverPassword, notesReader, notesWriter)
                  .exportNotesToServer(CategoryManager.getInstance().getCategories());
         } catch (SSLHandshakeException e) {
            LOG.error("SSL exception occured. Probably a self-signed certificate?", e);
            serverCommunicationResultHandler.onSslError();
            uploadSuccessful = false;
         } catch (IOException | XMLStreamException e) {
            uploadSuccessful = false;
         }

         if (uploadSuccessful) {
            serverCommunicationResultHandler.onUploadSuccess();
         } else {
            serverCommunicationResultHandler.onUploadFailure();
         }
      } else {
         List<Category> categoriesFromServer;

         try {
            categoriesFromServer = ServerConnectionFactory.createServerConnection(
                  UserSettings.getInstance().getServerType(), serverPassword, notesReader, notesWriter)
                  .importCategoriesFromServer();
         } catch (SSLHandshakeException e) {
            // certificate error (self-signed?)
            serverCommunicationResultHandler.onSslError();
            categoriesFromServer = null;
         } catch (IOException e) {
            categoriesFromServer = null;
         }

         if (categoriesFromServer != null) {
            notesSaveTrigger.setDisabled(true);
            CategoryManager.getInstance().replaceWithNewCategories(categoriesFromServer);
            notesSaveTrigger.setDisabled(false);
            notesFileWriter.writeCategoriesToFile(CategoryManager.getInstance().getCategories(),
                  UserSettings.getInstance().getNotesFile());
            serverCommunicationResultHandler.onDownloadSuccess();
         } else {
            serverCommunicationResultHandler.onDownloadFailure();
         }
      }
   }
}
 public interface NotesReader {
   List<Category> readCategoriesFromInputStream(InputStream inputStream);

   boolean dataIsValid(InputStream inputStream);
}
 public interface NotesWriter {
   boolean writeCategoriesToOutputStream(List<Category> categories, OutputStream outputStream);
}
 public interface UpdateCheckResultHandler {
   void handleUpdateFound(String updateMessage);

   void handleNoUpdateFound();
} public class UpdateCheckThread extends Thread {
   private static final String UPDATE_URL = "http://pinemup.sourceforge.net/version.php?version="
         + CommonConfiguration.getApplicationVersion();
   private static final String UPDATE_DOCUMENT_ENCODING = "UTF-8";

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

   private final UpdateCheckResultHandler updateCheckResultHandler;

   public UpdateCheckThread(UpdateCheckResultHandler updateCheckResultHandler) {
      super("Update-Check Thread");
      this.updateCheckResultHandler = updateCheckResultHandler;
      this.start();
   }

   public void run() {
      try {
         URL url = new URL(UPDATE_URL);
         URLConnection urlc = url.openConnection();
         BufferedReader br = new BufferedReader(new InputStreamReader(urlc.getInputStream(), Charset.forName(UPDATE_DOCUMENT_ENCODING)
               .newDecoder()));

         String versionString = br.readLine();
         if (versionString != null && !versionString.equals(CommonConfiguration.getApplicationVersion())) {
            StringBuilder changelogString = new StringBuilder();
            changelogString.append("<html>");
            changelogString.append("<p>");
            changelogString.append(I18N.getInstance().getString("info.updateavailable.part1"));
            changelogString.append("</p>");
            changelogString.append("<p>");
            changelogString.append(I18N.getInstance().getString("info.updateavailable.part2", CommonConfiguration.getApplicationVersion()));
            changelogString.append("<br />");
            changelogString.append(I18N.getInstance().getString("info.updateavailable.part3", versionString));
            changelogString.append("</p>");
            changelogString.append("<p>");
            changelogString.append(I18N.getInstance().getString("info.updateavailable.part4",
                        "<a href=\"http://pinemup.sourceforge.net\">http://pinemup.sourceforge.net</a>"));
            changelogString.append("</p>");
            changelogString.append("<p>&nbsp;</p>");
            changelogString.append("<p>Changelog:<br />");
            changelogString.append("--------------------------------</p><p>");
            boolean firstList = true;
            String nextLine;
            do {
               nextLine = br.readLine();
               if (nextLine != null) {
                  if (nextLine.startsWith("-")) {
                     changelogString.append("<li>");
                     changelogString.append(nextLine.substring(2));
                     changelogString.append("</li>");
                  } else {
                     if (!firstList) {
                        changelogString.append("</ul>");
                     } else {
                        firstList = false;
                     }
                     changelogString.append(nextLine);
                     changelogString.append("<ul>");
                  }

               }
            } while (nextLine != null);
            changelogString.append("</p></html>");

            updateCheckResultHandler.handleUpdateFound(changelogString.toString());
         } else {
            updateCheckResultHandler.handleNoUpdateFound();
         }
         br.close();
      } catch (IOException e) {
         LOG.error("Error while trying to check for updates.", e);
      }

   }
} public final class UserSettings {
   private final Collection<UserSettingsChangedEventListener> userSettingsChangedEventListeners = new LinkedList<>();

   private static final String PREFIX = "peu_dev_";
   private static final int MIN_NOTEWINDOW_WIDTH = 30;
   private static final int MIN_NOTEWINDOW_HEIGHT = 30;

   private final Preferences prefs;

   private short defaultWindowWidth;
   private short defaultWindowHeight;
   private short defaultWindowXPosition;
   private short defaultWindowYPosition;
   private short defaultFontSize;
   private String notesFile;
   private String serverAddress;
   private String serverUser;
   private String serverPasswd;
   private String serverDir;
   private boolean defaultAlwaysOnTop;
   private byte tempDef = 0;
   private byte closeicon;
   private boolean showCategory;
   private boolean confirmDeletion;
   private boolean storeServerPass;
   private boolean updateCheckEnabled;
   private ConnectionType serverType;
   private Locale locale;
   private boolean confirmUpDownload;

   private static class Holder {
      private static final UserSettings INSTANCE = new UserSettings();
   }

   public static UserSettings getInstance() {
      return Holder.INSTANCE;
   }

   public void addUserSettingsChangedEventListener(UserSettingsChangedEventListener listener) {
      userSettingsChangedEventListeners.add(listener);
   }

   public void removeUserSettingsChangedEventListener(UserSettingsChangedEventListener listener) {
      userSettingsChangedEventListeners.remove(listener);
   }

   public boolean getConfirmUpDownload() {
      return confirmUpDownload;
   }

   public void setConfirmUpDownload(boolean b) {
      confirmUpDownload = b;
   }

   public void setServerType(ConnectionType st) {
      serverType = st;
   }

   public ConnectionType getServerType() {
      return serverType;
   }

   public void setUpdateCheckEnabled(boolean b) {
      updateCheckEnabled = b;
   }

   public boolean isUpdateCheckEnabled() {
      return updateCheckEnabled;
   }

   public void setConfirmDeletion(boolean b) {
      confirmDeletion = b;
   }

   public boolean getConfirmDeletion() {
      return confirmDeletion;
   }

   public void setShowCategory(boolean sc) {
      showCategory = sc;
   }

   public boolean getShowCategory() {
      return showCategory;
   }

   public byte getCloseIcon() {
      return closeicon;
   }

   public void setCloseIcon(byte ci) {
      closeicon = ci;
   }

   public String getNotesFile() {
      return notesFile;
   }

   public void setNotesFile(String s) {
      notesFile = s;
   }

   public boolean getDefaultAlwaysOnTop() {
      return defaultAlwaysOnTop;
   }

   public void setDefaultAlwaysOnTop(boolean b) {
      defaultAlwaysOnTop = b;
   }

   public short getDefaultFontSize() {
      return defaultFontSize;
   }

   public void setDefaultFontSize(short size) {
      defaultFontSize = size;
   }

   public byte getTempDef() {
      return tempDef;
   }

   public void setTempDef(byte td) {
      tempDef = td;
   }

   public short getDefaultWindowWidth() {
      return defaultWindowWidth;
   }

   public void setDefaultWindowWidth(short x) {
      if (x < MIN_NOTEWINDOW_WIDTH) {
         x = MIN_NOTEWINDOW_WIDTH;
      }
      defaultWindowWidth = x;
   }

   public short getDefaultWindowHeight() {
      return defaultWindowHeight;
   }

   public void setDefaultWindowHeight(short y) {
      if (y < MIN_NOTEWINDOW_HEIGHT) {
         y = MIN_NOTEWINDOW_HEIGHT;
      }
      defaultWindowHeight = y;
   }

   public short getDefaultWindowXPostition() {
      return defaultWindowXPosition;
   }

   public void setDefaultWindowXPosition(short x) {
      defaultWindowXPosition = x;
   }

   public short getDefaultWindowYPostition() {
      return defaultWindowYPosition;
   }

   public void setDefaultWindowYPosition(short y) {
      defaultWindowYPosition = y;
   }

   public String getServerAddress() {
      return serverAddress;
   }

   public void setServerAddress(String s) {
      serverAddress = s;
   }

   public String getServerUser() {
      return serverUser;
   }

   public void setServerUser(String u) {
      serverUser = u;
   }

   public String getServerPasswd() {
      return serverPasswd;
   }

   public void setServerPasswdFromCharArray(char[] passwdCharArray) {
      if (passwdCharArray != null) {
         serverPasswd = String.valueOf(passwdCharArray);
      } else {
         serverPasswd = "";
      }
   }

   public String getServerDir() {
      return serverDir;
   }

   public void setServerDir(String d) {
      serverDir = d;
   }

   public void setStoreServerPass(boolean b) {
      storeServerPass = b;
   }

   public boolean getStoreServerPass() {
      return storeServerPass;
   }

   public Locale getLocale() {
      return locale;
   }

   public void setLocale(Locale l) {
      locale = l;
   }

   public void saveSettings() {
      // save preferences
      prefs.putInt(PREFIX + "defaultWindowWidth", defaultWindowWidth);
      prefs.putInt(PREFIX + "defaultWindowHeight", defaultWindowHeight);
      prefs.putInt(PREFIX + "defaultWindowXPosition", defaultWindowXPosition);
      prefs.putInt(PREFIX + "defaultWindowYPosition", defaultWindowYPosition);
      prefs.putInt(PREFIX + "defaultFontSize", defaultFontSize);
      prefs.putBoolean(PREFIX + "defaultAlwaysOnTop", defaultAlwaysOnTop);
      prefs.put(PREFIX + "notesFile", notesFile);
      prefs.put(PREFIX + "serverAddress", serverAddress);
      prefs.put(PREFIX + "serverUser", serverUser);
      if (storeServerPass) {
         prefs.put(PREFIX + "serverPasswd", getServerPasswd());
      } else {
         prefs.put(PREFIX + "serverPasswd", "");
      }
      prefs.put(PREFIX + "serverDir", serverDir);
      prefs.putInt(PREFIX + "closeicon", closeicon);
      prefs.putBoolean(PREFIX + "showCategory", showCategory);
      prefs.putBoolean(PREFIX + "confirmDeletion", confirmDeletion);
      prefs.putBoolean(PREFIX + "storeServerPass", storeServerPass);
      prefs.putBoolean(PREFIX + "updateCheckEnabled", updateCheckEnabled);
      prefs.putInt(PREFIX + "serverType", serverType.getCode());
      prefs.put(PREFIX + "locale", locale.toString());
      prefs.putBoolean(PREFIX + "confirmUpDownload", confirmUpDownload);
   }

   private UserSettings() {
      prefs = Preferences.userNodeForPackage(UserSettings.class);

      String homeDir = System.getProperty("user.home");
      if (homeDir.charAt(homeDir.length() - 1) != '\\' && homeDir.charAt(homeDir.length() - 1) != '/') {
         homeDir = homeDir + "/";
      }

      defaultWindowWidth = Short.parseShort(prefs.get(PREFIX + "defaultWindowWidth", "170"));
      defaultWindowHeight = Short.parseShort(prefs.get(PREFIX + "defaultWindowHeight", "150"));
      defaultWindowXPosition = Short.parseShort(prefs.get(PREFIX + "defaultWindowXPosition", "0"));
      defaultWindowYPosition = Short.parseShort(prefs.get(PREFIX + "defaultWindowYPosition", "0"));
      defaultFontSize = Short.parseShort(prefs.get(PREFIX + "defaultFontSize", "14"));
      defaultAlwaysOnTop = prefs.getBoolean(PREFIX + "defaultAlwaysOnTop", false);
      notesFile = prefs.get(PREFIX + "notesFile", homeDir + "pinemup.xml");
      serverAddress = prefs.get(PREFIX + "serverAddress", "ftp.example.com");
      serverUser = prefs.get(PREFIX + "serverUser", "anonymous");
      serverPasswd = prefs.get(PREFIX + "serverPasswd", "");
      serverDir = prefs.get(PREFIX + "serverDir", "/");
      closeicon = Byte.parseByte(prefs.get(PREFIX + "closeicon", "1"));
      showCategory = prefs.getBoolean(PREFIX + "showCategory", false);
      confirmDeletion = prefs.getBoolean(PREFIX + "confirmDeletion", true);
      storeServerPass = prefs.getBoolean(PREFIX + "storeServerPass", false);
      updateCheckEnabled = prefs.getBoolean(PREFIX + "updateCheckEnabled", true);
      serverType = ConnectionType.getConnectionTypeByCode(Short.parseShort(prefs.get(PREFIX + "serverType", "0")));
      String localeString = prefs.get(PREFIX + "locale", "en_US");
      locale = SupportedLocale.fromLocaleString(localeString).getLocale();
      confirmUpDownload = prefs.getBoolean(PREFIX + "confirmUpDownload", true);
   }

   public void fireUserSettingsChangedEvent() {
      for (UserSettingsChangedEventListener listener : userSettingsChangedEventListeners) {
         listener.settingsChanged(new UserSettingsChangedEvent(this));
      }
   }
}
 public class DialogFactory {
   private UpdateCheckResultHandler updateCheckResultHandler;

   /** All of these dialogs must only exist once. */
   private SettingsDialog settingsDialogInstance;
   private CategoryDialog categoryDialogInstance;
   private AboutDialog aboutDialogInstance;
   private ExportDialog exportDialogInstance;

   private final NotesFileReader notesFileReader;
   private final NotesFileWriter notesFileWriter;
   private final NotesSaveTrigger notesSaveTrigger;

   public DialogFactory(UpdateCheckResultHandler updateCheckResultHandler, NotesFileReader notesFileReader,
         NotesFileWriter notesFileWriter, NotesSaveTrigger notesSaveTrigger) {
      this.updateCheckResultHandler = updateCheckResultHandler;
      this.notesFileReader = notesFileReader;
      this.notesFileWriter = notesFileWriter;
      this.notesSaveTrigger = notesSaveTrigger;
   }

   public void showSettingsDialog() {
      if (settingsDialogInstance == null || !settingsDialogInstance.isVisible()) {
         settingsDialogInstance = new SettingsDialog(updateCheckResultHandler,
               notesFileReader, notesFileWriter, notesSaveTrigger);
      }
   }

   public void showCategoryDialog() {
      if (categoryDialogInstance == null || !categoryDialogInstance.isVisible()) {
         categoryDialogInstance = new CategoryDialog();
      }
   }

   public void showAboutDialog() {
      if (aboutDialogInstance == null || !aboutDialogInstance.isVisible()) {
         aboutDialogInstance = new AboutDialog();
      }
   }

   public void showExportDialog() {
      if (exportDialogInstance == null || !exportDialogInstance.isVisible()) {
         exportDialogInstance = new ExportDialog();
      }
   }
} public final class DialogUtils {
   private static final int PASSWORD_FIELD_LENGTH = 12;

   private DialogUtils() {
      super();
   }

   public static String retrievePasswordFromUser() {
      JPasswordField p = new JPasswordField(PASSWORD_FIELD_LENGTH);
      JOptionPane.showMessageDialog(null, p, I18N.getInstance().getString("confirm.enterserverpassword"), JOptionPane.PLAIN_MESSAGE);
      return String.valueOf(p.getPassword());
   }

   public static File retieveFileChoiceFromUser() {
      File f = null;
      if (FileDialogCreator.getFileDialogInstance().showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
         f = FileDialogCreator.getFileDialogInstance().getSelectedFile();
      }
      return f;
   }

   public static boolean retrieveUserConfirmation(String title, String message) {
      return JOptionPane.showConfirmDialog(null, message, title, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
   }

   public static void showErrorMessageToUser(String title, String message) {
      JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
   }

   public static void showInfoMessageToUser(String title, String message) {
      JOptionPane.showMessageDialog(null, message, title, JOptionPane.INFORMATION_MESSAGE);
   }

   public static String makeSureNotesFileIsValid(String notesFilePath, NotesFileReader notesFileReader) {
      String selectedFilePath = notesFilePath;
      boolean selectedFileIsValid;

      do {
         selectedFileIsValid = notesFileReader.fileIsValid(selectedFilePath);

            if (!selectedFileIsValid) {
               if (!retrieveUserConfirmation(I18N.getInstance().getString("error.title"),
                     I18N.getInstance().getString("error.notesfilenotvalid"))) {
                  System.exit(0);
               }

               File userFileChoice = retieveFileChoiceFromUser();
               if (userFileChoice != null) {
                  selectedFilePath = FileUtils.checkAndAddExtension(userFileChoice.getAbsolutePath(), ".xml");
               }
            }
       } while (!selectedFileIsValid);

      return selectedFilePath;
   }
}
import net.sourceforge.pinemup.core.CategoryManager;
import net.sourceforge.pinemup.core.i18n.I18N;
import net.sourceforge.pinemup.core.io.notes.file.NotesFileWriter;
import net.sourceforge.pinemup.core.io.notes.file.NotesSaveTrigger;
import net.sourceforge.pinemup.core.io.notes.server.ServerCommunicationResultHandler;
import net.sourceforge.pinemup.core.io.notes.server.ServerThread;
import net.sourceforge.pinemup.core.io.notes.stream.NotesReader;
import net.sourceforge.pinemup.core.io.notes.stream.NotesWriter;
import net.sourceforge.pinemup.core.io.updatecheck.UpdateCheckResultHandler;
import net.sourceforge.pinemup.core.io.updatecheck.UpdateCheckThread;
import net.sourceforge.pinemup.core.settings.UserSettings;
import net.sourceforge.pinemup.ui.swing.dialogs.DialogFactory;
import net.sourceforge.pinemup.ui.swing.utils.DialogUtils;
import javax.swing.JOptionPane;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
package net.sourceforge.pinemup.ui.swing.tray;



public class TrayMenuLogic implements ActionListener {
   public static final String ACTION_SHOW_ABOUT_DIALOG = "SHOW_ABOUT_DIALOG";
   public static final String ACTION_SHOW_SETTINGS_DIALOG = "SHOW_SETTINGS_DIALOG";
   public static final String ACTION_EXIT_APPLICATION = "EXIT_APPLICATION";
   public static final String ACTION_UPLOAD_TO_SERVER = "UPLOAD_TO_SERVER";
   public static final String ACTION_DOWNLOAD_FROM_SERVER = "DOWNLOAD_FROM_SERVER";
   public static final String ACTION_EXPORT = "EXPORT";
   public static final String ACTION_MANAGE_CATEGORIES = "MANAGE_CATEGORIES";
   public static final String ACTION_CHECK_FOR_UPDATES = "CHECK_FOR_UPDATES";

   private final DialogFactory dialogFactory;
   private final UpdateCheckResultHandler updateCheckResultHandler;
   private final NotesReader notesReader;
   private final NotesWriter notesWriter;
   private final NotesFileWriter notesFileWriter;
   private final NotesSaveTrigger notesSaveTrigger;

   public TrayMenuLogic(DialogFactory dialogFactory, UpdateCheckResultHandler updateCheckResultHandler, NotesReader notesReader,
         NotesWriter notesWriter, NotesFileWriter notesFileWriter, NotesSaveTrigger notesSaveTrigger) {
      super();
      this.dialogFactory = dialogFactory;
      this.updateCheckResultHandler = updateCheckResultHandler;
      this.notesReader = notesReader;
      this.notesWriter = notesWriter;
      this.notesFileWriter = notesFileWriter;
      this.notesSaveTrigger = notesSaveTrigger;
   }

   @Override
   public void actionPerformed(ActionEvent e) {
      switch (e.getActionCommand()) {
      case ACTION_SHOW_ABOUT_DIALOG:
         dialogFactory.showAboutDialog();
         break;
      case ACTION_SHOW_SETTINGS_DIALOG:
         dialogFactory.showSettingsDialog();
         break;
      case ACTION_EXIT_APPLICATION:
         // save notes to xml and exit
         writeCategoriesToFile();
         System.exit(0);
      case ACTION_UPLOAD_TO_SERVER:
         if (!UserSettings.getInstance().getConfirmUpDownload()
               || JOptionPane.showConfirmDialog(null, I18N.getInstance().getString("confirm.replacefileonserver"), I18N.getInstance()
               .getString("confirm.title"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
            // save notes to xml
            writeCategoriesToFile();

            String serverPassword = getServerPassword();

            // copy xml to server
            new ServerThread(ServerThread.Direction.UPLOAD, notesReader, notesWriter, notesFileWriter, notesSaveTrigger,
                  new SwingServerCommunicationResultHandler(), serverPassword);
         }
         break;
      case ACTION_DOWNLOAD_FROM_SERVER:
         if (!UserSettings.getInstance().getConfirmUpDownload()
               || JOptionPane.showConfirmDialog(null, I18N.getInstance().getString("confirm.replacelocalfile"), I18N.getInstance()
               .getString("confirm.title"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {

            String serverPassword = getServerPassword();

            new ServerThread(ServerThread.Direction.DOWNLOAD, notesReader, notesWriter, notesFileWriter, notesSaveTrigger,
                  new SwingServerCommunicationResultHandler(), serverPassword);
         }
         break;
      case ACTION_EXPORT:
         dialogFactory.showExportDialog();
         break;
      case ACTION_MANAGE_CATEGORIES:
         dialogFactory.showCategoryDialog();
         break;
      case ACTION_CHECK_FOR_UPDATES:
         new UpdateCheckThread(updateCheckResultHandler);
         break;
      default:
         // unknown action, do nothing
         break;
      }
   }

   private void writeCategoriesToFile() {
      notesFileWriter.writeCategoriesToFile(CategoryManager.getInstance().getCategories(),