/* * Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see <http://www.gnu.org/licenses/>. */ package com.tulskiy.musique.system.configuration; import java.awt.Color; import java.awt.Font; import java.awt.Rectangle; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import java.util.Formatter; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.logging.Logger; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.XMLConfiguration; import com.sun.org.apache.xml.internal.serialize.OutputFormat; import com.sun.org.apache.xml.internal.serialize.XMLSerializer; import com.tulskiy.musique.plugins.hotkeys.HotkeyConfiguration; import com.tulskiy.musique.system.Application; /** * Author: Denis Tulskiy * Date: Jun 15, 2010 */ public class Configuration extends XMLConfiguration { public static final int VERSION = 1; public static final String PROPERTY_INFO_VERSION = "info.version"; private Logger logger = Logger.getLogger(getClass().getName()); // TODO move to ConfigurationListener given by Configuration Commons private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); @Deprecated private Map<String, Object> map = new TreeMap<String, Object>(); { // Disable delimiter mechanism completely for this instance // // https://issues.apache.org/jira/browse/CONFIGURATION-268 // We might add a note in the javadoc suggesting that setDelimiterParsingDisabled(true) // is not recommended if list properties are used in attributes, // and that changing the list delimiter to an untypical character is preferred. // // http://commons.apache.org/configuration/userguide/howto_xml.html // Using the static setDefaultDelimiter() method of AbstractConfiguration // you can globally define a different delimiter character // or - by setting the delimiter to 0 - disabling this mechanism completely. setListDelimiter((char) 0); } @Override public void load(Reader reader) { logger.fine("Loading configuration"); try { super.load(reader); } catch (ConfigurationException e) { // error log disabled as we're gonna try to load deprecated configuration file of v0.2 // logger.severe("Failed to load configuration: " + e.getMessage()); convertOldConfiguration(reader); } int version = getInt(PROPERTY_INFO_VERSION, -1); if (version > VERSION) { logger.warning(String.format("Configuration of newer v%d found, but v%d is latest supported." + " Backward compatibility is not guaranteed.", version, VERSION)); } else if (version == -1) { logger.warning("Configuration of unknown version is loaded." + " Backward compatibility is not guaranteed."); } else { logger.config(String.format("Configuration of v%d is loaded.", version)); } } @SuppressWarnings("unchecked") @Deprecated private void convertOldConfiguration(Reader reader) { // reader has been used, so try to reposition read point to start try { reader.reset(); } catch (IOException e) { // just ignore, will try to read from where it is } loadFromCustomFormat(reader); setFile(new File(Application.getInstance().CONFIG_HOME, "config")); addProperty(PROPERTY_INFO_VERSION, VERSION); Iterator<Entry<String, Object>> entries = map.entrySet().iterator(); while (entries.hasNext()) { Entry<String, Object> entry = entries.next(); String key = /*"musique." +*/ entry.getKey(); if (entry.getValue() instanceof List) { List values = (List) entry.getValue(); if (key.equals("albumart.stubs")) { AlbumArtConfiguration.setStubs(values); } else if (key.equals("fileOperations.patterns")) { FileOperationsConfiguration.setPatterns(values); } else if (key.equals("hotkeys.list")) { HotkeyConfiguration.setHotkeysRaw(values); } else if (key.equals("library.folders")) { LibraryConfiguration.setFolders(values); } else if (key.equals("playlist.columns")) { PlaylistConfiguration.setColumnsRaw(values); } else if (key.equals("playlist.tabs.bounds")) { PlaylistConfiguration.setTabBoundsRaw(values); } else if (key.equals("playlists")) { PlaylistConfiguration.setPlaylistsRaw(values); } else { for (Object value : values) { addProperty(key, value); } } } else { if (key.equals("wavpack.encoder.hybrid.wvc")) { addProperty("encoder.wavpack.hybrid.wvc.enabled", entry.getValue()); } else if (key.equals("playlist.activePlaylist")) { addProperty("playlists.activePlaylist", entry.getValue()); } else if (key.equals("playlist.cursorFollowsPlayback")) { addProperty("playlists.cursorFollowsPlayback", entry.getValue()); } else if (key.equals("playlist.groupBy")) { addProperty("playlists.groupBy", entry.getValue()); } else if (key.equals("playlist.lastDir")) { addProperty("playlists.lastDir", entry.getValue()); } else if (key.equals("playlist.playbackFollowsCursor")) { addProperty("playlists.playbackFollowsCursor", entry.getValue()); } else if (key.equals("playlist.tabs.hideSingle")) { addProperty("playlists.tabs.hideSingle", entry.getValue()); } else if (key.startsWith("ape.encoder")) { addProperty(key.replace("ape.encoder", "encoder.ape"), entry.getValue()); } else if (key.startsWith("vorbis.encoder")) { addProperty(key.replace("vorbis.encoder", "encoder.vorbis"), entry.getValue()); } else if (key.startsWith("wavpack.encoder")) { addProperty(key.replace("wavpack.encoder", "encoder.wavpack"), entry.getValue()); } else { addProperty(key, entry.getValue()); } } } map.clear(); try { save(); } catch (ConfigurationException e) { logger.severe("Failed to save converted configuration: " + e.getMessage()); } } @Override public void save(Writer writer) { logger.fine("Saving configuration"); try { OutputFormat format = new OutputFormat(createDocument()); format.setLineWidth(65); format.setIndenting(true); format.setIndent(2); XMLSerializer serializer = new XMLSerializer(writer, format); serializer.serialize(getDocument()); writer.close(); } catch (ConfigurationException ce) { logger.severe("Failed to save configuration: " + ce.getMessage()); } catch (IOException ioe) { logger.severe("Failed to save configuration: " + ioe.getMessage()); } } // TODO remove when 0.3 released @Deprecated public void loadFromCustomFormat(Reader reader) { try { BufferedReader r = new BufferedReader(reader); ArrayList<String> array = null; String key = null; while (r.ready()) { String line = r.readLine(); if (line == null) break; if (line.startsWith(" ") && array != null) { array.add(line.trim()); } else { if (array != null) { if (array.size() > 0) map.put(key, array); array = null; } int index = line.indexOf(':'); if (index == -1) continue; key = line.substring(0, index); String value = line.substring(index + 1).trim(); if (value.isEmpty()) { array = new ArrayList<String>(); } else { map.put(key, value); } } } if (array != null) map.put(key, array); } catch (IOException e) { logger.severe("Failed to load configuration: " + e.getMessage()); } } @Deprecated /** * use addProperty instead */ public void add(String key, Object value) { Object old = get(key); addProperty(key, value == null ? null : value.toString()); changeSupport.firePropertyChange(key, old, value); } @Deprecated /** * use setProperty instead */ public void put(String key, Object value) { Object old = get(key); if (value == null) { remove(key); } else { setProperty(key, value.toString()); } changeSupport.firePropertyChange(key, old, value); } public void remove(String key) { clearTree(key); } @Deprecated /** * use getProperty instead */ public Object get(String key) { return getProperty(key); } public void setInt(String key, int value) { put(key, value); } public void setFloat(String key, float value) { put(key, value); } public void setString(String key, String value) { put(key, value); } public Color getColor(String key, Color def) { try { String s = getString(key).substring(1); return new Color(Integer.parseInt(s, 16)); } catch (Exception e) { setColor(key, def); return def; } } public void setColor(String key, Color value) { if (value == null) remove(key); else { String s = new Formatter().format( "#%06X", value.getRGB() & 0xFFFFFF).toString(); put(key, s); } } public Rectangle getRectangle(String key, Rectangle def) { try { String value = getString(key); String[] tokens = value.split(" "); if (tokens.length != 4) throw new NumberFormatException(); int[] values = new int[4]; for (int i = 0; i < tokens.length; i++) { String s = tokens[i]; values[i] = Integer.parseInt(s); } return new Rectangle(values[0], values[1], values[2], values[3]); } catch (Exception e) { setRectangle(key, def); return def; } } public void setRectangle(String key, Rectangle value) { if (value == null) remove(key); else { String s = new Formatter().format("%d %d %d %d", (int) value.getX(), (int) value.getY(), (int) value.getWidth(), (int) value.getHeight()).toString(); put(key, s); } } public Font getFont(String key, Font def) { try { String value = getString(key); String[] tokens = value.split(", "); return new Font(tokens[0], Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2])); } catch (Exception e) { setFont(key, def); return def; } } public void setFont(String key, Font value) { if (value == null) remove(key); else { String s = new Formatter().format( "%s, %d, %d", value.getName(), value.getStyle(), value.getSize()).toString(); put(key, s); } } public void setBoolean(String key, boolean value) { put(key, value); } public void setList(String key, List<?> values) { remove(key); if (values == null) { // TODO refactor when dev cycle finished (right now check implemented for debug in emergency case) logger.severe("Illegal argument (empty list). Please check calling code."); } for (Object value : values) { add(key, value); } } @SuppressWarnings({"unchecked"}) public <E extends Enum<E>> E getEnum(String key, E def) { String val = getString(key, def.name()); Class<E> clazz = (Class<E>) def.getClass(); return E.valueOf(clazz, val); } public <E extends Enum<E>> void setEnum(String key, E value) { setString(key, value.name()); } public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(propertyName, listener); } public void addPropertyChangeListener(String propertyName, boolean initialize, PropertyChangeListener listener) { addPropertyChangeListener(propertyName, listener); if (initialize) listener.propertyChange(new PropertyChangeEvent(this, propertyName, null, get(propertyName))); } public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } }