From 7b2fb5cf625bd92795b3fca0d1719753fb5e1be6 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Tue, 6 Jul 2010 08:57:32 +0000 Subject: Moves Persistence.java from plugin/keybindingchooser/chooser to service/keybindings because it is what its package says and Eclipse freaks out (while Ant does not). --- .../service/keybindings/Persistence.java | 402 +++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 src/net/java/sip/communicator/service/keybindings/Persistence.java (limited to 'src/net/java/sip/communicator/service/keybindings') diff --git a/src/net/java/sip/communicator/service/keybindings/Persistence.java b/src/net/java/sip/communicator/service/keybindings/Persistence.java new file mode 100644 index 0000000..a556eb7 --- /dev/null +++ b/src/net/java/sip/communicator/service/keybindings/Persistence.java @@ -0,0 +1,402 @@ +package net.java.sip.communicator.service.keybindings; + +import java.io.*; +import java.util.*; +import java.text.ParseException; + +import javax.swing.InputMap; +import javax.swing.KeyStroke; + +/** + * Convenience methods providing a quick means of loading and saving + * keybindings. None preserve disabled mappings. The formats provided are as + * follows:
+ * SERIAL_HASH- Serialized hash map of bindings. Ordering is preserved if + * available.
+ * SERIAL_INPUT- Serialized input map of bindings.
+ * PROPERTIES_PAIR- Persistence provided by java.util.Properties using plain + * text key/value pairs.
+ * PROPERTIES_XML- Persistence provided by java.util.Properties using its XML + * format. + * + * @author Damian Johnson (atagar1@gmail.com) + * @version September 21, 2007 + */ +public enum Persistence +{ + SERIAL_HASH, SERIAL_INPUT, PROPERTIES_PAIRS, PROPERTIES_XML; + + private static final String PROPERTIES_COMMENT = + "Keybindings (mapping of KeyStrokes to string representations of actions)"; + + /** + * Returns the enum representation of a string. This is case sensitive. + * + * @param str toString representation of this enum + * @return enum associated with a string + * @throws IllegalArgumentException if argument is not represented by this + * enum. + */ + public static Persistence fromString(String str) + { + for (Persistence type : Persistence.values()) + { + if (str.equals(type.toString())) + return type; + } + throw new IllegalArgumentException(); + } + + /** + * Attempts to load this type of persistent keystroke map from a given path. + * This is unable to parse any null content. + * + * @param path absolute path to resource to be loaded + * @return keybinding map reflecting file contents + * @throws IOException if unable to load resource + * @throws ParseException if unable to parse content + */ + public LinkedHashMap load(String path) + throws IOException, + ParseException + { + return load(new FileInputStream(path)); + } + + /** + * Attempts to load this type of persistent keystroke map from a given + * stream. This is unable to parse any null content. + * + * @param input source of keybindings to be parsed + * @return keybinding map reflecting file contents + * @throws IOException if unable to load resource + * @throws ParseException if unable to parse content + */ + public LinkedHashMap load(InputStream input) + throws IOException, + ParseException + { + LinkedHashMap output = + new LinkedHashMap(); + + if (this == SERIAL_HASH || this == SERIAL_INPUT) + { + Object instance = null; // Loaded serialized object + + try + { + ObjectInputStream objectInput = new ObjectInputStream(input); + instance = objectInput.readObject(); + objectInput.close(); + } + catch (ClassNotFoundException exc) + { + throw new ParseException("Unable to load serialized content", 0); + } + + if (this == SERIAL_HASH) + { + if (!(instance instanceof HashMap)) + { + throw new ParseException( + "Serialized resource doesn't represent a HashMap", 0); + } + + HashMap mapping = (HashMap) instance; + for (Object key : mapping.keySet()) + { + Object value = mapping.get(key); + + if (key instanceof KeyStroke && value instanceof String) + { + output.put((KeyStroke) key, (String) value); + } + else + { + if (key == null || value == null) + { + throw new ParseException( + "Unable to load null content", 0); + } + else + { + StringBuilder message = new StringBuilder(); + message + .append("Entry doesn't represent a keybinding: "); + message.append(key.getClass().getName()); + message.append(" -> "); + message.append(value.getClass().getName()); + message + .append("\nMust match KeyStroke -> String mapping"); + throw new ParseException(message.toString(), 0); + } + } + } + } + else + { + if (!(instance instanceof InputMap)) + { + throw new ParseException( + "Serialized resource doesn't represent an InputMap", 0); + } + + InputMap mapping = (InputMap) instance; + if (mapping.keys() != null) + { + for (KeyStroke shortcut : mapping.keys()) + { + if (shortcut == null || mapping.get(shortcut) == null) + { + throw new ParseException( + "Unable to load null content", 0); + } + else + { + output.put(shortcut, mapping.get(shortcut) + .toString()); + } + } + } + } + } + else if (this == PROPERTIES_PAIRS || this == PROPERTIES_XML) + { + Properties properties = new Properties(); + if (this == PROPERTIES_PAIRS) + properties.load(input); + else if (this == PROPERTIES_XML) + properties.loadFromXML(input); + + for (Object key : properties.keySet()) + { + Object value = properties.get(key); + + if (key instanceof String && value instanceof String) + { + KeyStroke keystroke = KeyStroke.getKeyStroke((String) key); + if (keystroke == null) + { + StringBuilder message = new StringBuilder(); + message + .append("Unable to parse keystroke, see the getKeyStroke(String) method of "); + message.append(KeyStroke.class.getName()); + message.append(" for proper format"); + throw new ParseException(message.toString(), 0); + } + else + { + output.put(keystroke, (String) value); + } + } + else + { + if (key == null || value == null) + { + throw new ParseException("Unable to load null content", + 0); + } + else + { + StringBuilder message = new StringBuilder(); + message + .append("Entry doesn't represent a keybinding: "); + message.append(key.getClass().getName()); + message.append(" -> "); + message.append(value.getClass().getName()); + message + .append("\nMust match String -> String mapping where the first string represents a keystroke"); + throw new ParseException(message.toString(), 0); + } + } + } + } + + input.close(); + return output; + } + + /** + * Writes the persistent state of the bindings to an output stream. + * + * @param output stream where persistent state should be written + * @param bindings keybindings to be saved + * @throws IOException if unable to save bindings + * @throws UnsupportedOperationException if any keys or values of the + * binding are null + */ + public void save(OutputStream output, Map bindings) + throws IOException + { + for (KeyStroke key : bindings.keySet()) + { + if (key == null || bindings.get(key) == null) + { + throw new UnsupportedOperationException( + "Invalid binding: Shortcuts and actions cannot be null"); + } + } + + if (this == SERIAL_HASH || this == SERIAL_INPUT) + { + Object mapping; // Mapping to be serialized + if (this == SERIAL_HASH) + mapping = bindings; + else + { + InputMap inputMap = new InputMap(); + for (KeyStroke shortcut : bindings.keySet()) + { + inputMap.put(shortcut, bindings.get(shortcut)); + } + mapping = inputMap; + } + + ObjectOutputStream objectOutput = new ObjectOutputStream(output); + objectOutput.writeObject(mapping); + objectOutput.flush(); + objectOutput.close(); + } + else if (this == PROPERTIES_PAIRS || this == PROPERTIES_XML) + { + Properties properties = new Properties(); + for (KeyStroke shortcut : bindings.keySet()) + { + properties.setProperty(shortcut.toString(), bindings + .get(shortcut)); + } + + if (this == PROPERTIES_PAIRS) + properties.store(output, PROPERTIES_COMMENT); + else + properties.storeToXML(output, PROPERTIES_COMMENT); + } + } + + /** + * Writes the persistent state of the bindings to a file. + * + * @param path absolute path to where bindings should be saved + * @param bindings keybindings to be saved + * @throws IOException if unable to save bindings + * @throws UnsupportedOperationException if any keys or values of the + * binding are null + */ + public void save(String path, Map bindings) + throws IOException + { + FileOutputStream output = new FileOutputStream(path); + try + { + save(output, bindings); + output.flush(); + output.close(); + } + catch (IOException exc) + { + output.flush(); + output.close(); + throw exc; + } + catch (UnsupportedOperationException exc) + { + output.flush(); + output.close(); + throw exc; + } + } + + /** + * Provides the textual output of what this persistence format would save + * given a set of bindings. This silently fails, returning null if unable to + * generate output from bindings. + * + * @param bindings bindings for which to generate saved output + * @return string reflecting what would be saved by this persistence format + */ + public String getOutput(Map bindings) + { + /*- + * This utilizes a rather lengthy chain of redirection to generate output as a string: + * PipedOutputStream -> + * PipedInputStream -> + * Scanner -> + * StringBuilder -> + * String + */ + + PipedOutputStream pipeOut = new PipedOutputStream(); + PipedInputStream pipeIn = new PipedInputStream(); + Scanner scanner = new Scanner(pipeIn); + + try + { + pipeOut.connect(pipeIn); + save(pipeOut, bindings); + pipeOut.flush(); + pipeOut.close(); + + StringBuilder builder = new StringBuilder(); + if (scanner.hasNextLine()) + builder.append(scanner.nextLine()); + while (scanner.hasNextLine()) + { + builder.append("\n"); + builder.append(scanner.nextLine()); + } + scanner.close(); + pipeIn.close(); + return builder.toString(); + } + catch (IOException exc) + { + return null; + } + catch (UnsupportedOperationException exc) + { + return null; + } + } + + @Override + public String toString() + { + if (this == SERIAL_HASH) + return "Serialized Hash Map"; + else if (this == SERIAL_INPUT) + return "Serialized Input Map"; + else if (this == PROPERTIES_XML) + return "Properties XML"; + else + return getReadableConstant(this.name()); + } + + /** + * Provides a more readable version of constant names. Spaces replace + * underscores and this changes the input to lowercase except the first + * letter of each word. For instance, "RARE_CARDS" would become + * "Rare Cards". + * + * @param input string to be converted + * @return reader friendly variant of constant name + */ + public static String getReadableConstant(String input) + { + char[] name = input.toCharArray(); + + boolean isStartOfWord = true; + for (int i = 0; i < name.length; ++i) + { + char chr = name[i]; + if (chr == '_') + name[i] = ' '; + else if (isStartOfWord) + name[i] = Character.toUpperCase(chr); + else + name[i] = Character.toLowerCase(chr); + isStartOfWord = chr == '_'; + } + + return new String(name); + } +} -- cgit v1.1