Java tutorial
/* * Lilith - a log event viewer. * Copyright (C) 2007-2017 Joern Huxhorn * * 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 de.huxhorn.lilith.swing; import de.huxhorn.lilith.Lilith; import de.huxhorn.lilith.LilithSounds; import de.huxhorn.lilith.conditions.CallLocationCondition; import de.huxhorn.lilith.conditions.EventContainsCondition; import de.huxhorn.lilith.conditions.FormattedMessageContainsCondition; import de.huxhorn.lilith.conditions.FormattedMessageEqualsCondition; import de.huxhorn.lilith.conditions.GroovyCondition; import de.huxhorn.lilith.conditions.LevelCondition; import de.huxhorn.lilith.conditions.LilithCondition; import de.huxhorn.lilith.conditions.LoggerContainsCondition; import de.huxhorn.lilith.conditions.LoggerEqualsCondition; import de.huxhorn.lilith.conditions.LoggerStartsWithCondition; import de.huxhorn.lilith.conditions.MessagePatternContainsCondition; import de.huxhorn.lilith.conditions.MessagePatternEqualsCondition; import de.huxhorn.lilith.conditions.ThreadGroupNameCondition; import de.huxhorn.lilith.conditions.ThreadNameCondition; import de.huxhorn.lilith.conditions.ThrowableCondition; import de.huxhorn.lilith.data.access.HttpStatus; import de.huxhorn.lilith.data.logging.LoggingEvent; import de.huxhorn.lilith.prefs.LilithPreferences; import de.huxhorn.lilith.swing.filefilters.GroovyConditionFileFilter; import de.huxhorn.lilith.swing.preferences.SavedCondition; import de.huxhorn.lilith.swing.table.ColorScheme; import de.huxhorn.sulky.conditions.Condition; import de.huxhorn.sulky.io.IOUtilities; import de.huxhorn.sulky.swing.PersistentTableColumnModel; import java.awt.Color; import java.awt.event.ActionEvent; import java.beans.Encoder; import java.beans.Expression; import java.beans.PersistenceDelegate; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import java.util.stream.Collectors; import javax.swing.LookAndFeel; import javax.swing.UIManager; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ApplicationPreferences { private static final Preferences PREFERENCES = Preferences.userNodeForPackage(ApplicationPreferences.class); private static final int MAX_PREV_SEARCHES = 15; private static final int MAX_RECENT_FILES = 15; private static final String PREVIOUS_SEARCH_STRINGS_XML_FILENAME = "previousSearchStrings.xml"; private static final String RECENT_FILES_XML_FILENAME = "recentFiles.xml"; public static final String STATUS_COLORS_XML_FILENAME = "statusColors.xml"; public static final String LEVEL_COLORS_XML_FILENAME = "levelColors.xml"; private static final String DETAILS_VIEW_ROOT_FOLDER = "detailsView"; public static final String DETAILS_VIEW_CSS_FILENAME = "detailsView.css"; public static final String DETAILS_VIEW_GROOVY_FILENAME = "detailsView.groovy"; public static final String CONDITIONS_XML_FILENAME = "savedConditions.xml"; public static final String STATUS_COLORS_PROPERTY = "statusColors"; public static final String LEVEL_COLORS_PROPERTY = "levelColors"; public static final String LOOK_AND_FEEL_PROPERTY = "lookAndFeel"; public static final String CLEANING_LOGS_ON_EXIT_PROPERTY = "cleaningLogsOnExit"; public static final String COLORING_WHOLE_ROW_PROPERTY = "coloringWholeRow"; public static final String SHOWING_TOOLBAR_PROPERTY = "showingToolbar"; public static final String SHOWING_STATUSBAR_PROPERTY = "showingStatusbar"; public static final String SHOWING_PRIMARY_IDENTIFIER_PROPERTY = "showingPrimaryIdentifier"; public static final String SHOWING_SECONDARY_IDENTIFIER_PROPERTY = "showingSecondaryIdentifier"; public static final String SHOWING_FULL_CALLSTACK_PROPERTY = "showingFullCallstack"; public static final String USING_WRAPPED_EXCEPTION_STYLE_PROPERTY = "usingWrappedExceptionStyle"; public static final String SHOWING_STACKTRACE_PROPERTY = "showingStackTrace"; public static final String CHECKING_FOR_UPDATE_PROPERTY = "checkingForUpdate"; public static final String CHECKING_FOR_SNAPSHOT_PROPERTY = "checkingForSnapshot"; public static final String SOURCE_FILTERING_PROPERTY = "sourceFiltering"; public static final String SOUND_LOCATIONS_PROPERTY = "soundLocations"; public static final String SCALE_FACTOR_PROPERTY = "scaleFactor"; public static final String MUTE_PROPERTY = "mute"; public static final String USING_INTERNAL_FRAMES_PROPERTY = "usingInternalFrames"; public static final String SCROLLING_TO_BOTTOM_PROPERTY = "scrollingToBottom"; public static final String SOURCE_NAMES_PROPERTY = "sourceNames"; public static final String APPLICATION_PATH_PROPERTY = "applicationPath"; public static final String TRAY_ACTIVE_PROPERTY = "trayActive"; public static final String HIDING_ON_CLOSE_PROPERTY = "hidingOnClose"; public static final String AUTO_OPENING_PROPERTY = "autoOpening"; public static final String AUTO_CLOSING_PROPERTY = "autoClosing"; public static final String IMAGE_PATH_PROPERTY = "imagePath"; public static final String SOUND_PATH_PROPERTY = "soundPath"; public static final String AUTO_FOCUSING_WINDOW_PROPERTY = "autoFocusingWindow"; public static final String SOURCE_LISTS_PROPERTY = "sourceLists"; public static final String BLACK_LIST_NAME_PROPERTY = "blackListName"; public static final String WHITE_LIST_NAME_PROPERTY = "whiteListName"; public static final String CONDITIONS_PROPERTY = "conditions"; public static final String SPLASH_SCREEN_DISABLED_PROPERTY = "splashScreenDisabled"; public static final String ASKING_BEFORE_QUIT_PROPERTY = "askingBeforeQuit"; public static final String CURRENT_TIP_OF_THE_DAY_PROPERTY = "currentTipOfTheDay"; public static final String SHOWING_TIP_OF_THE_DAY_PROPERTY = "showingTipOfTheDay"; public static final String MAXIMIZING_INTERNAL_FRAMES_PROPERTY = "maximizingInternalFrames"; public static final String GLOBAL_LOGGING_ENABLED_PROPERTY = "globalLoggingEnabled"; public static final String PREVIOUS_SEARCH_STRINGS_PROPERTY = "previousSearchStrings"; public static final String RECENT_FILES_PROPERTY = "recentFiles"; public static final String SHOWING_FULL_RECENT_PATH_PROPERTY = "showingFullRecentPath"; public static final String DEFAULT_CONDITION_NAME_PROPERTY = "defaultConditionName"; public static final String LOGGING_LAYOUT_GLOBAL_XML_FILENAME = "loggingLayoutGlobal.xml"; public static final String LOGGING_LAYOUT_XML_FILENAME = "loggingLayout.xml"; public static final String ACCESS_LAYOUT_GLOBAL_XML_FILENAME = "accessLayoutGlobal.xml"; public static final String ACCESS_LAYOUT_XML_FILENAME = "accessLayout.xml"; public static final String SOURCE_NAMES_XML_FILENAME = "SourceNames.xml"; public static final String SOURCE_LISTS_XML_FILENAME = "SourceLists.xml"; public static final String SOURCE_NAMES_PROPERTIES_FILENAME = "SourceNames.properties"; public static final String SOUND_LOCATIONS_XML_FILENAME = "SoundLocations.xml"; //public static final String SOUND_LOCATIONS_PROPERTIES_FILENAME = "SoundLocations.properties"; public static final String PREVIOUS_APPLICATION_PATH_FILENAME = ".previous.application.path"; private static final String OLD_LICENSED_PREFERENCES_KEY = "licensed"; private static final String LICENSED_PREFERENCES_KEY = "licensedVersion"; public static final String USER_HOME; public static final String DEFAULT_APPLICATION_PATH; private static final Map<String, String> DEFAULT_SOURCE_NAMES; private static final Map<String, String> DEFAULT_SOUND_LOCATIONS; private static final Map<LoggingEvent.Level, ColorScheme> DEFAULT_LEVEL_COLOR_SCHEMES; private static final Map<HttpStatus.Type, ColorScheme> DEFAULT_STATUS_COLOR_SCHEMES; private static final String PREVIOUS_OPEN_PATH_PROPERTY = "previousOpenPath"; private static final String PREVIOUS_IMPORT_PATH_PROPERTY = "previousImportPath"; private static final String PREVIOUS_EXPORT_PATH_PROPERTY = "previousExportPath"; public static final String STARTUP_LOOK_AND_FEEL; private static final long CONDITIONS_CHECK_INTERVAL = 30000; private static final String GROOVY_SUFFIX = ".groovy"; private static final String EXAMPLE_GROOVY_CONDITIONS_BASE = "/conditions/"; private static final String EXAMPLE_GROOVY_CLIPBOARD_FORMATTERS_BASE = "/clipboardFormatters/"; private static final String GROOVY_EXAMPLE_LIST = "list.txt"; public static final String SAVED_CONDITION = "Saved"; private static final String[] DEFAULT_CONDITIONS = new String[] { EventContainsCondition.DESCRIPTION, LevelCondition.DESCRIPTION, FormattedMessageContainsCondition.DESCRIPTION, FormattedMessageEqualsCondition.DESCRIPTION, MessagePatternContainsCondition.DESCRIPTION, MessagePatternEqualsCondition.DESCRIPTION, LoggerStartsWithCondition.DESCRIPTION, LoggerContainsCondition.DESCRIPTION, LoggerEqualsCondition.DESCRIPTION, CallLocationCondition.DESCRIPTION, ThrowableCondition.DESCRIPTION, ThreadNameCondition.DESCRIPTION, ThreadGroupNameCondition.DESCRIPTION, SAVED_CONDITION, }; private static final String[] LEVEL_VALUES = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", }; private String[] clipboardFormatterScriptFiles; private long lastClipboardFormatterCheck; private static final LilithPreferences DEFAULT_VALUES = new LilithPreferences(); static { PREFERENCES.remove(OLD_LICENSED_PREFERENCES_KEY); // remove garbage USER_HOME = System.getProperty("user.home"); File defaultAppPath = new File(USER_HOME, ".lilith"); DEFAULT_APPLICATION_PATH = defaultAppPath.getAbsolutePath(); Map<String, String> defaultSoundLocations = new HashMap<>(); defaultSoundLocations.put(LilithSounds.SOURCE_ADDED, "/events/SourceAdded.mp3"); defaultSoundLocations.put(LilithSounds.SOURCE_REMOVED, "/events/SourceRemoved.mp3"); defaultSoundLocations.put(LilithSounds.ERROR_EVENT_ALARM, "/events/ErrorEventAlarm.mp3"); defaultSoundLocations.put(LilithSounds.WARN_EVENT_ALARM, "/events/WarnEventAlarm.mp3"); DEFAULT_SOUND_LOCATIONS = Collections.unmodifiableMap(defaultSoundLocations); Map<String, String> defaultSourceNames = new HashMap<>(); defaultSourceNames.put("127.0.0.1", "Localhost"); DEFAULT_SOURCE_NAMES = Collections.unmodifiableMap(defaultSourceNames); Map<LoggingEvent.Level, ColorScheme> defaultLevelColors = new HashMap<>(); defaultLevelColors.put(LoggingEvent.Level.TRACE, new ColorScheme(new Color(0x1F, 0x44, 0x58), new Color(0x80, 0xBA, 0xD9), new Color(0x80, 0xBA, 0xD9))); defaultLevelColors.put(LoggingEvent.Level.DEBUG, new ColorScheme(Color.BLACK, Color.GREEN, Color.GREEN)); defaultLevelColors.put(LoggingEvent.Level.INFO, new ColorScheme(Color.BLACK, Color.WHITE, Color.WHITE)); defaultLevelColors.put(LoggingEvent.Level.WARN, new ColorScheme(Color.BLACK, Color.YELLOW, Color.YELLOW)); defaultLevelColors.put(LoggingEvent.Level.ERROR, new ColorScheme(Color.YELLOW, Color.RED, Color.ORANGE)); DEFAULT_LEVEL_COLOR_SCHEMES = Collections.unmodifiableMap(defaultLevelColors); Map<HttpStatus.Type, ColorScheme> defaultStatusColors = new HashMap<>(); defaultStatusColors.put(HttpStatus.Type.SUCCESSFUL, new ColorScheme(Color.BLACK, Color.GREEN, Color.GREEN)); defaultStatusColors.put(HttpStatus.Type.INFORMATIONAL, new ColorScheme(Color.BLACK, Color.WHITE, Color.WHITE)); defaultStatusColors.put(HttpStatus.Type.REDIRECTION, new ColorScheme(Color.BLACK, Color.YELLOW, Color.YELLOW)); defaultStatusColors.put(HttpStatus.Type.CLIENT_ERROR, new ColorScheme(Color.GREEN, Color.RED, Color.ORANGE)); defaultStatusColors.put(HttpStatus.Type.SERVER_ERROR, new ColorScheme(Color.YELLOW, Color.RED, Color.ORANGE)); DEFAULT_STATUS_COLOR_SCHEMES = Collections.unmodifiableMap(defaultStatusColors); String lafName = null; LookAndFeel laf = UIManager.getLookAndFeel(); if (laf != null) { lafName = laf.getName(); } STARTUP_LOOK_AND_FEEL = lafName; } private boolean usingScreenMenuBar; /** * Creates a condition of the given name and value. * * @param conditionName the name of the condition * @param value the value for the condition * @return the created condition * @throws IllegalArgumentException if value is not allowed for conditionName. */ public Condition createCondition(String conditionName, String value) { if (conditionName == null) { throw new NullPointerException("conditionName must not be null!"); } switch (conditionName) { case EventContainsCondition.DESCRIPTION: return new EventContainsCondition(value); case LevelCondition.DESCRIPTION: boolean found = false; for (String current : LEVEL_VALUES) { if (current.equalsIgnoreCase(value)) { value = current; found = true; } } if (found) { return new LevelCondition(value); } throw new IllegalArgumentException("Unknown level value '" + value + "'!"); case FormattedMessageContainsCondition.DESCRIPTION: return new FormattedMessageContainsCondition(value); case FormattedMessageEqualsCondition.DESCRIPTION: return new FormattedMessageEqualsCondition(value); case MessagePatternContainsCondition.DESCRIPTION: return new MessagePatternContainsCondition(value); case MessagePatternEqualsCondition.DESCRIPTION: return new MessagePatternEqualsCondition(value); case LoggerStartsWithCondition.DESCRIPTION: return new LoggerStartsWithCondition(value); case LoggerContainsCondition.DESCRIPTION: return new LoggerContainsCondition(value); case LoggerEqualsCondition.DESCRIPTION: return new LoggerEqualsCondition(value); case ThrowableCondition.DESCRIPTION: return new ThrowableCondition(value); case ThreadNameCondition.DESCRIPTION: return new ThreadNameCondition(value); case ThreadGroupNameCondition.DESCRIPTION: return new ThreadGroupNameCondition(value); case CallLocationCondition.DESCRIPTION: return new CallLocationCondition(value); case SAVED_CONDITION: SavedCondition savedCondition = resolveSavedCondition(value); if (savedCondition != null) { return savedCondition.getCondition(); } throw new IllegalArgumentException("Couldn't find saved condition named '" + value + "'."); default: // we assume a groovy condition... File resolvedScriptFile = resolveGroovyConditionScriptFile(conditionName); if (resolvedScriptFile != null) { // there is a file... return new GroovyCondition(resolvedScriptFile.getAbsolutePath(), value); } throw new IllegalArgumentException("Couldn't find condition '" + conditionName + "'!"); } } public String resolveConditionName(Condition condition) { if (condition instanceof GroovyCondition) { // special handling of script files, even though it's also a LilithCondition GroovyCondition groovyCondition = (GroovyCondition) condition; String scriptFileName = groovyCondition.getScriptFileName(); if (scriptFileName != null) { File scriptFile = new File(scriptFileName); // return the pure filename without the path return scriptFile.getName(); } return null; } if (condition instanceof LilithCondition) { return ((LilithCondition) condition).getDescription(); } // TODO? Special handling of SAVED_CONDITION return null; } public List<String> retrieveLevelValues() { return Arrays.asList(LEVEL_VALUES); } public List<String> retrieveAllConditions() { List<String> itemsVector = new ArrayList<>(); itemsVector.addAll(Arrays.asList(DEFAULT_CONDITIONS)); String[] groovyConditions = getAllGroovyConditionScriptFiles(); if (groovyConditions != null) { itemsVector.addAll(Arrays.asList(groovyConditions)); } return itemsVector; } private final Logger logger = LoggerFactory.getLogger(ApplicationPreferences.class); private PropertyChangeSupport propertyChangeSupport; private File startupApplicationPath; private File detailsViewRoot; private List<String> installedLookAndFeels; private String[] conditionScriptFiles; private long lastConditionsCheck; private Map<LoggingEvent.Level, ColorScheme> levelColors; private Map<HttpStatus.Type, ColorScheme> statusColors; private URL detailsViewRootUrl; /** * Identifier => Name */ private Map<String, String> sourceNames; private long lastSourceNamesModified; private long lastConditionsModified; private Map<String, String> soundLocations; private long lastSoundLocationsModified; private Map<String, Set<String>> sourceLists; private long lastSourceListsModified; private LilithPreferences.SourceFiltering sourceFiltering; private Set<String> blackList; private Set<String> whiteList; private List<SavedCondition> conditions; private List<String> previousSearchStrings; private List<String> recentFiles; private File groovyConditionsPath; private File groovyClipboardFormattersPath; public ApplicationPreferences() { lastSourceNamesModified = -1; lastConditionsModified = -1; propertyChangeSupport = new PropertyChangeSupport(this); startupApplicationPath = getApplicationPath(); installedLookAndFeels = new ArrayList<>(); for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { installedLookAndFeels.add(info.getName()); } Collections.sort(installedLookAndFeels); groovyConditionsPath = new File(startupApplicationPath, "conditions"); if (groovyConditionsPath.mkdirs()) { // groovy conditions directory was generated, create examples... installExampleConditions(); } groovyClipboardFormattersPath = new File(startupApplicationPath, "clipboardFormatters"); if (groovyClipboardFormattersPath.mkdirs()) { // groovy clipboardFormatters directory was generated, create examples... installExampleClipboardFormatters(); } } public File getGroovyConditionsPath() { return groovyConditionsPath; } public File getGroovyClipboardFormattersPath() { return groovyClipboardFormattersPath; } public void addRecentFile(File dataFile) { if (dataFile == null) { return; } if (!dataFile.isFile() || !dataFile.canRead()) { if (logger.isWarnEnabled()) logger.warn("Tried to add invalid recent file."); } String absName = dataFile.getAbsolutePath(); List<String> recents = getRecentFiles(); recents.remove(absName); // remove previous entry if available recents.add(0, absName); // add to start of list. setRecentFiles(recents); } public void removeRecentFile(File dataFile) { if (dataFile == null) { return; } String absName = dataFile.getAbsolutePath(); List<String> recents = getRecentFiles(); recents.remove(absName); // remove previous entry if available setRecentFiles(recents); } private void setRecentFiles(List<String> recents) { List<String> copy; if (recents == null) { copy = new ArrayList<>(); } else { copy = new ArrayList<>(recents); } Iterator<String> iter = copy.iterator(); while (iter.hasNext()) { String current = iter.next(); File f = new File(current); if (!f.isFile() || !f.canRead()) { iter.remove(); } // Cleanup before setting... } Object oldValue = getRecentFiles(); while (copy.size() > MAX_RECENT_FILES) { copy.remove(MAX_RECENT_FILES); } writeRecentFiles(copy); Object newValue = getRecentFiles(); propertyChangeSupport.firePropertyChange(RECENT_FILES_PROPERTY, oldValue, newValue); if (logger.isInfoEnabled()) logger.info("recentFiles set to {}.", newValue); } public void clearRecentFiles() { setRecentFiles(new ArrayList<>()); } public List<String> getRecentFiles() { initRecentFiles(); return new ArrayList<>(recentFiles); } public void clearPreviousSearchStrings() { setPreviousSearchStrings(new ArrayList<>()); } /** * This will prevent unchecked warnings and will also validate the content properly. * * @param iface the expected type of the elements. * @param obj the input Object, ideally a List of the given type * @return the input as a List of the given type. */ private static <T> List<T> transformToList(Class<T> iface, Object obj) { final Logger logger = LoggerFactory.getLogger(ApplicationPreferences.class); List<T> resultList = null; if (obj instanceof List) { List list = (List) obj; resultList = new ArrayList<>(list.size()); for (Object current : list) { if (iface.isInstance(current)) { resultList.add(iface.cast(current)); } else { if (logger.isWarnEnabled()) logger.warn("Expected {} but got {}!", iface.getName(), current); } } } else { if (logger.isWarnEnabled()) logger.warn("Expected List but got {}!", obj); } return resultList; } /** * This will prevent unchecked warnings and will also validate the content properly. * * @param iface the expected type of the elements. * @param obj the input Object, ideally a Set of the given type * @return the input as a Set of the given type. */ private static <T> Set<T> transformToSet(Class<T> iface, Object obj) { final Logger logger = LoggerFactory.getLogger(ApplicationPreferences.class); Set<T> resultSet = null; if (obj instanceof Set) { Set set = (Set) obj; resultSet = new HashSet<>(set.size()); for (Object current : set) { if (iface.isInstance(current)) { resultSet.add(iface.cast(current)); } else { if (logger.isWarnEnabled()) logger.warn("Expected {} but got {}!", iface.getName(), current); } } } else { if (logger.isWarnEnabled()) logger.warn("Expected Set but got {}!", obj); } return resultSet; } /** * This will prevent unchecked warnings and will also validate the content properly. * * @param keyClass the expected type of the keys. * @param valueClass the expected type of the values. * @param obj the input Object, ideally a Map of the given types * @return the input as a Map of the given types. */ private static <K, V> Map<K, V> transformToMap(Class<K> keyClass, Class<V> valueClass, Object obj) { final Logger logger = LoggerFactory.getLogger(ApplicationPreferences.class); Map<K, V> resultMap = null; if (obj instanceof Map) { Map map = (Map) obj; resultMap = new HashMap<>(map.size()); for (Object c : map.entrySet()) { Map.Entry current = (Map.Entry) c; Object key = current.getKey(); Object value = current.getValue(); if (!keyClass.isInstance(key)) { if (logger.isWarnEnabled()) logger.warn("Expected {} as key but got {}!", keyClass.getName(), key); continue; } if (!valueClass.isInstance(value)) { if (logger.isWarnEnabled()) logger.warn("Expected {} as value but got {}!", valueClass.getName(), value); continue; } resultMap.put(keyClass.cast(key), valueClass.cast(value)); } } else { if (logger.isWarnEnabled()) logger.warn("Expected Map but got {}!", obj); } return resultMap; } private void initRecentFiles() { File appPath = getStartupApplicationPath(); File file = new File(appPath, RECENT_FILES_XML_FILENAME); if (file.isFile() && this.recentFiles == null) { XMLDecoder d = null; try { d = new XMLDecoder(new BufferedInputStream(new FileInputStream(file))); this.recentFiles = transformToList(String.class, d.readObject()); } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while loading recentFiles from file '" + file.getAbsolutePath() + "'!", ex); IOUtilities.interruptIfNecessary(ex); } finally { if (d != null) { d.close(); } } } if (this.recentFiles == null) { this.recentFiles = new ArrayList<>(); } } private boolean writeRecentFiles(List<String> recentFiles) { File appPath = getStartupApplicationPath(); File file = new File(appPath, RECENT_FILES_XML_FILENAME); XMLEncoder e = null; Throwable error = null; try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); e = new XMLEncoder(bos); e.writeObject(recentFiles); } catch (FileNotFoundException ex) { error = ex; } finally { if (e != null) { e.close(); } } this.recentFiles = null; if (error != null) { if (logger.isWarnEnabled()) logger.warn("Exception while writing recentFiles!", error); return false; } return true; } public void addPreviousSearchString(String searchString) { if (searchString == null) { return; } if (searchString.trim().length() == 0) { // ignore whitespace-only strings return; } List<String> previousSearchStrings = getPreviousSearchStrings(); if (previousSearchStrings.contains(searchString)) { // remove previous string so it'll be at position 0 previousSearchStrings.remove(searchString); } previousSearchStrings.add(0, searchString); setPreviousSearchStrings(previousSearchStrings); } private void setPreviousSearchStrings(List<String> previousSearchStrings) { Object oldValue = getPreviousSearchStrings(); while (previousSearchStrings.size() > MAX_PREV_SEARCHES) { previousSearchStrings.remove(MAX_PREV_SEARCHES); } writePreviousSearchStrings(previousSearchStrings); Object newValue = getPreviousSearchStrings(); propertyChangeSupport.firePropertyChange(PREVIOUS_SEARCH_STRINGS_PROPERTY, oldValue, newValue); if (logger.isInfoEnabled()) logger.info("previousSearchStrings set to {}.", newValue); } public List<String> getPreviousSearchStrings() { initPreviousSearchStrings(); return new ArrayList<>(previousSearchStrings); } private void initPreviousSearchStrings() { File appPath = getStartupApplicationPath(); File file = new File(appPath, PREVIOUS_SEARCH_STRINGS_XML_FILENAME); if (file.isFile() && this.previousSearchStrings == null) { XMLDecoder d = null; try { d = new XMLDecoder(new BufferedInputStream(new FileInputStream(file))); this.previousSearchStrings = transformToList(String.class, d.readObject()); } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while loading previous search strings from file '" + file.getAbsolutePath() + "'!", ex); IOUtilities.interruptIfNecessary(ex); } finally { if (d != null) { d.close(); } } } if (this.previousSearchStrings == null) { this.previousSearchStrings = new ArrayList<>(); } } private boolean writePreviousSearchStrings(List<String> searchStrings) { File appPath = getStartupApplicationPath(); File file = new File(appPath, PREVIOUS_SEARCH_STRINGS_XML_FILENAME); XMLEncoder e = null; Throwable error = null; try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); e = new XMLEncoder(bos); e.writeObject(searchStrings); } catch (FileNotFoundException ex) { error = ex; } finally { if (e != null) { e.close(); } } this.previousSearchStrings = null; if (error != null) { if (logger.isWarnEnabled()) logger.warn("Exception while writing previous search strings!", error); return false; } return true; } public File resolveGroovyConditionScriptFile(String input) { if (input == null) { return null; } if (!input.endsWith(GROOVY_SUFFIX)) { input = input + GROOVY_SUFFIX; } File scriptFile = new File(groovyConditionsPath, input); if (scriptFile.isFile()) { return scriptFile; } return null; } public String[] getAllGroovyConditionScriptFiles() { if (conditionScriptFiles == null || ((System.currentTimeMillis() - lastConditionsCheck) > CONDITIONS_CHECK_INTERVAL)) { File[] groovyFiles = groovyConditionsPath.listFiles(new GroovyConditionFileFilter()); if (groovyFiles != null && groovyFiles.length > 0) { conditionScriptFiles = new String[groovyFiles.length]; for (int i = 0; i < groovyFiles.length; i++) { File current = groovyFiles[i]; conditionScriptFiles[i] = current.getName(); } Arrays.sort(conditionScriptFiles); lastConditionsCheck = System.currentTimeMillis(); } } return conditionScriptFiles; } public File resolveClipboardFormatterScriptFile(String input) { if (input == null) { return null; } if (!input.endsWith(GROOVY_SUFFIX)) { input = input + GROOVY_SUFFIX; } File scriptFile = new File(groovyClipboardFormattersPath, input); if (scriptFile.isFile()) { return scriptFile; } return null; } public String[] getClipboardFormatterScriptFiles() { if (clipboardFormatterScriptFiles == null || ((System.currentTimeMillis() - lastClipboardFormatterCheck) > CONDITIONS_CHECK_INTERVAL)) { File[] groovyFiles = groovyClipboardFormattersPath.listFiles(new GroovyConditionFileFilter()); if (groovyFiles != null && groovyFiles.length > 0) { clipboardFormatterScriptFiles = new String[groovyFiles.length]; for (int i = 0; i < groovyFiles.length; i++) { File current = groovyFiles[i]; clipboardFormatterScriptFiles[i] = current.getName(); } Arrays.sort(clipboardFormatterScriptFiles); lastClipboardFormatterCheck = System.currentTimeMillis(); } else { clipboardFormatterScriptFiles = null; } } return clipboardFormatterScriptFiles; } public void installExampleConditions() { String path = EXAMPLE_GROOVY_CONDITIONS_BASE + GROOVY_EXAMPLE_LIST; URL url = ApplicationPreferences.class.getResource(path); if (url == null) { if (logger.isErrorEnabled()) logger.error("Couldn't find resource at {}!", path); } else { List<String> lines = readLines(url); for (String current : lines) { path = EXAMPLE_GROOVY_CONDITIONS_BASE + current; url = ApplicationPreferences.class.getResource(path); if (url == null) { if (logger.isErrorEnabled()) logger.error("Couldn't find resource at {}!", path); continue; } File target = new File(groovyConditionsPath, current); copy(url, target, true); } } } public void installExampleClipboardFormatters() { String path = EXAMPLE_GROOVY_CLIPBOARD_FORMATTERS_BASE + GROOVY_EXAMPLE_LIST; URL url = ApplicationPreferences.class.getResource(path); if (url == null) { if (logger.isErrorEnabled()) logger.error("Couldn't find resource at {}!", path); } else { List<String> lines = readLines(url); for (String current : lines) { path = EXAMPLE_GROOVY_CLIPBOARD_FORMATTERS_BASE + current; url = ApplicationPreferences.class.getResource(path); if (url == null) { if (logger.isErrorEnabled()) logger.error("Couldn't find resource at {}!", path); continue; } File target = new File(groovyClipboardFormattersPath, current); copy(url, target, true); } } } private void initLevelColors() { if (levelColors == null) { File appPath = getStartupApplicationPath(); File levelColorsFile = new File(appPath, LEVEL_COLORS_XML_FILENAME); if (levelColorsFile.isFile()) { XMLDecoder d = null; try { d = new XMLDecoder(new BufferedInputStream(new FileInputStream(levelColorsFile))); levelColors = transformToMap(LoggingEvent.Level.class, ColorScheme.class, d.readObject()); } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while loading Level-ColorSchemes from file '" + levelColorsFile.getAbsolutePath() + "'!", ex); levelColors = null; IOUtilities.interruptIfNecessary(ex); } finally { if (d != null) { d.close(); } } } } if (levelColors != null && levelColors.size() != DEFAULT_LEVEL_COLOR_SCHEMES.size()) { if (logger.isWarnEnabled()) logger.warn("Reverting Level-ColorSchemes to defaults."); levelColors = null; } if (levelColors == null) { levelColors = cloneLevelColors(DEFAULT_LEVEL_COLOR_SCHEMES); } } private Map<LoggingEvent.Level, ColorScheme> cloneLevelColors(Map<LoggingEvent.Level, ColorScheme> input) { if (input != null && input.size() != DEFAULT_LEVEL_COLOR_SCHEMES.size()) { if (logger.isWarnEnabled()) logger.warn("Reverting Level-ColorSchemes to defaults."); input = null; } if (input == null) { input = DEFAULT_LEVEL_COLOR_SCHEMES; } Map<LoggingEvent.Level, ColorScheme> result = new HashMap<>(); for (Map.Entry<LoggingEvent.Level, ColorScheme> current : input.entrySet()) { try { result.put(current.getKey(), current.getValue().clone()); } catch (CloneNotSupportedException ex) { if (logger.isErrorEnabled()) logger.error("Exception while cloning Level-ColorScheme!!", ex); } } return result; } public void setLevelColors(Map<LoggingEvent.Level, ColorScheme> colors) { Object oldValue = getLevelColors(); colors = cloneLevelColors(colors); writeLevelColors(colors); this.levelColors = colors; Object newValue = getLevelColors(); propertyChangeSupport.firePropertyChange(LEVEL_COLORS_PROPERTY, oldValue, newValue); if (logger.isInfoEnabled()) logger.info("LevelColors set to {}.", this.levelColors); } private void writeLevelColors(Map<LoggingEvent.Level, ColorScheme> colors) { File appPath = getStartupApplicationPath(); File file = new File(appPath, LEVEL_COLORS_XML_FILENAME); try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); XMLEncoder e = new XMLEncoder(bos); PersistenceDelegate delegate = new EnumPersistenceDelegate(); e.setPersistenceDelegate(LoggingEvent.Level.class, delegate); e.writeObject(colors); e.close(); } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while writing colors!", ex); IOUtilities.interruptIfNecessary(ex); } } public Map<LoggingEvent.Level, ColorScheme> getLevelColors() { if (levelColors == null) { initLevelColors(); } return cloneLevelColors(levelColors); } private void initStatusColors() { if (statusColors == null) { File appPath = getStartupApplicationPath(); File statusColorsFile = new File(appPath, STATUS_COLORS_XML_FILENAME); if (statusColorsFile.isFile()) { XMLDecoder d = null; try { d = new XMLDecoder(new BufferedInputStream(new FileInputStream(statusColorsFile))); statusColors = transformToMap(HttpStatus.Type.class, ColorScheme.class, d.readObject()); } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while loading status Status-ColorSchemes from file '" + statusColorsFile.getAbsolutePath() + "'!", ex); statusColors = null; IOUtilities.interruptIfNecessary(ex); } finally { if (d != null) { d.close(); } } } } if (statusColors != null && statusColors.size() != DEFAULT_STATUS_COLOR_SCHEMES.size()) { if (logger.isWarnEnabled()) logger.warn("Reverting Status-ColorSchemes to defaults."); statusColors = null; } if (statusColors == null) { statusColors = cloneStatusColors(DEFAULT_STATUS_COLOR_SCHEMES); } } private Map<HttpStatus.Type, ColorScheme> cloneStatusColors(Map<HttpStatus.Type, ColorScheme> input) { if (input != null && input.size() != DEFAULT_STATUS_COLOR_SCHEMES.size()) { if (logger.isWarnEnabled()) logger.warn("Reverting Status-ColorSchemes to defaults."); input = null; } if (input == null) { input = DEFAULT_STATUS_COLOR_SCHEMES; } Map<HttpStatus.Type, ColorScheme> result = new HashMap<>(); for (Map.Entry<HttpStatus.Type, ColorScheme> current : input.entrySet()) { try { result.put(current.getKey(), current.getValue().clone()); } catch (CloneNotSupportedException ex) { if (logger.isErrorEnabled()) logger.error("Exception while cloning Status-ColorScheme!!", ex); } } return result; } public void setStatusColors(Map<HttpStatus.Type, ColorScheme> colors) { Object oldValue = getStatusColors(); colors = cloneStatusColors(colors); writeStatusColors(colors); this.statusColors = colors; Object newValue = getStatusColors(); propertyChangeSupport.firePropertyChange(STATUS_COLORS_PROPERTY, oldValue, newValue); if (logger.isInfoEnabled()) logger.info("StatusColors set to {}.", this.statusColors); } private void writeStatusColors(Map<HttpStatus.Type, ColorScheme> colors) { File appPath = getStartupApplicationPath(); File file = new File(appPath, STATUS_COLORS_XML_FILENAME); try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); XMLEncoder e = new XMLEncoder(bos); PersistenceDelegate delegate = new EnumPersistenceDelegate(); e.setPersistenceDelegate(HttpStatus.Type.class, delegate); e.writeObject(colors); e.close(); } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while writing colors!", ex); IOUtilities.interruptIfNecessary(ex); } } public Map<HttpStatus.Type, ColorScheme> getStatusColors() { if (statusColors == null) { initStatusColors(); } return cloneStatusColors(statusColors); } private void initSourceLists() { File appPath = getStartupApplicationPath(); File sourceListsFile = new File(appPath, SOURCE_LISTS_XML_FILENAME); if (sourceListsFile.isFile()) { long lastModified = sourceListsFile.lastModified(); if (sourceLists != null && lastSourceListsModified >= lastModified) { if (logger.isDebugEnabled()) logger.debug("Won't reload source lists."); return; } XMLDecoder d = null; try { d = new XMLDecoder(new BufferedInputStream(new FileInputStream(sourceListsFile))); Map<String, Set> interimMap = transformToMap(String.class, Set.class, d.readObject()); Map<String, Set<String>> resultMap = null; if (interimMap != null) { resultMap = new HashMap<>(); for (Map.Entry<String, Set> current : interimMap.entrySet()) { Set<String> value = transformToSet(String.class, current.getValue()); if (value == null) { continue; } resultMap.put(current.getKey(), value); } } sourceLists = resultMap; lastSourceListsModified = lastModified; } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while loading source lists from sourceListsFile '" + sourceListsFile.getAbsolutePath() + "'!", ex); sourceLists = new HashMap<>(); IOUtilities.interruptIfNecessary(ex); } finally { if (d != null) { d.close(); } } } else if (sourceLists == null) { sourceLists = new HashMap<>(); } } public Map<String, Set<String>> getSourceLists() { initSourceLists(); return new HashMap<>(sourceLists); } public void setSourceLists(Map<String, Set<String>> sourceLists) { Object oldValue = getSourceLists(); writeSourceLists(sourceLists); Object newValue = getSourceLists(); blackList = null; whiteList = null; propertyChangeSupport.firePropertyChange(SOURCE_LISTS_PROPERTY, oldValue, newValue); if (sourceLists == null) { setSourceFiltering(LilithPreferences.SourceFiltering.NONE); setWhiteListName(""); setBlackListName(""); } else { String blackListName = getBlackListName(); if (sourceLists.get(blackListName) == null) { setBlackListName(""); if (getSourceFiltering() == LilithPreferences.SourceFiltering.BLACKLIST) { setSourceFiltering(LilithPreferences.SourceFiltering.NONE); } } String whiteListName = getWhiteListName(); if (sourceLists.get(whiteListName) == null) { setWhiteListName(""); if (getSourceFiltering() == LilithPreferences.SourceFiltering.WHITELIST) { setSourceFiltering(LilithPreferences.SourceFiltering.NONE); } } } } public void setSourceFiltering(LilithPreferences.SourceFiltering sourceFiltering) { Object oldValue = getSourceFiltering(); PREFERENCES.put(SOURCE_FILTERING_PROPERTY, sourceFiltering.toString()); this.sourceFiltering = sourceFiltering; propertyChangeSupport.firePropertyChange(SOURCE_FILTERING_PROPERTY, oldValue, sourceFiltering); if (logger.isInfoEnabled()) logger.info("SourceFiltering set to {}.", this.sourceFiltering); } public LilithPreferences.SourceFiltering getSourceFiltering() { if (sourceFiltering != null) { return sourceFiltering; } String sf = PREFERENCES.get(SOURCE_FILTERING_PROPERTY, "NONE"); try { sourceFiltering = LilithPreferences.SourceFiltering.valueOf(sf); } catch (IllegalArgumentException e) { sourceFiltering = LilithPreferences.SourceFiltering.NONE; } return sourceFiltering; } public void initDetailsViewRoot(boolean overwriteAlways) { detailsViewRoot = new File(startupApplicationPath, DETAILS_VIEW_ROOT_FOLDER); if (detailsViewRoot.mkdirs()) { if (logger.isInfoEnabled()) logger.info("Created directory {}.", detailsViewRoot.getAbsolutePath()); } try { detailsViewRootUrl = detailsViewRoot.toURI().toURL(); } catch (MalformedURLException e) { if (logger.isWarnEnabled()) logger.warn("Exception while creating detailsViewRootUrl for '{}'!", detailsViewRoot.getAbsolutePath()); detailsViewRootUrl = null; } { String resourcePath = "/detailsView/" + DETAILS_VIEW_CSS_FILENAME; String historyBasePath = "/detailsView/history/detailsView.css/"; File detailsViewCssFile = new File(detailsViewRoot, DETAILS_VIEW_CSS_FILENAME); initIfNecessary(detailsViewCssFile, resourcePath, historyBasePath, overwriteAlways); } { String resourcePath = "/detailsView/" + DETAILS_VIEW_GROOVY_FILENAME; String historyBasePath = "/detailsView/history/detailsView.groovy/"; File detailsViewGroovyFile = new File(detailsViewRoot, DETAILS_VIEW_GROOVY_FILENAME); initIfNecessary(detailsViewGroovyFile, resourcePath, historyBasePath, overwriteAlways); } } private void initIfNecessary(File file, String resourcePath, String historyBasePath, boolean overwriteAlways) { boolean delete = false; if (overwriteAlways) { delete = true; } else if (file.isFile()) { byte[] available = null; try { FileInputStream availableFile = new FileInputStream(file); available = getMD5(availableFile); } catch (FileNotFoundException e) { // ignore } byte[] current = getMD5(ApplicationPreferences.class.getResourceAsStream(resourcePath)); if (Arrays.equals(available, current)) { // we are done already. The current version is the latest version. if (logger.isDebugEnabled()) logger.debug("The current version of {} is also the latest version.", file.getAbsolutePath()); return; } if (available != null) { // check older versions if available URL historyUrl = getClass().getResource(historyBasePath + "history.txt"); if (historyUrl != null) { List<String> historyList = readLines(historyUrl); for (String currentLine : historyList) { InputStream is = getClass().getResourceAsStream(historyBasePath + currentLine + ".md5"); if (is != null) { DataInputStream dis = new DataInputStream(is); byte[] checksum = new byte[16]; try { dis.readFully(checksum); if (Arrays.equals(available, checksum)) { if (logger.isInfoEnabled()) logger.info("Found old version of {}: {}", file.getAbsolutePath(), currentLine); delete = true; break; } } catch (IOException e) { if (logger.isWarnEnabled()) logger.warn("Exception while reading checksum of {}!", currentLine, e); } finally { try { dis.close(); } catch (IOException e) { // ignore } } } } } } else { // we couldn't calculate the checksum. Try to delete it... delete = true; } } URL resourceUrl = ApplicationPreferences.class.getResource(resourcePath); if (resourceUrl == null) { if (logger.isErrorEnabled()) logger.error("Couldn't find resource {}!", resourcePath); return; } copy(resourceUrl, file, delete); } private void copy(URL source, File target, boolean overwrite) { if (overwrite) { if (target.isFile()) { if (target.delete()) { if (logger.isInfoEnabled()) logger.info("Deleted {}. ", target.getAbsolutePath()); } else { if (logger.isWarnEnabled()) logger.warn("Tried to delete {} but couldn't!", target.getAbsolutePath()); } } } if (!target.isFile()) { InputStream is = null; FileOutputStream os = null; try { os = new FileOutputStream(target); is = source.openStream(); IOUtils.copy(is, os); if (logger.isInfoEnabled()) logger.info("Initialized file at '{}' with data from '{}'.", target.getAbsolutePath(), source); } catch (IOException e) { if (logger.isWarnEnabled()) logger.warn("Exception while initializing '" + target.getAbsolutePath() + "' with data from '" + source + "'.!", e); } finally { IOUtilities.closeQuietly(is); IOUtilities.closeQuietly(os); } } else { if (logger.isInfoEnabled()) logger.info("Won't overwrite '{}'.", target.getAbsolutePath()); } } /** * Returns a list of strings containing all non-empty, non-comment lines found in the given URL. * Commented lines start with a #. * * @param url the URL to read the lines from. * @return a List of type String containing all non-empty, non-comment lines. */ private List<String> readLines(URL url) { List<String> result = new ArrayList<>(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)); for (;;) { String currentLine = reader.readLine(); if (currentLine == null) { break; } currentLine = currentLine.trim(); if (!"".equals(currentLine) && !currentLine.startsWith("#")) { result.add(currentLine); } } } catch (IOException e) { if (logger.isWarnEnabled()) logger.warn("Exception while reading lines from {}!", url, e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // ignore } } } return result; } public File getDetailsViewRoot() { if (detailsViewRoot != null) { return detailsViewRoot; } initDetailsViewRoot(false); return detailsViewRoot; } public URL getDetailsViewRootUrl() { if (detailsViewRootUrl != null) { return detailsViewRootUrl; } initDetailsViewRoot(false); return detailsViewRootUrl; } public boolean isValidSource(String source) { if (source == null) { return false; } LilithPreferences.SourceFiltering filtering = getSourceFiltering(); switch (filtering) { case BLACKLIST: return !isBlackListed(source); case WHITELIST: return isWhiteListed(source); } return true; } public boolean isBlackListed(String source) { if (blackList == null) { String listName = getBlackListName(); initSourceLists(); blackList = sourceLists.get(listName); if (blackList == null) { // meaning there was no list of the given name. if (logger.isInfoEnabled()) logger.info("Couldn't find source list '{}'!", listName); setSourceFiltering(LilithPreferences.SourceFiltering.NONE); setBlackListName(""); return true; } } return blackList.contains(source); } public void setBlackListName(String name) { Object oldValue = getBlackListName(); PREFERENCES.put(BLACK_LIST_NAME_PROPERTY, name); Object newValue = getBlackListName(); propertyChangeSupport.firePropertyChange(BLACK_LIST_NAME_PROPERTY, oldValue, newValue); if (logger.isInfoEnabled()) logger.info("BlackListName set to {}.", newValue); } public String getBlackListName() { return PREFERENCES.get(BLACK_LIST_NAME_PROPERTY, ""); } public boolean isWhiteListed(String source) { if (whiteList == null) { String listName = getWhiteListName(); initSourceLists(); whiteList = sourceLists.get(listName); if (whiteList == null) { // meaning there was no source list of the given name. if (logger.isInfoEnabled()) logger.info("Couldn't find source list '{}'!", listName); setSourceFiltering(LilithPreferences.SourceFiltering.NONE); setWhiteListName(""); return true; } } return whiteList.contains(source); } public void setWhiteListName(String name) { Object oldValue = getWhiteListName(); PREFERENCES.put(WHITE_LIST_NAME_PROPERTY, name); Object newValue = getWhiteListName(); propertyChangeSupport.firePropertyChange(WHITE_LIST_NAME_PROPERTY, oldValue, newValue); if (logger.isInfoEnabled()) logger.info("WhiteListName set to {}.", newValue); } public String getWhiteListName() { return PREFERENCES.get(WHITE_LIST_NAME_PROPERTY, ""); } public void setLookAndFeel(String name) { Object oldValue = getLookAndFeel(); PREFERENCES.put(LOOK_AND_FEEL_PROPERTY, name); Object newValue = getLookAndFeel(); propertyChangeSupport.firePropertyChange(LOOK_AND_FEEL_PROPERTY, oldValue, newValue); if (logger.isInfoEnabled()) logger.info("LookAndFeel set to {}.", newValue); } public String getLookAndFeel() { String result = PREFERENCES.get(LOOK_AND_FEEL_PROPERTY, STARTUP_LOOK_AND_FEEL); if (!installedLookAndFeels.contains(result)) { result = STARTUP_LOOK_AND_FEEL; if (logger.isInfoEnabled()) logger.info("Look and Feel corrected to \"{}\".", result); } return result; } public void setCurrentTipOfTheDay(int currentTipOfTheDay) { Object oldValue = getCurrentTipOfTheDay(); PREFERENCES.putInt(CURRENT_TIP_OF_THE_DAY_PROPERTY, currentTipOfTheDay); Object newValue = getCurrentTipOfTheDay(); propertyChangeSupport.firePropertyChange(CURRENT_TIP_OF_THE_DAY_PROPERTY, oldValue, newValue); } public int getCurrentTipOfTheDay() { return PREFERENCES.getInt(CURRENT_TIP_OF_THE_DAY_PROPERTY, -1); } public void setShowingTipOfTheDay(boolean showingTipOfTheDay) { Object oldValue = isShowingTipOfTheDay(); PREFERENCES.putBoolean(SHOWING_TIP_OF_THE_DAY_PROPERTY, showingTipOfTheDay); Object newValue = isShowingTipOfTheDay(); propertyChangeSupport.firePropertyChange(SHOWING_TIP_OF_THE_DAY_PROPERTY, oldValue, newValue); } public boolean isShowingTipOfTheDay() { return PREFERENCES.getBoolean(SHOWING_TIP_OF_THE_DAY_PROPERTY, DEFAULT_VALUES.isShowingTipOfTheDay()); } public void setMaximizingInternalFrames(boolean showingTipOfTheDay) { Object oldValue = isMaximizingInternalFrames(); PREFERENCES.putBoolean(MAXIMIZING_INTERNAL_FRAMES_PROPERTY, showingTipOfTheDay); Object newValue = isMaximizingInternalFrames(); propertyChangeSupport.firePropertyChange(MAXIMIZING_INTERNAL_FRAMES_PROPERTY, oldValue, newValue); } public boolean isMaximizingInternalFrames() { return PREFERENCES.getBoolean(MAXIMIZING_INTERNAL_FRAMES_PROPERTY, DEFAULT_VALUES.isMaximizingInternalFrames()); } public void setGlobalLoggingEnabled(boolean globalLoggingEnabled) { Object oldValue = isGlobalLoggingEnabled(); PREFERENCES.putBoolean(GLOBAL_LOGGING_ENABLED_PROPERTY, globalLoggingEnabled); Object newValue = isGlobalLoggingEnabled(); propertyChangeSupport.firePropertyChange(GLOBAL_LOGGING_ENABLED_PROPERTY, oldValue, newValue); } public boolean isGlobalLoggingEnabled() { return PREFERENCES.getBoolean(GLOBAL_LOGGING_ENABLED_PROPERTY, DEFAULT_VALUES.isGlobalLoggingEnabled()); } private void initConditions() { File appPath = getStartupApplicationPath(); File conditionsFile = new File(appPath, CONDITIONS_XML_FILENAME); if (conditionsFile.isFile()) { long lastModified = conditionsFile.lastModified(); if (conditions != null && lastConditionsModified >= lastModified) { if (logger.isDebugEnabled()) logger.debug("Won't reload conditions."); return; } XMLDecoder d = null; try { d = new XMLDecoder(new BufferedInputStream(new FileInputStream(conditionsFile))); conditions = transformToList(SavedCondition.class, d.readObject()); lastConditionsModified = lastModified; if (logger.isDebugEnabled()) logger.debug("Loaded conditions {}.", conditions); } catch (Throwable ex) { if (logger.isWarnEnabled()) logger.warn("Exception while loading conditions from file '" + conditionsFile.getAbsolutePath() + "'!", ex); IOUtilities.interruptIfNecessary(ex); } finally { if (d != null) { d.close(); } } } if (conditions == null) { conditions = new ArrayList<>(); } } public SavedCondition resolveSavedCondition(Condition condition) { if (condition == null) { return null; } initConditions(); for (SavedCondition current : conditions) { if (condition.equals(current.getCondition())) { try { return current.clone(); } catch (CloneNotSupportedException e) { return null; } } } return null; } public SavedCondition resolveSavedCondition(String conditionName) { if (conditionName == null) { return null; } initConditions(); for (SavedCondition current : conditions) { if (conditionName.equals(current.getName())) { try { return current.clone(); } catch (CloneNotSupportedException e) { return null; } } } return null; } public List<SavedCondition> getConditions() { initConditions(); // perform deep clone... otherwise no propchange would be fired. ArrayList<SavedCondition> result = new ArrayList<>(conditions.size()); for (SavedCondition current : conditions) { try { result.add(current.clone()); } catch (CloneNotSupportedException e) { // ignore } } return result; } public List<String> getConditionNames() { initConditions(); // perform deep clone... otherwise no propchange would be fired. ArrayList<String> result = new ArrayList<>(conditions.size()); result.addAll(conditions.stream().map(SavedCondition::getName).collect(Collectors.toList())); return result; } public void setConditions(List<SavedCondition> conditions) { Object oldValue = getConditions(); writeConditions(conditions); Object newValue = getConditions(); propertyChangeSupport.firePropertyChange(CONDITIONS_PROPERTY, oldValue, newValue); } public void setAutoOpening(boolean autoOpening) { Object oldValue = isAutoOpening(); PREFERENCES.putBoolean(AUTO_OPENING_PROPERTY, autoOpening); Object newValue = isAutoOpening(); propertyChangeSupport.firePropertyChange(AUTO_OPENING_PROPERTY, oldValue, newValue); } public boolean isAutoOpening() { return PREFERENCES.getBoolean(AUTO_OPENING_PROPERTY, DEFAULT_VALUES.isAutoOpening()); } public void setTrayActive(boolean trayActive) { Object oldValue = isTrayActive(); PREFERENCES.putBoolean(TRAY_ACTIVE_PROPERTY, trayActive); Object newValue = isTrayActive(); propertyChangeSupport.firePropertyChange(TRAY_ACTIVE_PROPERTY, oldValue, newValue); } public boolean isTrayActive() { return PREFERENCES.getBoolean(TRAY_ACTIVE_PROPERTY, DEFAULT_VALUES.isTrayActive()); } public void setHidingOnClose(boolean trayActive) { Object oldValue = isHidingOnClose(); PREFERENCES.putBoolean(HIDING_ON_CLOSE_PROPERTY, trayActive); Object newValue = isHidingOnClose(); propertyChangeSupport.firePropertyChange(HIDING_ON_CLOSE_PROPERTY, oldValue, newValue); } public boolean isHidingOnClose() { return PREFERENCES.getBoolean(HIDING_ON_CLOSE_PROPERTY, DEFAULT_VALUES.isHidingOnClose()); } public void setShowingToolbar(boolean showingToolbar) { Object oldValue = isShowingToolbar(); PREFERENCES.putBoolean(SHOWING_TOOLBAR_PROPERTY, showingToolbar); Object newValue = isShowingToolbar(); propertyChangeSupport.firePropertyChange(SHOWING_TOOLBAR_PROPERTY, oldValue, newValue); } public boolean isShowingToolbar() { return PREFERENCES.getBoolean(SHOWING_TOOLBAR_PROPERTY, DEFAULT_VALUES.isShowingToolbar()); } public boolean isShowingStatusBar() { return PREFERENCES.getBoolean(SHOWING_STATUSBAR_PROPERTY, DEFAULT_VALUES.isShowingStatusbar()); } public void setShowingStatusBar(boolean showingStatusBar) { Object oldValue = isShowingStatusBar(); PREFERENCES.putBoolean(SHOWING_STATUSBAR_PROPERTY, showingStatusBar); Object newValue = isShowingStatusBar(); propertyChangeSupport.firePropertyChange(SHOWING_STATUSBAR_PROPERTY, oldValue, newValue); } public void setShowingPrimaryIdentifier(boolean showingPrimaryIdentifier) { Object oldValue = isShowingPrimaryIdentifier(); PREFERENCES.putBoolean(SHOWING_PRIMARY_IDENTIFIER_PROPERTY, showingPrimaryIdentifier); Object newValue = isShowingPrimaryIdentifier(); propertyChangeSupport.firePropertyChange(SHOWING_PRIMARY_IDENTIFIER_PROPERTY, oldValue, newValue); } public boolean isShowingPrimaryIdentifier() { return PREFERENCES.getBoolean(SHOWING_PRIMARY_IDENTIFIER_PROPERTY, DEFAULT_VALUES.isShowingPrimaryIdentifier()); } public void setShowingSecondaryIdentifier(boolean showingSecondaryIdentifier) { Object oldValue = isShowingSecondaryIdentifier(); PREFERENCES.putBoolean(SHOWING_SECONDARY_IDENTIFIER_PROPERTY, showingSecondaryIdentifier); Object newValue = isShowingSecondaryIdentifier(); propertyChangeSupport.firePropertyChange(SHOWING_SECONDARY_IDENTIFIER_PROPERTY, oldValue, newValue); } public boolean isShowingSecondaryIdentifier() { return PREFERENCES.getBoolean(SHOWING_SECONDARY_IDENTIFIER_PROPERTY, DEFAULT_VALUES.isShowingPrimaryIdentifier()); } public void setSplashScreenDisabled(boolean splashScreenDisabled) { Object oldValue = isSplashScreenDisabled(); PREFERENCES.putBoolean(SPLASH_SCREEN_DISABLED_PROPERTY, splashScreenDisabled); Object newValue = isSplashScreenDisabled(); propertyChangeSupport.firePropertyChange(SPLASH_SCREEN_DISABLED_PROPERTY, oldValue, newValue); } public boolean isSplashScreenDisabled() { return PREFERENCES.getBoolean(SPLASH_SCREEN_DISABLED_PROPERTY, DEFAULT_VALUES.isSplashScreenDisabled()); } public void setAskingBeforeQuit(boolean askingBeforeQuit) { Object oldValue = isAskingBeforeQuit(); PREFERENCES.putBoolean(ASKING_BEFORE_QUIT_PROPERTY, askingBeforeQuit); Object newValue = isAskingBeforeQuit(); propertyChangeSupport.firePropertyChange(ASKING_BEFORE_QUIT_PROPERTY, oldValue, newValue); } public boolean isAskingBeforeQuit() { return PREFERENCES.getBoolean(ASKING_BEFORE_QUIT_PROPERTY, DEFAULT_VALUES.isAskingBeforeQuit()); } public void setShowingFullCallstack(boolean showingFullCallstack) { Object oldValue = isShowingFullCallstack(); PREFERENCES.putBoolean(SHOWING_FULL_CALLSTACK_PROPERTY, showingFullCallstack); Object newValue = isShowingFullCallstack(); propertyChangeSupport.firePropertyChange(SHOWING_FULL_CALLSTACK_PROPERTY, oldValue, newValue); } public boolean isShowingFullCallstack() { return PREFERENCES.getBoolean(SHOWING_FULL_CALLSTACK_PROPERTY, DEFAULT_VALUES.isShowingFullCallstack()); } public void setUsingWrappedExceptionStyle(boolean showingFullCallstack) { Object oldValue = isUsingWrappedExceptionStyle(); PREFERENCES.putBoolean(USING_WRAPPED_EXCEPTION_STYLE_PROPERTY, showingFullCallstack); Object newValue = isUsingWrappedExceptionStyle(); propertyChangeSupport.firePropertyChange(USING_WRAPPED_EXCEPTION_STYLE_PROPERTY, oldValue, newValue); } public boolean isUsingWrappedExceptionStyle() { return PREFERENCES.getBoolean(USING_WRAPPED_EXCEPTION_STYLE_PROPERTY, DEFAULT_VALUES.isUsingWrappedExceptionStyle()); } public void setShowingStackTrace(boolean showingStackTrace) { Object oldValue = isShowingStackTrace(); PREFERENCES.putBoolean(SHOWING_STACKTRACE_PROPERTY, showingStackTrace); Object newValue = isShowingStackTrace(); propertyChangeSupport.firePropertyChange(SHOWING_STACKTRACE_PROPERTY, oldValue, newValue); } public boolean isShowingStackTrace() { return PREFERENCES.getBoolean(SHOWING_STACKTRACE_PROPERTY, DEFAULT_VALUES.isShowingStackTrace()); } public void setCleaningLogsOnExit(boolean cleaningLogsOnExit) { Object oldValue = isCleaningLogsOnExit(); PREFERENCES.putBoolean(CLEANING_LOGS_ON_EXIT_PROPERTY, cleaningLogsOnExit); Object newValue = isCleaningLogsOnExit(); propertyChangeSupport.firePropertyChange(CLEANING_LOGS_ON_EXIT_PROPERTY, oldValue, newValue); } public boolean isCleaningLogsOnExit() { return PREFERENCES.getBoolean(CLEANING_LOGS_ON_EXIT_PROPERTY, DEFAULT_VALUES.isCleaningLogsOnExit()); } public void setColoringWholeRow(boolean coloringWholeRow) { Object oldValue = isColoringWholeRow(); PREFERENCES.putBoolean(COLORING_WHOLE_ROW_PROPERTY, coloringWholeRow); Object newValue = isColoringWholeRow(); propertyChangeSupport.firePropertyChange(COLORING_WHOLE_ROW_PROPERTY, oldValue, newValue); } public boolean isColoringWholeRow() { return PREFERENCES.getBoolean(COLORING_WHOLE_ROW_PROPERTY, DEFAULT_VALUES.isColoringWholeRow()); } public void setCheckingForUpdate(boolean checkingForUpdate) { Object oldValue = isCheckingForUpdate(); PREFERENCES.putBoolean(CHECKING_FOR_UPDATE_PROPERTY, checkingForUpdate); Object newValue = isCheckingForUpdate(); propertyChangeSupport.firePropertyChange(CHECKING_FOR_UPDATE_PROPERTY, oldValue, newValue); } public boolean isCheckingForUpdate() { return PREFERENCES.getBoolean(CHECKING_FOR_UPDATE_PROPERTY, DEFAULT_VALUES.isCheckingForUpdate()); } public void setCheckingForSnapshot(boolean checkingForSnapshot) { Object oldValue = isCheckingForSnapshot(); PREFERENCES.putBoolean(CHECKING_FOR_SNAPSHOT_PROPERTY, checkingForSnapshot); Object newValue = isCheckingForSnapshot(); propertyChangeSupport.firePropertyChange(CHECKING_FOR_SNAPSHOT_PROPERTY, oldValue, newValue); } public boolean isCheckingForSnapshot() { return PREFERENCES.getBoolean(CHECKING_FOR_SNAPSHOT_PROPERTY, DEFAULT_VALUES.isCheckingForSnapshot()); } public void setAutoClosing(boolean autoClosing) { Object oldValue = isAutoClosing(); PREFERENCES.putBoolean(AUTO_CLOSING_PROPERTY, autoClosing); Object newValue = isAutoClosing(); propertyChangeSupport.firePropertyChange(AUTO_CLOSING_PROPERTY, oldValue, newValue); } public boolean isAutoClosing() { return PREFERENCES.getBoolean(AUTO_CLOSING_PROPERTY, DEFAULT_VALUES.isAutoClosing()); } public File getImagePath() { String imagePath = PREFERENCES.get(IMAGE_PATH_PROPERTY, USER_HOME); File result = new File(imagePath); if (!result.isDirectory()) { result = new File(USER_HOME); } return result; } public void setImagePath(File imagePath) { if (!imagePath.isDirectory()) { throw new IllegalArgumentException("'" + imagePath.getAbsolutePath() + "' is not a directory!"); } Object oldValue = getImagePath(); PREFERENCES.put(IMAGE_PATH_PROPERTY, imagePath.getAbsolutePath()); Object newValue = getImagePath(); propertyChangeSupport.firePropertyChange(IMAGE_PATH_PROPERTY, oldValue, newValue); } public File getPreviousOpenPath() { String imagePath = PREFERENCES.get(PREVIOUS_OPEN_PATH_PROPERTY, USER_HOME); File result = new File(imagePath); if (!result.isDirectory()) { result = new File(USER_HOME); } return result; } public void setPreviousOpenPath(File openPath) { if (!openPath.isDirectory()) { throw new IllegalArgumentException("'" + openPath.getAbsolutePath() + "' is not a directory!"); } Object oldValue = getPreviousOpenPath(); PREFERENCES.put(PREVIOUS_OPEN_PATH_PROPERTY, openPath.getAbsolutePath()); Object newValue = getPreviousOpenPath(); propertyChangeSupport.firePropertyChange(PREVIOUS_OPEN_PATH_PROPERTY, oldValue, newValue); } public File getPreviousImportPath() { String path = PREFERENCES.get(PREVIOUS_IMPORT_PATH_PROPERTY, USER_HOME); File result = new File(path); if (!result.isDirectory()) { result = new File(USER_HOME); } return result; } public void setPreviousImportPath(File importPath) { if (!importPath.isDirectory()) { throw new IllegalArgumentException("'" + importPath.getAbsolutePath() + "' is not a directory!"); } Object oldValue = getPreviousImportPath(); PREFERENCES.put(PREVIOUS_IMPORT_PATH_PROPERTY, importPath.getAbsolutePath()); Object newValue = getPreviousImportPath(); propertyChangeSupport.firePropertyChange(PREVIOUS_IMPORT_PATH_PROPERTY, oldValue, newValue); } public File getPreviousExportPath() { String path = PREFERENCES.get(PREVIOUS_EXPORT_PATH_PROPERTY, USER_HOME); File result = new File(path); if (!result.isDirectory()) { result = new File(USER_HOME); } return result; } public void setPreviousExportPath(File exportPath) { if (!exportPath.isDirectory()) { throw new IllegalArgumentException("'" + exportPath.getAbsolutePath() + "' is not a directory!"); } Object oldValue = getPreviousImportPath(); PREFERENCES.put(PREVIOUS_EXPORT_PATH_PROPERTY, exportPath.getAbsolutePath()); Object newValue = getPreviousExportPath(); propertyChangeSupport.firePropertyChange(PREVIOUS_EXPORT_PATH_PROPERTY, oldValue, newValue); } public File getSoundPath() { String soundPath = PREFERENCES.get(SOUND_PATH_PROPERTY, USER_HOME); File result = new File(soundPath); if (!result.isDirectory()) { result = new File(USER_HOME); } return result; } public void setSoundPath(File soundPath) { if (!soundPath.isDirectory()) { throw new IllegalArgumentException("'" + soundPath.getAbsolutePath() + "' is not a directory!"); } Object oldValue = getSoundPath(); PREFERENCES.put(SOUND_PATH_PROPERTY, soundPath.getAbsolutePath()); Object newValue = getSoundPath(); propertyChangeSupport.firePropertyChange(SOUND_PATH_PROPERTY, oldValue, newValue); } public void setScaleFactor(double scale) { Object oldValue = getScaleFactor(); PREFERENCES.putDouble(SCALE_FACTOR_PROPERTY, scale); Object newValue = getScaleFactor(); propertyChangeSupport.firePropertyChange(SCALE_FACTOR_PROPERTY, oldValue, newValue); } public double getScaleFactor() { return PREFERENCES.getDouble(SCALE_FACTOR_PROPERTY, 1.0d); } public void setMute(boolean mute) { Object oldValue = isMute(); PREFERENCES.putBoolean(MUTE_PROPERTY, mute); Object newValue = isMute(); propertyChangeSupport.firePropertyChange(MUTE_PROPERTY, oldValue, newValue); } public boolean isMute() { return PREFERENCES.getBoolean(MUTE_PROPERTY, DEFAULT_VALUES.isMute()); } public void setLicensed(boolean licensed) { Object oldValue = isLicensed(); if (licensed) { PREFERENCES.put(LICENSED_PREFERENCES_KEY, Lilith.APP_VERSION); } else { PREFERENCES.remove(LICENSED_PREFERENCES_KEY); } Object newValue = isLicensed(); propertyChangeSupport.firePropertyChange(LICENSED_PREFERENCES_KEY, oldValue, newValue); } public boolean isLicensed() { return Lilith.APP_VERSION.equals(PREFERENCES.get(LICENSED_PREFERENCES_KEY, null)); } public void setApplicationPath(File applicationPath) { if (applicationPath.mkdirs()) { if (logger.isInfoEnabled()) logger.info("Created directory {}.", applicationPath.getAbsolutePath()); } if (!applicationPath.isDirectory()) { throw new IllegalArgumentException("'" + applicationPath.getAbsolutePath() + "' is not a directory!"); } Object oldValue = getStartupApplicationPath(); // !!! PREFERENCES.put(APPLICATION_PATH_PROPERTY, applicationPath.getAbsolutePath()); Object newValue = getApplicationPath(); propertyChangeSupport.firePropertyChange(APPLICATION_PATH_PROPERTY, oldValue, newValue); } public File getApplicationPath() { String appPath = PREFERENCES.get(APPLICATION_PATH_PROPERTY, DEFAULT_APPLICATION_PATH); File result = new File(appPath); if (result.mkdirs()) { if (logger.isInfoEnabled()) logger.info("Created directory {}.", result.getAbsolutePath()); } return result; } public void setDefaultConditionName(String conditionName) { Object oldValue = getDefaultConditionName(); PREFERENCES.put(DEFAULT_CONDITION_NAME_PROPERTY, conditionName); Object newValue = getDefaultConditionName(); propertyChangeSupport.firePropertyChange(DEFAULT_CONDITION_NAME_PROPERTY, oldValue, newValue); } public String getDefaultConditionName() { return PREFERENCES.get(DEFAULT_CONDITION_NAME_PROPERTY, EventContainsCondition.DESCRIPTION); } /** * The StartupApplicationPath is initialized on application startup via ApplicationPreferences.getApplicationPath. * If a part of the application needs the application path it should *always* use this method instead of * getApplicationPath() since the application path might change while this one will always stay * the same. * * A switch of the application path while the application is running isn't safe so it's changed for real * upon next restart. * * @return the application path at startup time. */ public File getStartupApplicationPath() { return startupApplicationPath; } public void setUsingInternalFrames(boolean usingInternalFrames) { Object oldValue = isUsingInternalFrames(); PREFERENCES.putBoolean(USING_INTERNAL_FRAMES_PROPERTY, usingInternalFrames); Object newValue = isUsingInternalFrames(); propertyChangeSupport.firePropertyChange(USING_INTERNAL_FRAMES_PROPERTY, oldValue, newValue); } public boolean isUsingInternalFrames() { return PREFERENCES.getBoolean(USING_INTERNAL_FRAMES_PROPERTY, DEFAULT_VALUES.isUsingInternalFrames()); } public void setAutoFocusingWindow(boolean autoFocusingWindow) { Object oldValue = isAutoFocusingWindow(); PREFERENCES.putBoolean(AUTO_FOCUSING_WINDOW_PROPERTY, autoFocusingWindow); Object newValue = isAutoFocusingWindow(); propertyChangeSupport.firePropertyChange(AUTO_FOCUSING_WINDOW_PROPERTY, oldValue, newValue); } public boolean isAutoFocusingWindow() { return PREFERENCES.getBoolean(AUTO_FOCUSING_WINDOW_PROPERTY, DEFAULT_VALUES.isAutoFocusingWindow()); } /** * Whether or not the full path of recent files should be visible in the "Recent Files" menu. * I'm not going to add this to the preferences UI until someone screams for this option. * Default is false. * * @param showingFullRecentPath show full path for recent files */ public void setShowingFullRecentPath(boolean showingFullRecentPath) { Object oldValue = isShowingFullRecentPath(); PREFERENCES.putBoolean(SHOWING_FULL_RECENT_PATH_PROPERTY, showingFullRecentPath); Object newValue = isShowingFullRecentPath(); propertyChangeSupport.firePropertyChange(SHOWING_FULL_RECENT_PATH_PROPERTY, oldValue, newValue); } /** * Whether or not the full path of recent files should be visible in the "Recent Files" menu. * I'm not going to add this to the preferences UI until someone screams for this option. * Default is false. * * @return show full path for recent files */ public boolean isShowingFullRecentPath() { return PREFERENCES.getBoolean(SHOWING_FULL_RECENT_PATH_PROPERTY, DEFAULT_VALUES.isShowingFullRecentPath()); } public void setSourceNames(Map<String, String> sourceNames) { Object oldValue = getSourceNames(); writeSourceNames(sourceNames); Object newValue = getSourceNames(); propertyChangeSupport.firePropertyChange(SOURCE_NAMES_PROPERTY, oldValue, newValue); } public Map<String, String> getSourceNames() { File appPath = getStartupApplicationPath(); File sourceNamesFile = new File(appPath, SOURCE_NAMES_XML_FILENAME); if (sourceNamesFile.isFile()) { if (loadSourceNamesXml(sourceNamesFile)) { return new HashMap<>(sourceNames); } } sourceNamesFile = new File(appPath, SOURCE_NAMES_PROPERTIES_FILENAME); if (sourceNamesFile.isFile()) { if (loadSourceNamesProperties(sourceNamesFile)) { return new HashMap<>(sourceNames); } } return new HashMap<>(DEFAULT_SOURCE_NAMES); } public Map<String, String> getSoundLocations() { File appPath = getStartupApplicationPath(); File file = new File(appPath, SOUND_LOCATIONS_XML_FILENAME); if (file.isFile()) { if (loadSoundLocationsXml(file)) { return new HashMap<>(soundLocations); } } return new HashMap<>(DEFAULT_SOUND_LOCATIONS); } public void setSoundLocations(Map<String, String> soundLocations) { Object oldValue = getSoundLocations(); writeSoundLocations(soundLocations); Object newValue = getSoundLocations(); propertyChangeSupport.firePropertyChange(SOUND_LOCATIONS_PROPERTY, oldValue, newValue); } public void resetSoundLocations() { if (logger.isInfoEnabled()) logger.info("Initializing preferences with default sound locations."); setSoundLocations(DEFAULT_SOUND_LOCATIONS); } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void reset() { final Logger logger = LoggerFactory.getLogger(ApplicationPreferences.class); boolean licensed = isLicensed(); try { PREFERENCES.clear(); resetSoundLocations(); setLicensed(licensed); setApplicationPath(new File(DEFAULT_APPLICATION_PATH)); } catch (BackingStoreException e) { if (logger.isWarnEnabled()) logger.warn("Exception while clearing preferences!"); } } public void setScrollingToBottom(boolean scrollingToBottom) { Object oldValue = isScrollingToBottom(); PREFERENCES.putBoolean(SCROLLING_TO_BOTTOM_PROPERTY, scrollingToBottom); Object newValue = isScrollingToBottom(); propertyChangeSupport.firePropertyChange(SCROLLING_TO_BOTTOM_PROPERTY, oldValue, newValue); } public boolean isScrollingToBottom() { return PREFERENCES.getBoolean(SCROLLING_TO_BOTTOM_PROPERTY, DEFAULT_VALUES.isScrollingToBottom()); } private boolean loadSoundLocationsXml(File file) { long lastModified = file.lastModified(); if (soundLocations != null && lastSoundLocationsModified >= lastModified) { if (logger.isDebugEnabled()) logger.debug("Won't reload sound locations."); return true; } Map<String, String> props = loadPropertiesXml(file); if (props != null) { lastSoundLocationsModified = lastModified; // correct values, i.e. add missing keys DEFAULT_SOUND_LOCATIONS.entrySet().stream().filter(current -> !props.containsKey(current.getKey())) .forEach(current -> props.put(current.getKey(), "")); soundLocations = props; return true; } return false; } private boolean writeSoundLocations(Map<String, String> sourceNames) { File appPath = getStartupApplicationPath(); File file = new File(appPath, SOUND_LOCATIONS_XML_FILENAME); return writePropertiesXml(file, sourceNames, "Sound locations"); } private boolean loadSourceNamesXml(File file) { long lastModified = file.lastModified(); if (sourceNames != null && lastSourceNamesModified >= lastModified) { if (logger.isDebugEnabled()) logger.debug("Won't reload source names."); return true; } Map<String, String> props = loadPropertiesXml(file); if (props != null) { lastSourceNamesModified = lastModified; sourceNames = props; return true; } return false; } private boolean loadSourceNamesProperties(File sourceNamesFile) { long lastModified = sourceNamesFile.lastModified(); if (sourceNames != null && lastSourceNamesModified >= lastModified) { if (logger.isDebugEnabled()) logger.debug("Won't reload source names."); return true; } Map<String, String> props = loadProperties(sourceNamesFile); if (props != null) { lastSourceNamesModified = lastModified; sourceNames = props; return true; } return false; } private boolean writeSourceNames(Map<String, String> sourceNames) { File appPath = getStartupApplicationPath(); File file = new File(appPath, SOURCE_NAMES_XML_FILENAME); return writePropertiesXml(file, sourceNames, "Source names"); } private boolean writeSourceLists(Map<String, Set<String>> sourceLists) { File appPath = getStartupApplicationPath(); File file = new File(appPath, SOURCE_LISTS_XML_FILENAME); XMLEncoder e = null; Throwable error = null; try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); e = new XMLEncoder(bos); e.writeObject(sourceLists); } catch (FileNotFoundException ex) { error = ex; } finally { if (e != null) { e.close(); } } if (error != null) { if (logger.isWarnEnabled()) logger.warn("Exception while writing source lists!", error); return false; } return true; } private boolean writeConditions(List<SavedCondition> conditions) { File appPath = getStartupApplicationPath(); File file = new File(appPath, CONDITIONS_XML_FILENAME); XMLEncoder e = null; Throwable error = null; try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); e = new XMLEncoder(bos); e.writeObject(conditions); if (logger.isInfoEnabled()) logger.info("Wrote conditions {}.", conditions); } catch (FileNotFoundException ex) { error = ex; } finally { if (e != null) { e.close(); } } if (error != null) { if (logger.isWarnEnabled()) logger.warn("Exception while writing source lists!", error); return false; } return true; } /** * @param file the properties xml file * @return the resulting map * @noinspection MismatchedQueryAndUpdateOfCollection */ private Map<String, String> loadPropertiesXml(File file) { InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(file)); Properties props = new Properties(); props.loadFromXML(is); Map<String, String> result = new HashMap<>(); for (Object keyObj : props.keySet()) { String key = (String) keyObj; String value = (String) props.get(key); if (value != null) { result.put(key, value); } } return result; } catch (IOException e) { if (logger.isWarnEnabled()) logger.warn("Couldn't load properties from '" + file.getAbsolutePath() + "'!", e); } finally { IOUtilities.closeQuietly(is); } return null; } /** * @param file the properties xml file * @param stringStringMap the map to be written * @param comment the comment * @return whether or not the file could be written * @noinspection MismatchedQueryAndUpdateOfCollection */ private boolean writePropertiesXml(File file, Map<String, String> stringStringMap, String comment) { Properties output = new Properties(); for (Map.Entry<String, String> entry : stringStringMap.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if (value != null) { output.put(key, value); } } OutputStream os = null; Throwable error = null; try { os = new BufferedOutputStream(new FileOutputStream(file)); output.storeToXML(os, comment, StandardCharsets.UTF_8.toString()); } catch (IOException e) { error = e; } finally { IOUtilities.closeQuietly(os); } if (error != null) { if (logger.isWarnEnabled()) logger.warn("Exception while writing source names!", error); return false; } return true; } private Map<String, String> loadProperties(File file) { InputStream is = null; try { is = new BufferedInputStream(new FileInputStream(file)); Properties props = new Properties(); props.load(is); Map<String, String> result = new HashMap<>(); for (Object keyObj : props.keySet()) { String key = (String) keyObj; String value = (String) props.get(key); if (value != null) { result.put(key, value); } } return result; } catch (IOException e) { if (logger.isWarnEnabled()) logger.warn("Couldn't load properties from '" + file.getAbsolutePath() + "'!", e); } finally { IOUtilities.closeQuietly(is); } return null; } public void writeLoggingColumnLayout(boolean global, List<PersistentTableColumnModel.TableColumnLayoutInfo> layoutInfos) { File appPath = getStartupApplicationPath(); File file; if (global) { file = new File(appPath, LOGGING_LAYOUT_GLOBAL_XML_FILENAME); } else { file = new File(appPath, LOGGING_LAYOUT_XML_FILENAME); } writeColumnLayout(file, layoutInfos); } public void writeAccessColumnLayout(boolean global, List<PersistentTableColumnModel.TableColumnLayoutInfo> layoutInfos) { File appPath = getStartupApplicationPath(); File file; if (global) { file = new File(appPath, ACCESS_LAYOUT_GLOBAL_XML_FILENAME); } else { file = new File(appPath, ACCESS_LAYOUT_XML_FILENAME); } writeColumnLayout(file, layoutInfos); } public List<PersistentTableColumnModel.TableColumnLayoutInfo> readLoggingColumnLayout(boolean global) { File appPath = getStartupApplicationPath(); File file; if (global) { file = new File(appPath, LOGGING_LAYOUT_GLOBAL_XML_FILENAME); } else { file = new File(appPath, LOGGING_LAYOUT_XML_FILENAME); } return readColumnLayout(file); } public List<PersistentTableColumnModel.TableColumnLayoutInfo> readAccessColumnLayout(boolean global) { File appPath = getStartupApplicationPath(); File file; if (global) { file = new File(appPath, ACCESS_LAYOUT_GLOBAL_XML_FILENAME); } else { file = new File(appPath, ACCESS_LAYOUT_XML_FILENAME); } return readColumnLayout(file); } private boolean writeColumnLayout(File file, List<PersistentTableColumnModel.TableColumnLayoutInfo> layoutInfos) { XMLEncoder e = null; Throwable error = null; try { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); e = new XMLEncoder(bos); e.writeObject(layoutInfos); if (logger.isInfoEnabled()) logger.info("Wrote layouts {} to file '{}'.", layoutInfos, file.getAbsolutePath()); } catch (FileNotFoundException ex) { error = ex; } finally { if (e != null) { e.close(); } } if (error != null) { if (logger.isWarnEnabled()) logger.warn("Exception while writing layouts to file '" + file.getAbsolutePath() + "'!", error); return false; } return true; } private List<PersistentTableColumnModel.TableColumnLayoutInfo> readColumnLayout(File file) { XMLDecoder d = null; List<PersistentTableColumnModel.TableColumnLayoutInfo> result; try { d = new XMLDecoder(new BufferedInputStream(new FileInputStream(file))); result = transformToList(PersistentTableColumnModel.TableColumnLayoutInfo.class, d.readObject()); } catch (Throwable ex) { if (logger.isInfoEnabled()) logger.info("Exception while loading layouts from file '{}'':", file.getAbsolutePath(), ex.getMessage()); result = null; IOUtilities.interruptIfNecessary(ex); } finally { if (d != null) { d.close(); } } return result; } /** * Quick & dirty MD5 checksum function. * Returns null in case of error. * * @param input the input * @return the checksum */ public static byte[] getMD5(InputStream input) { if (input == null) { return null; } MessageDigest messageDigest; try { messageDigest = MessageDigest.getInstance("MD5"); byte[] buffer = new byte[1024]; for (;;) { int read = input.read(buffer); if (read < 0) { break; } messageDigest.update(buffer, 0, read); } return messageDigest.digest(); } catch (Throwable ex) { final Logger logger = LoggerFactory.getLogger(ApplicationPreferences.class); if (logger.isWarnEnabled()) logger.warn("Exception while calculating checksum!", ex); IOUtilities.interruptIfNecessary(ex); } finally { try { input.close(); } catch (IOException e) { // ignore } } return null; } public void flush() { try { PREFERENCES.flush(); } catch (BackingStoreException e) { if (logger.isWarnEnabled()) logger.warn("Exception while flushing preferences!", e); } } public boolean isReplacingOnApply(ActionEvent event) { // TODO make default behavior configurable. boolean result = false; if (event == null || (ActionEvent.SHIFT_MASK & event.getModifiers()) == 0) { result = true; } return result; } public void setUsingScreenMenuBar(boolean usingScreenMenuBar) { this.usingScreenMenuBar = usingScreenMenuBar; } public boolean isUsingScreenMenuBar() { return usingScreenMenuBar; } /** * As described in http://weblogs.java.net/blog/malenkov/archive/2006/08/how_to_encode_e.html */ static class EnumPersistenceDelegate extends PersistenceDelegate { protected boolean mutatesTo(Object oldInstance, Object newInstance) { return oldInstance == newInstance; } protected Expression instantiate(Object oldInstance, Encoder out) { Enum e = (Enum) oldInstance; return new Expression(e, e.getClass(), "valueOf", new Object[] { e.name() }); } } }