org.assertj.swing.keystroke.KeyStrokeMappingsParser Maven / Gradle / Ivy
Show all versions of assertj-swing Show documentation
/*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2012-2018 the original author or authors.
*/
package org.assertj.swing.keystroke;
import static java.lang.Thread.currentThread;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Closeables.closeQuietly;
import static org.assertj.core.util.Lists.newArrayList;
import static org.assertj.core.util.Preconditions.checkNotNull;
import static org.assertj.core.util.Preconditions.checkNotNullOrEmpty;
import static org.assertj.core.util.Strings.concat;
import static org.assertj.core.util.Strings.quote;
import static org.assertj.swing.keystroke.KeyStrokeMapping.mapping;
import static org.assertj.swing.keystroke.KeyStrokeMappingProvider.NO_MASK;
import static org.assertj.swing.util.Maps.newHashMap;
import static org.fest.reflect.core.Reflection.field;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import org.assertj.core.util.VisibleForTesting;
import org.assertj.swing.exception.ParsingException;
import org.fest.reflect.exception.ReflectionError;
/**
*
* Creates {@link KeyStrokeMapping}s by parsing a text file.
*
*
*
* Mappings for the following characters:
*
*
* - Backspace
* - Delete
* - Enter
* - Escape
* - Tab
*
*
* will be automatically added and should not be included to the file to parse.
*
*
*
* The following is an example of a mapping file:
*
*
*
* a, A, NO_MASK
* A, A, SHIFT_MASK
* COMMA, COMMA, NO_MASK
*
*
*
* Each line represents a character-keystroke mapping where each value is separated by a comma.
*
*
*
* The first value represents the character to map. For example 'a' or 'A'. Since each field is separated by a comma, to
* map the ',' character we need to specify the text "COMMA."
*
*
*
* The second value represents the key code, which should be the name of a key code from {@link KeyEvent} without the
* prefix "VK_". For example, if the key code is {@link KeyEvent#VK_COMMA} we just need to specify "COMMA".
*
*
*
* The third value represents any modifiers to use, which should be the name of a modifier from a {@code InputEvent}.
* For example, if the modifier to use is {@code InputEvent.SHIFT_MASK} we need to specify "SHIFT_MASK". If no modifiers
* are necessary, we just specify "NO_MASK".
*
*
* @author Olivier DOREMIEUX
* @author Alex Ruiz
*/
public class KeyStrokeMappingsParser {
private static final Map SPECIAL_MAPPINGS = newHashMap();
static {
SPECIAL_MAPPINGS.put("COMMA", ',');
}
/**
*
* Creates a {@link KeyStrokeMappingProvider} containing all the character-keystroke mappings specified in the file
* with the given name.
*
*
*
* Note: This attempts to read the file using {@link ClassLoader#getResourceAsStream(String)}.
*
*
* @param fileName the name of the file to parse.
* @return the created {@code KeyStrokeMappingProvider}.
* @throws NullPointerException if the given name is {@code null}.
* @throws IllegalArgumentException if the given name is empty.
* @throws ParsingException if any error occurs during parsing.
* @see #parse(File)
*/
@Nonnull public KeyStrokeMappingProvider parse(@Nonnull String fileName) {
checkNotNullOrEmpty(fileName);
try {
return parse(fileAsStream(fileName));
} catch (IOException e) {
throw new ParsingException(concat("An I/O error ocurred while parsing file ", fileName), e);
}
}
@Nonnull private InputStream fileAsStream(String file) {
InputStream stream = currentThread().getContextClassLoader().getResourceAsStream(file);
if (stream == null) {
throw new ParsingException(String.format("Unable to open file %s", file));
}
return stream;
}
/**
* Creates a {@link KeyStrokeMappingProvider} containing all the character-keystroke mappings specified in the given
* file.
*
* @param file the file to parse.
* @return the created {@code KeyStrokeMappingProvider}.
* @throws NullPointerException if the given file is {@code null}.
* @throws AssertionError if the given file does not represent an existing file.
* @throws ParsingException if any error occurs during parsing.
*/
@Nonnull public KeyStrokeMappingProvider parse(@Nonnull File file) {
assertThat(file).isFile();
try {
return parse(fileAsStream(file));
} catch (IOException e) {
throw new ParsingException(concat("An I/O error ocurred while parsing file ", file), e);
}
}
@Nonnull private InputStream fileAsStream(@Nonnull File file) {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
String msg = String.format("The file %s was not found", file.getPath());
throw new ParsingException(msg, e);
}
}
@Nonnull private KeyStrokeMappingProvider parse(@Nonnull InputStream input) throws IOException {
List mappings = newArrayList();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
try {
String line = reader.readLine();
while (line != null) {
mappings.add(mappingFrom(line));
line = reader.readLine();
}
return new ParsedKeyStrokeMappingProvider(mappings);
} finally {
closeQuietly(reader);
}
}
@VisibleForTesting
@Nonnull
KeyStrokeMapping mappingFrom(@Nonnull String line) {
String[] parts = line.trim().split(",");
if (parts.length != 3) {
String msg = String.format("Line '%s' does not conform with pattern '{char}, {keycode}, {modifiers}'", line);
throw new ParsingException(msg);
}
char character = characterFrom(parts[0].trim());
int keyCode = keyCodeFrom(parts[1].trim());
int modifiers = modifiersFrom(parts[2].trim());
return mapping(character, keyCode, modifiers);
}
private static char characterFrom(@Nonnull String s) {
if (SPECIAL_MAPPINGS.containsKey(s)) {
return SPECIAL_MAPPINGS.get(s);
}
if (s.length() == 1) {
return s.charAt(0);
}
throw new ParsingException(String.format("The text '%s' should have a single character", s));
}
private static int keyCodeFrom(@Nonnull String s) {
try {
Integer keyCode = field("VK_" + s).ofType(int.class).in(KeyEvent.class).get();
return checkNotNull(keyCode);
} catch (ReflectionError e) {
throw new ParsingException(concat("Unable to retrieve key code from text ", quote(s)), e.getCause());
}
}
private static int modifiersFrom(@Nonnull String s) {
if ("NO_MASK".equals(s)) {
return NO_MASK;
}
try {
Integer modifiers = field(s).ofType(int.class).in(InputEvent.class).get();
return checkNotNull(modifiers);
} catch (ReflectionError e) {
throw new ParsingException(concat("Unable to retrieve modifiers from text ", quote(s)), e.getCause());
}
}
}