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 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 class NotesFileReader {
   private static final Logger LOG = LoggerFactory.getLogger(NotesFileReader.class);

   private final NotesReader notesReader;

   public NotesFileReader(NotesReader notesReader) {
      this.notesReader = notesReader;
   }

   public List<Category> readCategoriesFromFile(String filePath) {
      List<Category> categories = new LinkedList<>();

      if (new File(filePath).exists()) {
         try (InputStream in = new FileInputStream(filePath)) {
            categories = notesReader.readCategoriesFromInputStream(in);
         } catch (IOException e) {
            LOG.error("Error during attempt to read notes from xml.", e);
         }
      } else {
         // create default categories
         categories.add(new Category("Home", true, NoteColor.YELLOW));
         categories.add(new Category("Office", false, NoteColor.GREEN));
      }

      return categories;
   }

   public boolean fileIsValid(String filePath) {
      boolean fileIsValid;

      File file = new File(filePath);

      if (file.exists()) {
         try (InputStream inputStream = new FileInputStream(file)) {
            fileIsValid = notesReader.dataIsValid(inputStream);
         } catch (IOException e) {
            fileIsValid = false;
         }
      } else {
         fileIsValid = true;
      }

      return fileIsValid;
   }
}
 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 interface NotesReader {
   List<Category> readCategoriesFromInputStream(InputStream inputStream);

   boolean dataIsValid(InputStream inputStream);
}
 public interface NotesWriter {
   boolean writeCategoriesToOutputStream(List<Category> categories, OutputStream outputStream);
}
 public class NotesXmlReader implements NotesReader {
   private static final Logger LOG = LoggerFactory.getLogger(NotesXmlReader.class);

   @Override
   public List<Category> readCategoriesFromInputStream(InputStream inputStream) {
      List<Category> categories = new LinkedList<>();

      XMLStreamReader parser = null;
      try {
         XMLInputFactory myFactory = XMLInputFactory.newInstance();
         parser = myFactory.createXMLStreamReader(inputStream, NotesXmlMetaData.NOTESFILE_ENCODING);

         Category currentCategory = null;
         Note currentNote = null;
         boolean defaultCategoryAdded = false;

         while (parser.hasNext()) {
            int event = parser.next();
            switch (event) {
            case XMLStreamConstants.START_ELEMENT:
               switch (parser.getLocalName()) {
               case "category":
                  currentCategory = handleCategory(parser);
                  if (currentCategory.isDefaultCategory()) {
                     if (defaultCategoryAdded) {
                        // there must be only one default category
                        currentCategory.setDefault(false);
                     } else {
                        defaultCategoryAdded = true;
                     }
                  }
                  categories.add(currentCategory);
                  break;
               case "note":
                  currentNote = handleNote(parser);
                  if (currentCategory != null) {
                     currentCategory.addNote(currentNote);
                  }
                  break;
               case "text":
                  for (int i = 0; i < parser.getAttributeCount(); i++) {
                     if (parser.getAttributeLocalName(i).equals("size")) {
                        short fontSize = Short.parseShort(parser.getAttributeValue(i));
                        currentNote.setFontSize(fontSize);
                     }
                  }
                  break;
               case "newline":
                  currentNote.setText(currentNote.getText() + "\n");
                  break;
               default:
                  // unkown element name, do nothing
                  break;
               }
               break;
            case XMLStreamConstants.CHARACTERS:
               if (!parser.isWhiteSpace()) {
                  if (currentNote != null) {
                     String str = parser.getText();
                     currentNote.setText(currentNote.getText() + str);
                  }
               }
               break;
            default:
               // do nothing for all other listeners
               break;
            }
         }
      } catch (XMLStreamException e) {
         categories = null;
      } finally {
         if (parser != null) {
            try {
               parser.close();
            } catch (XMLStreamException e) {
               LOG.error("Error during attempt to close stream.", e);
            }
         }
      }

      return categories;
   }

   private Category handleCategory(XMLStreamReader parser) {
      String name = "";
      boolean isDefaultCategory = false;
      NoteColor defNoteColor = NoteColor.DEFAULT_COLOR;
      for (int i = 0; i < parser.getAttributeCount(); i++) {
         if (parser.getAttributeLocalName(i).equals("name")) {
            name = parser.getAttributeValue(i);
         } else if (parser.getAttributeLocalName(i).equals("default")) {
            isDefaultCategory = (parser.getAttributeValue(i).equals("true"));
         } else if (parser.getAttributeLocalName(i).equals("defaultnotecolor")) {
            defNoteColor = NoteColor.getNoteColorByCode(Byte.parseByte(parser.getAttributeValue(i)));
         }
      }
      return new Category(name, isDefaultCategory, defNoteColor);
   }

   private Note handleNote(XMLStreamReader parser) {
      Note currentNote = new Note();

      for (int i = 0; i < parser.getAttributeCount(); i++) {
         if (parser.getAttributeLocalName(i).equals("hidden")) {
            boolean h = parser.getAttributeValue(i).equals("true");
            currentNote.setHidden(h);
         } else if (parser.getAttributeLocalName(i).equals("xposition")) {
            short x = Short.parseShort(parser.getAttributeValue(i));
            currentNote.setPosition(x, currentNote.getYPos());
         } else if (parser.getAttributeLocalName(i).equals("yposition")) {
            short y = Short.parseShort(parser.getAttributeValue(i));
            currentNote.setPosition(currentNote.getXPos(), y);
         } else if (parser.getAttributeLocalName(i).equals("width")) {
            short w = Short.parseShort(parser.getAttributeValue(i));
            currentNote.setSize(w, currentNote.getYSize());
         } else if (parser.getAttributeLocalName(i).equals("height")) {
            short h = Short.parseShort(parser.getAttributeValue(i));
            currentNote.setSize(currentNote.getXSize(), h);
         } else if (parser.getAttributeLocalName(i).equals("alwaysontop")) {
            boolean a = parser.getAttributeValue(i).equals("true");
            currentNote.setAlwaysOnTop(a);
         } else if (parser.getAttributeLocalName(i).equals("color")) {
            NoteColor color = NoteColor.getNoteColorByCode(Byte.parseByte(parser.getAttributeValue(i)));
            currentNote.setColor(color);
         }
      }

      return currentNote;
   }

   @Override
   public boolean dataIsValid(InputStream inputStream) {
      try {
         SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
         URL schemaLocation = ResourceLoader.getInstance().getSchemaFile(NotesXmlMetaData.LATEST_NOTESFILE_VERSION);
         Schema schema = factory.newSchema(schemaLocation);
         Validator validator = schema.newValidator();
         Source source = new StreamSource(inputStream);
         validator.validate(source);

         return true;
      } catch (IOException e) {
         return false;
      } catch (SAXException e) {
         return false;
      }
   }
} public class NotesXmlWriter implements NotesWriter {
   private static final Logger LOG = LoggerFactory.getLogger(NotesXmlWriter.class);

   public NotesXmlWriter() {
      super();
   }

   @Override
   public boolean writeCategoriesToOutputStream(List<Category> categories, OutputStream out) {
      boolean writtenSuccessfully = true;

      XMLStreamWriter writer = null;
      try {
         XMLOutputFactory myFactory = XMLOutputFactory.newInstance();
         writer = myFactory.createXMLStreamWriter(out, NotesXmlMetaData.NOTESFILE_ENCODING);

         writer.writeStartDocument(NotesXmlMetaData.NOTESFILE_ENCODING, "1.0");
         writer.writeStartElement("notesfile");
         writer.writeAttribute("version", NotesXmlMetaData.LATEST_NOTESFILE_VERSION);

         for (Category cat : categories) {
            writer.writeStartElement("category");
            writer.writeAttribute("name", cat.getName());
            writer.writeAttribute("default", String.valueOf(cat.isDefaultCategory()));
            writer.writeAttribute("defaultnotecolor", String.valueOf(cat.getDefaultNoteColor().getCode()));

            for (Note n : cat.getNotes()) {
               writer.writeStartElement("note");
               writer.writeAttribute("hidden", String.valueOf(n.isHidden()));
               writer.writeAttribute("alwaysontop", String.valueOf(n.isAlwaysOnTop()));
               writer.writeAttribute("xposition", String.valueOf(n.getXPos()));
               writer.writeAttribute("yposition", String.valueOf(n.getYPos()));
               writer.writeAttribute("width", String.valueOf(n.getXSize()));
               writer.writeAttribute("height", String.valueOf(n.getYSize()));
               writer.writeAttribute("color", String.valueOf(n.getColor().getCode()));
               writer.writeStartElement("text");
               writer.writeAttribute("size", String.valueOf(n.getFontSize()));
               String noteText = n.getText();
               String[] textParts = noteText.split("\n");
               for (int i = 0; i < textParts.length; i++) {
                  writer.writeCharacters(textParts[i]);
                  if (i < textParts.length - 1) {
                     writer.writeEmptyElement("newline");
                  }
               }
               // newlines at the end
               while (noteText.endsWith("\n")) {
                  writer.writeEmptyElement("newline");
                  noteText = noteText.substring(0, noteText.length() - 1);
               }
               writer.writeEndElement();
               writer.writeEndElement();
            }
            writer.writeEndElement();
         }
         writer.writeEndElement();
         writer.writeEndDocument();
      } catch (XMLStreamException e) {
         writtenSuccessfully  = false;
      } finally {
         if (writer != null) {
            try {
               writer.close();
            } catch (XMLStreamException e) {
               LOG.error("Error during attempt to close stream.", e);
            }
         }
      }

      return writtenSuccessfully;
   }
} 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 interface PinEmUpUi {
   void initialize();

   String makeSureNotesFileIsValid(String notesFilePath);
} public final class SwingUI implements PinEmUpUi {
   private static final Logger LOG = LoggerFactory.getLogger(SwingUI.class);

   private NotesReader notesReader;

   private NotesWriter notesWriter;

   private NotesFileReader notesFileReader;

   private NotesFileWriter notesFileWriter;

   private NotesSaveTrigger notesSaveTrigger;

   public SwingUI(NotesReader notesReader, NotesWriter notesWriter, NotesFileReader notesFileReader, NotesFileWriter notesFileWriter, NotesSaveTrigger notesSaveTrigger) {
      super();
      this.notesReader = notesReader;
      this.notesWriter = notesWriter;
      this.notesFileReader = notesFileReader;
      this.notesFileWriter = notesFileWriter;
      this.notesSaveTrigger = notesSaveTrigger;
   }

   @Override
   public void initialize() {
      if (SystemTray.isSupported()) {
         // add trayicon
         SystemTray tray = SystemTray.getSystemTray();
         try {
            UpdateCheckResultHandler updateCheckResultHandler = new SwingUpdateCheckResultHandler(true);
            DialogFactory dialogFactory = new DialogFactory(updateCheckResultHandler,
                  notesFileReader, notesFileWriter, notesSaveTrigger);

            NoteWindowManager noteWindowManager = new NoteWindowManager();

            notesFileWriter.setNotesFileWriterResultHandler(new NotesFileWriterResultHandler() {
               @Override
               public void onFileWrittenSuccessfully() {
                  // do nothing
               }

               @Override
               public void onFileWriteError() {
                  DialogUtils.showErrorMessageToUser(I18N.getInstance().getString("error.title"),
                        I18N.getInstance().getString("error.notesfilenotsaved"));
               }
            });

            TrayMenuLogic trayMenuLogic = new TrayMenuLogic(dialogFactory, updateCheckResultHandler,
                  notesReader, notesWriter, notesFileWriter, notesSaveTrigger);
            TrayMenu trayMenu = new TrayMenu(trayMenuLogic);
            tray.add(new PinEmUpTrayIcon(trayMenu, noteWindowManager));

            TrayMenuUpdater trayMenuUpdater = new TrayMenuUpdater(trayMenu);
            CategoryManager.getInstance().registerDefaultCategoryChangedEventListener(trayMenuUpdater);
            CategoryManager.getInstance().registerDefaultCategoryAddedEventListener(trayMenuUpdater);
            CategoryManager.getInstance().registerDefaultCategoryRemovedEventListener(trayMenuUpdater);
            UserSettings.getInstance().addUserSettingsChangedEventListener(trayMenuUpdater);

            CategoryManager.getInstance().registerDefaultNoteChangedEventListener(noteWindowManager);
            CategoryManager.getInstance().registerDefaultNoteAddedEventListener(noteWindowManager);
            CategoryManager.getInstance().registerDefaultNoteRemovedEventListener(noteWindowManager);
         } catch (AWTException e) {
            LOG.error("Error during initialization of tray icon.", e);
         }
      } else {
         // tray icon not supported
         JOptionPane.showMessageDialog(null, I18N.getInstance().getString("error.trayiconnotsupported"),
               I18N.getInstance().getString("error.title"), JOptionPane.ERROR_MESSAGE);
         System.exit(1);
      }
   }

   @Override
   public String makeSureNotesFileIsValid(String notesFilePath) {
      return DialogUtils.makeSureNotesFileIsValid(notesFilePath, notesFileReader);
   }
} public class SwingUpdateCheckResultHandler implements UpdateCheckResultHandler {
   private final boolean showUpToDateMessage;

   public SwingUpdateCheckResultHandler(boolean showUpToDateMessage) {
      super();
      this.showUpToDateMessage = showUpToDateMessage;
   }

   @Override
   public void handleUpdateFound(String updateMessage) {
      new UpdateDialog(updateMessage);
   }

   @Override
   public void handleNoUpdateFound() {
      if (showUpToDateMessage) {
         DialogUtils.showInfoMessageToUser(I18N.getInstance().getString("info.title"),
               I18N.getInstance().getString("info.versionuptodate"));
      }
   }
}
import net.sourceforge.pinemup.core.io.updatecheck.UpdateCheckThread;
import net.sourceforge.pinemup.core.settings.UserSettings;
import net.sourceforge.pinemup.ui.PinEmUpUi;
import net.sourceforge.pinemup.ui.swing.SwingUI;
import net.sourceforge.pinemup.ui.swing.SwingUpdateCheckResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sourceforge.pinemup.core.CategoryManager;
import net.sourceforge.pinemup.core.i18n.I18N;
import net.sourceforge.pinemup.core.io.notes.file.NotesSaveTrigger;
import net.sourceforge.pinemup.core.io.notes.file.NotesFileReader;
import net.sourceforge.pinemup.core.io.notes.file.NotesFileWriter;
import net.sourceforge.pinemup.core.io.notes.stream.NotesReader;
import net.sourceforge.pinemup.core.io.notes.stream.NotesWriter;
import net.sourceforge.pinemup.core.io.notes.stream.xml.NotesXmlReader;
import net.sourceforge.pinemup.core.io.notes.stream.xml.NotesXmlWriter;

/*
 * pin 'em up
 *
 * Copyright (C) 2007-2013 by Mario Ködding
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package net.sourceforge.pinemup;


public final class PinEmUp {
   private static final int STARTUP_SLEEP_TIME = 1000;

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

   private PinEmUp() {
      super();
   }

   public static void main(String[] args) {
      // wait for a moment for SystemTray to be initialized (to prevent problems
      // with autostart on some systems)
      try {
         LOG.info("pin 'em up is starting up. Waiting a few seconds before initializing the tray icon...");
         Thread.sleep(STARTUP_SLEEP_TIME);
      } catch (InterruptedException e) {
         LOG.error("Startup sleep time has been interrupted.", e);
      }

      // set locale
      I18N.getInstance().setLocale(UserSettings.getInstance().getLocale());

      // initialize IO
      NotesReader notesReader = new NotesXmlReader();
      NotesWriter notesWriter = new NotesXmlWriter();
      NotesFileReader notesFileReader = new NotesFileReader(notesReader);
      NotesFileWriter notesFileWriter = new NotesFileWriter(notesWriter);
      NotesSaveTrigger notesSaveTrigger = new NotesSaveTrigger(notesFileWriter);

      // initialize UI
      PinEmUpUi pinEmUpUi = new SwingUI(notesReader, notesWriter, notesFileReader, notesFileWriter, notesSaveTrigger);
      pinEmUpUi.initialize();

      // make sure the currently saved notesfile is valid
      UserSettings.getInstance().setNotesFile(
            pinEmUpUi.makeSureNotesFileIsValid(UserSettings.getInstance().getNotesFile()));

      // add NotesFileSaveTrigger as default listeners for notes / categories
      CategoryManager.getInstance().registerDefaultCategoryChangedEventListener(notesSaveTrigger);
      CategoryManager.getInstance().registerDefaultCategoryAddedEventListener(notesSaveTrigger);
      CategoryManager.getInstance().registerDefaultCategoryRemovedEventListener(notesSaveTrigger);
      CategoryManager.getInstance().registerDefaultNoteChangedEventListener(notesSaveTrigger);
      CategoryManager.getInstance().registerDefaultNoteAddedEventListener(notesSaveTrigger);
      CategoryManager.getInstance().registerDefaultNoteRemovedEventListener(notesSaveTrigger);

      // load notes from xml
      CategoryManager.getInstance().replaceWithNewCategories(
            notesFileReader.readCategoriesFromFile(UserSettings.getInstance().getNotesFile()));

      // update check
      if (UserSettings.getInstance().isUpdateCheckEnabled()) {

         new UpdateCheckThread(new SwingUpdateCheckResultHandler(false));
