You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jitsi/src/net/java/sip/communicator/service/keybindings/Persistence.java

403 lines
14 KiB

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:<br>
* SERIAL_HASH- Serialized hash map of bindings. Ordering is preserved if
* available.<br>
* SERIAL_INPUT- Serialized input map of bindings.<br>
* PROPERTIES_PAIR- Persistence provided by java.util.Properties using plain
* text key/value pairs.<br>
* 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<KeyStroke, String> 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<KeyStroke, String> load(InputStream input)
throws IOException,
ParseException
{
LinkedHashMap<KeyStroke, String> output =
new LinkedHashMap<KeyStroke, String>();
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<KeyStroke, String> 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<KeyStroke, String> 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<KeyStroke, String> 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);
}
}