19 Commits
Author SHA1 Message Date
Thijs Wiefferink 669bfbd0fc Remove dead build server link 2024-05-19 23:22:42 +02:00
Thijs Wiefferink 3ce56e7a2a Set version to 1.1 2023-02-11 17:52:31 +01:00
Thijs Wiefferink e7749258ca Test github actions 2019-08-16 18:13:08 +02:00
Thijs Wiefferink 7aed184e8c Only add a prefix if the message is not empty 2018-03-09 12:01:59 +01:00
Thijs Wiefferink b5b7e9c1e5 Log warning for missing messages 2017-12-02 21:32:09 +01:00
Thijs Wiefferink c9a90bb352 Better limit management for argument and language variables
- Increment again for last round of argument and language replacements
- Limit and ReplacementLimitReachedException in separate files
- Add some replacement limit unit tests (more can be added later)
2017-10-23 16:53:20 +02:00
Thijs Wiefferink 90fa17d54d Improve logging during tests for Jenkins (don't use Logger.getGlobal()) 2017-10-19 15:56:33 +02:00
Thijs Wiefferink 255c1e4f60 Add tests for the Message class
- Test public static methods for creating a Message object
- Test isEmpty, prefix, append, prepend
- Get methods and variable replacement still needs to be tested
2017-10-19 15:48:40 +02:00
Thijs Wiefferink ddfef8ed50 Add YAMLMessageProvider and unit tests for it 2017-10-19 15:47:23 +02:00
Thijs Wiefferink 083da2228c Warn if a replacement is null, handle null message parts better 2017-10-16 15:14:59 +02:00
Thijs Wiefferink 73172b9b1a Cleanup variable replacements
- Allow ReplacementProvider returning a Message object
- Escape non-Message results from ReplacementProvider (might be user input or contain variables)
- null replacements do increase the index now (null replacements should never be used though)
2017-09-11 23:33:38 +02:00
Thijs Wiefferink 08ceec2c8e Add easy wrapping of replacements
- Replacement.name() to give a name to a replacement object (instead of the usual number)
- Replacement.prefix() to wrap a ReplacementProvider and add a prefix to the variables
2017-09-11 13:54:52 +02:00
Thijs Wiefferink 288c9a43e0 Fix Message#init() not saving the logger correctly 2017-09-01 23:27:21 +02:00
Thijs Wiefferink 5c42aa1cbe Merge remote-tracking branch 'origin/master' 2017-07-21 18:10:29 +02:00
Thijs Wiefferink b00e8fc946 Prevent crash when an argument replacement introduces its own variable again 2017-07-21 18:10:21 +02:00
Thijs Wiefferink fa8476db0b Set version in example to 1.1-SNAPSHOT 2017-07-21 16:57:17 +02:00
Thijs Wiefferink e8174c87a0 Improve javadoc 2017-07-21 16:55:59 +02:00
Thijs Wiefferink 678cf1030c Fix infinite replacement bug
- At one spot the replacement Limit object was not passed correctly, which could cause infinite replacements crashing the server
- Improve handling of reaching the limit, throw exception instead of checking in all places.
- Move logging class to library itself, so that InteractiveMessenger can also handle logging
2017-07-21 16:53:17 +02:00
Thijs Wiefferink b6066cc4e4 Change version to indicate working towards 1.1 2017-04-22 15:29:23 +02:00
21 changed files with 962 additions and 299 deletions

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
name: Build plugin
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn package --file pom.xml --update-snapshots --threads 8C -Dsources
+1 -2
View File
@@ -4,7 +4,6 @@ InteractiveMessenger
A format for text in config files to make use of the fancy Minecraft chat features like click, hover, suggest, etc.
## Information
* **Build server:** http://jenkins.wiefferink.me/job/InteractiveMessenger/
* **Javadocs:** https://wiefferink.me/InteractiveMessenger/javadocs
## Authors
@@ -29,7 +28,7 @@ A format for text in config files to make use of the fancy Minecraft chat featur
<dependency>
<groupId>me.wiefferink</groupId>
<artifactId>interactivemessenger</artifactId>
<version>1.0</version>
<version>1.1-SNAPSHOT</version>
</dependency>
</dependencies>
```
+1 -1
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>me.wiefferink</groupId>
<artifactId>interactivemessenger</artifactId>
<version>1.0</version>
<version>1.1</version>
<name>InteractiveMessenger</name>
<properties>
@@ -0,0 +1,139 @@
package me.wiefferink.interactivemessenger;
import org.apache.commons.lang.StringUtils;
import java.util.Collection;
import java.util.logging.Logger;
public class Log {
private static Logger logger;
/**
* Set the logger
* @param newLogger Logger to use
*/
public static void setLogger(Logger newLogger) {
logger = newLogger;
}
/**
* Print an information message to the console
* @param message The message to print
*/
public static void info(Object... message) {
String combined = StringUtils.join(message, " ");
if(logger != null) {
logger.info(combined);
} else {
System.out.println(combined);
}
}
/**
* Print a warning to the console
* @param message The message to print
*/
public static void warn(Object... message) {
String combined = StringUtils.join(message, " ");
if(logger != null) {
logger.warning(combined);
} else {
System.out.println("Warn: " + combined);
}
}
/**
* Print an error to the console
* @param message The message to print
*/
public static void error(Object... message) {
String combined = StringUtils.join(message, " ");
if(logger != null) {
logger.severe(combined);
} else {
System.err.println(combined);
}
}
/**
* Indent a message
* @param depth Number of indents to add, one indent is two spaces
* @param message Message to indent
* @return Original message with requested number of indents added
*/
public static Object[] indent(int depth, Object... message) {
String[] lines = StringUtils.join(message, " ").split("\\r?\\n");
for(int i = 0; i < lines.length; i++) {
lines[i] = StringUtils.leftPad("", depth * 2, " ") + lines[i];
}
return lines;
}
/**
* Print a string indented with a certain number of spaces
* @param indent The number of indents (each indent is 2 spaces)
* @param message Message to print (will be split on newline to apply indentation)
*/
public static void infoIndent(int indent, Object... message) {
for(Object line : indent(indent, message)) {
info(line);
}
}
/**
* Print a list of strings to the output with indentation
* @param indent The number of indents (each indent is 2 spaces)
* @param lines The string to print (will be split on newline to apply indentation)
*/
public static void infoIndent(int indent, Collection<String> lines) {
for(String part : lines) {
infoIndent(indent, part);
}
}
/**
* Print a string indented with a certain number of spaces
* @param indent The number of indents (each indent is 2 spaces)
* @param message Message to print (will be split on newline to apply indentation)
*/
public static void warnIndent(int indent, Object... message) {
for(Object line : indent(indent, message)) {
warn(line);
}
}
/**
* Print a list of strings to the output with indentation
* @param indent The number of indents (each indent is 2 spaces)
* @param lines The string to print (will be split on newline to apply indentation)
*/
public static void warnIndent(int indent, Collection<String> lines) {
for(String part : lines) {
warnIndent(indent, part);
}
}
/**
* Print a string indented with a certain number of spaces
* @param indent The number of indents (each indent is 2 spaces)
* @param message Message to print (will be split on newline to apply indentation)
*/
public static void errorIndent(int indent, Object... message) {
for(Object line : indent(indent, message)) {
error(line);
}
}
/**
* Print a list of strings to the output with indentation
* @param indent The number of indents (each indent is 2 spaces)
* @param lines The string to print (will be split on newline to apply indentation)
*/
public static void errorIndent(int indent, Collection<String> lines) {
for(String part : lines) {
errorIndent(indent, part);
}
}
}
@@ -4,10 +4,21 @@ import me.wiefferink.interactivemessenger.generators.ConsoleGenerator;
import me.wiefferink.interactivemessenger.message.InteractiveMessage;
import me.wiefferink.interactivemessenger.message.InteractiveMessagePart;
import me.wiefferink.interactivemessenger.message.TextMessagePart;
import me.wiefferink.interactivemessenger.message.enums.*;
import me.wiefferink.interactivemessenger.message.enums.Click;
import me.wiefferink.interactivemessenger.message.enums.Color;
import me.wiefferink.interactivemessenger.message.enums.Control;
import me.wiefferink.interactivemessenger.message.enums.Format;
import me.wiefferink.interactivemessenger.message.enums.Hover;
import org.bukkit.ChatColor;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -352,7 +363,7 @@ public class YamlParser {
* @param line The line to check
* @return true if the line is interactive, false when it is a text line
*/
private static boolean isTaggedInteractive(String line) {
public static boolean isTaggedInteractive(String line) {
return getInteractiveTag(line) != null;
}
@@ -0,0 +1,61 @@
package me.wiefferink.interactivemessenger.processing;
import me.wiefferink.interactivemessenger.Log;
/**
* Class to store a limit
*/
public class Limit {
public int left;
public int depth;
public boolean notified = false;
public Message message;
public long started;
/**
* Set the initial limit
* @param count The limit to use
* @param message The message this limit is started for
*/
public Limit(int count, Message message) {
this.left = count;
this.depth = 0;
this.message = message;
this.started = System.currentTimeMillis();
}
/**
* Decrease the limit
* @throws ReplacementLimitReachedException when the limit hits zero
*/
public void decrease() throws ReplacementLimitReachedException {
this.left--;
if(left <= 0) {
if(!notified) {
notified = true;
Log.error("Reached replacement limit, probably has replacements loops, problematic message key: " + message.getKey() + ", first characters of the message: " + Message.getMessageStart(message, 200));
}
throw new ReplacementLimitReachedException(this);
}
}
/**
* Increase the limit
*/
public void increase() {
this.left++;
}
/**
* Check if the limit is reached
* @return true if the limit is reached, otherwise false
*/
public boolean reached() {
return left <= 0;
}
@Override
public String toString() {
return "Limit(left: " + left + ", notified: " + notified + ", depth: " + depth + ", message.key: " + message.getKey() + ")";
}
}
@@ -1,5 +1,6 @@
package me.wiefferink.interactivemessenger.processing;
import me.wiefferink.interactivemessenger.Log;
import me.wiefferink.interactivemessenger.generators.ConsoleGenerator;
import me.wiefferink.interactivemessenger.generators.TellrawGenerator;
import me.wiefferink.interactivemessenger.parsers.YamlParser;
@@ -25,14 +26,13 @@ public class Message {
// CONFIGURATION
private static boolean useInteractiveMessages = true;
private static boolean useColorsInConsole = false;
private static Logger logger = null;
private static MessageProvider messageProvider = null;
// Define the symbols used for variables
public static final String VARIABLE_START = "%";
public static final String VARIABLE_END = "%";
public static final String LANGUAGE_KEY_PREFIX = "lang:";
public static final Pattern VARIABLE_PATTERN = Pattern.compile(Pattern.quote(VARIABLE_START) + "[a-zA-Z]+?" + Pattern.quote(VARIABLE_END));
public static final Pattern VARIABLE_PATTERN = Pattern.compile(Pattern.quote(VARIABLE_START) + "(?<name>[a-zA-Z]+)" + Pattern.quote(VARIABLE_END));
public static final Pattern LANGUAGE_VARIABLE_PATTERN = Pattern.compile(
Pattern.quote(VARIABLE_START) +
Pattern.quote(LANGUAGE_KEY_PREFIX) + "[a-zA-Z-]+?" + // Language key
@@ -58,7 +58,7 @@ public class Message {
// Language variable used to insert a prefix
public static final String CHATLANGUAGEVARIABLE = "prefix";
// Maximum number of replacement rounds (the replaced value can have variables again)
public static final int REPLACEMENTLIMIT = 100;
public static final int REPLACEMENTLIMIT = 200;
// Limit of the client is 32767 for the complete message
public static final int MAXIMUMJSONLENGTH = 30000;
// If sending a fancy message does not work we disable it for this run of the server
@@ -74,11 +74,11 @@ public class Message {
/**
* Initialize the Message class
* @param provider The provider to use for getting messages based on keys
* @param theLogger The logger to use for logging warning and error messages
* @param logger The logger to use for logging warning and error messages
*/
public static void init(MessageProvider provider, Logger theLogger) {
public static void init(MessageProvider provider, Logger logger) {
messageProvider = provider;
logger = theLogger;
Log.setLogger(logger);
}
/**
@@ -153,11 +153,9 @@ public class Message {
* Get the message with all replacements done
* @param limit the limit to hold to
* @return Message as a list
* @throws ReplacementLimitReachedException when the limit is reached
*/
private List<String> get(Limit limit) {
if(limit.reached()) {
return new ArrayList<>();
}
private List<String> get(Limit limit) throws ReplacementLimitReachedException {
doReplacements(limit);
return message;
}
@@ -171,6 +169,17 @@ public class Message {
return StringUtils.join(message, "");
}
/**
* Get the message with all replacements done
* @param limit Limit to use while processing
* @return Message as a string
* @throws ReplacementLimitReachedException when the limit is reached
*/
public String getSingle(Limit limit) throws ReplacementLimitReachedException {
doReplacements(limit);
return StringUtils.join(message, "");
}
/**
* Get the raw message without replacing anything
* @return The message
@@ -202,7 +211,7 @@ public class Message {
*/
public boolean isEmpty() {
for(String part : message) {
if(!part.isEmpty()) {
if(part != null && !part.isEmpty()) {
return false;
}
}
@@ -210,12 +219,12 @@ public class Message {
}
/**
* Add the default prefix to the message
* Add the default prefix to the message (if the message is not empty)
* @param doIt true if the prefix should be added, otherwise false
* @return this
*/
public Message prefix(boolean doIt) {
if(doIt) {
if(doIt && !isEmpty()) {
message.add(0, VARIABLE_START + LANGUAGE_KEY_PREFIX + CHATLANGUAGEVARIABLE + VARIABLE_END);
}
return this;
@@ -336,7 +345,7 @@ public class Message {
List<String> jsonMessages = TellrawGenerator.generate(YamlParser.parse(message));
for(String jsonMessage : jsonMessages) {
if(jsonMessage.length() > MAXIMUMJSONLENGTH) {
error("Message with key", key, "could not be send, results in a JSON string that is too big to send to the client, start of the message:", getMessageStart(this, 200));
Log.error("Message with key", key, "could not be send, results in a JSON string that is too big to send to the client, start of the message:", getMessageStart(this, 200));
return this;
}
result &= Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw "+((Player)target).getName()+" "+jsonMessage);
@@ -345,7 +354,7 @@ public class Message {
fancyWorks = result;
} catch(Exception e) {
fancyWorks = false;
error("Sending fancy message did not work, falling back to plain messages. Message key:", key, ", error:", ExceptionUtils.getStackTrace(e));
Log.error("Sending fancy message did not work, falling back to plain messages. Message key:", key, ", error:", ExceptionUtils.getStackTrace(e));
}
}
if(sendPlain) { // Fancy messages disabled or broken
@@ -368,15 +377,22 @@ public class Message {
((BufferedWriter)target).write(ChatColor.stripColor(plainMessage));
((BufferedWriter)target).newLine();
} catch(IOException e) {
warn("Exception while writing to BufferedWriter:", ExceptionUtils.getStackTrace(e));
Log.warn("Exception while writing to BufferedWriter:", ExceptionUtils.getStackTrace(e));
}
} else {
warn("Could not send message (key: "+key+") because the target ("+target.getClass().getName()+") is not recognized, message: "+plainMessage);
Log.warn("Could not send message (key: " + key + ") because the target (" + target.getClass().getName() + ") is not recognized, message: " + plainMessage);
}
}
return this;
}
/**
* Get the key that has been used to initialize this message (if any)
* @return Key used to create this message, or null if none
*/
public String getKey() {
return key;
}
// INTERNAL METHODS
@@ -401,7 +417,7 @@ public class Message {
private Message setMessageFromKey(String key) {
this.key = key;
if(messageProvider == null) {
error("Tried to get message with key", key+", but there is no MessageProvider!");
Log.error("Tried to get message with key", key + ", but there is no MessageProvider!");
} else {
this.setMessage(messageProvider.getMessage(key));
}
@@ -415,7 +431,9 @@ public class Message {
*/
private Message setMessage(String message) {
List<String> list = new ArrayList<>();
list.add(message);
if(message != null) {
list.add(message);
}
return this.setMessage(list);
}
@@ -425,63 +443,62 @@ public class Message {
*/
public Message doReplacements() {
Limit limit = new Limit(REPLACEMENTLIMIT, this);
doReplacements(limit);
try {
doReplacements(limit);
} catch(ReplacementLimitReachedException e) {
// Limit should have logged the error to the console already
}
//depthPrint(limit, "Replacing took", System.currentTimeMillis()-limit.started, "milliseconds", this);
return this;
}
private Message doReplacements(Limit limit) {
if(limit.reached()) {
return this;
}
/**
* Apply replacements to the message using a certain Limit (intended for internal and testing use)
* @param limit Limit to apply
* @return this
* @throws ReplacementLimitReachedException When the limit has been hit before replacement is finished
*/
public Message doReplacements(Limit limit) throws ReplacementLimitReachedException {
limit.depth++;
//depthPrint(limit, ">>> doReplacements:", message, limit);
// Replace LANGUAGE_VARIABLE_PATTERN until they are all gone, or when the limit is reached
// Replace LANGUAGE_VARIABLE_PATTERN until they are all gone, nothing changes anymore or when the limit is reached
try {
List<String> outerOriginal;
// Repeat replacements for if language replacements introduced new LANGUAGE_VARIABLE_PATTERN
int fullRounds = 0;
do {
outerOriginal = new ArrayList<>(message);
List<String> innerOriginal;
limit.left--;
if(limit.reached()) {
break;
}
limit.decrease();
// Do argument replacements
do {
innerOriginal = new ArrayList<>(message);
if(limit.reached()) {
break;
}
limit.decrease();
replaceArgumentVariables(limit);
} while(!message.equals(innerOriginal));
limit.increase(); // Compensate last round that did nothing
// Do language replacements
if(doLanguageReplacements) {
do {
innerOriginal = new ArrayList<>(message);
if(limit.reached()) {
break;
}
limit.decrease();
replaceLanguageVariables(limit);
} while(!message.equals(innerOriginal));
}
fullRounds++;
limit.increase(); // Compensate last round that did nothing
} while(!message.equals(outerOriginal));
limit.increase(); // Compensate last round that did nothing
// Increase limit by one to compensate for the last round where no replacements have been done
if(!limit.reached() && fullRounds >= 1) {
limit.left++;
}
} catch(StackOverflowError e) {
limit.left = 0;
limit.notified = true;
error("Too many recursive replacements for message with key: "+limit.message.key+" (probably includes itself as replacement), start of the message: "+getMessageStart(limit.message, 200));
Log.error("Too many recursive replacements for message with key: " + limit.message.key + " (probably includes itself as replacement), start of the message: " + getMessageStart(limit.message, 200));
// Trigger exception
limit.decrease();
}
limit.depth--;
return this;
@@ -492,11 +509,12 @@ public class Message {
* The arguments to apply as replacements:
* - If it is a GeneralRegion the replacements of the region will be applied
* - Else the parameter will replace its number surrounded with VARIABLE_START and VARIABLE_END
* @throws ReplacementLimitReachedException when the limit is reached
*/
private void replaceArgumentVariables(Limit limit) {
private void replaceArgumentVariables(Limit limit) throws ReplacementLimitReachedException {
limit.depth++;
//depthPrint(limit, ">>> replaceArgumentVariables:", message, limit);
if(message == null || message.size() == 0 || replacements == null || limit.reached()) {
if(message == null || message.size() == 0 || replacements == null) {
//depthPrint(limit, "quick return");
limit.depth--;
return;
@@ -506,90 +524,78 @@ public class Message {
int number = 0;
for(Object param : replacements) {
String line = message.get(i);
if(param != null) {
if(param instanceof ReplacementProvider) {
// Find the first non-escaped named variable
Matcher matcher = VARIABLE_PATTERN.matcher(line);
int startAt = 0;
while(matcher.find()) {
// Check for escaping
int beforeAt = matcher.start()-1;
if(beforeAt >= 0 && line.charAt(beforeAt) == YamlParser.ESCAPE_CHAR) {
//depthPrint(limit, "skipping named variable:", matcher.group(), limit);
continue;
}
//depthPrint(limit, "replacing named variable:", matcher.group());
// Insert replacement provided by the ReplacementProvider
Object replacement = ((ReplacementProvider)param).provideReplacement(matcher.group().substring(1, matcher.group().length()-1));
if(replacement != null) {
String result = "";
// Prefix
if(matcher.start() > 0) {
result += line.substring(0, matcher.start());
}
// Replacement
String add = replacement.toString();
result += add;
// Suffix
if(matcher.end() < line.length()) {
result += line.substring(matcher.end());
}
// Skip null, but increase index
if(param == null) {
Log.warn("null replacement for message", limit.message, "at index", number);
message.set(i, result);
line = result;
int matcherStart = matcher.start();
matcher = VARIABLE_PATTERN.matcher(line);
matcher.region(matcherStart+add.length(), line.length());
}
}
} else {
// Find first non-escaped numbered variable
Matcher matcher = getIndexPattern(number).matcher(line);
while(matcher.find()) {
// Check for escaping
int beforeAt = matcher.start()-1;
if(beforeAt >= 0 && line.charAt(beforeAt) == YamlParser.ESCAPE_CHAR) {
//depthPrint(limit, "skipping indexed variable:", matcher.group(), limit);
continue;
}
//depthPrint(limit, "replacing indexed variable:", matcher.group());
// Set param to an empty string to ensure the variable will be removed from the message (preventing loops)
param = "";
}
// Insert another Message
if(param instanceof Message) {
int startDiff = message.size()-i;
//depthPrint(limit, "insert message raw:", ((Message)param).message);
Message mParam = (Message)param;
// Match indexed or named variables
Matcher matcher;
boolean named;
if(param instanceof ReplacementProvider) {
matcher = VARIABLE_PATTERN.matcher(line);
named = true;
} else {
matcher = getIndexPattern(number).matcher(line);
named = false;
}
// Insert inline
if(mParam.inline) {
message.set(i, insert(line, mParam.getSingle(), matcher.start(), matcher.end()));
}
// Find an replace
while(matcher.find()) {
// Insert as message
else {
List<String> insertMessage = ((Message)param).get(limit);
//depthPrint(limit, "insert message resolved:", ((Message)param).message);
if(limit.reached()) {
limit.depth--;
return;
}
YamlParser.insertMessage(message, insertMessage, i, matcher.start(), matcher.end());
// Skip to end of insert
i = message.size()-startDiff;
}
}
// Insert a simple string
else {
// Insert it inline, assuming this might be user input, therefore escaping it
//depthPrint(limit, "insert string:", param.toString());
message.set(i, insert(line, YamlParser.escape(param.toString()), matcher.start(), matcher.end()));
}
break; // Maximum of one replacement
}
number++;
// Skip escaped matches
int beforeAt = matcher.start() - 1;
if(beforeAt >= 0 && line.charAt(beforeAt) == YamlParser.ESCAPE_CHAR) {
//depthPrint(limit, "skipping indexed variable:", matcher.group(), limit);
continue;
}
//depthPrint(limit, "replacing indexed variable:", matcher.group());
Object toInsert = param;
if(named) {
toInsert = ((ReplacementProvider) param).provideReplacement(matcher.group("name"));
// Skip if null
if(toInsert == null) {
break;
}
}
// Insert another Message
if(toInsert instanceof Message) {
int startDiff = message.size() - i;
//depthPrint(limit, "insert message raw:", ((Message)toInsert).message);
Message toInsertMessage = (Message) toInsert;
// Insert inline
if(toInsertMessage.inline) {
message.set(i, insert(line, toInsertMessage.getSingle(limit), matcher.start(), matcher.end()));
}
// Insert as message
else {
List<String> insertMessage = toInsertMessage.get(limit);
//depthPrint(limit, "insert message resolved:", ((Message)toInsert).message);
YamlParser.insertMessage(message, insertMessage, i, matcher.start(), matcher.end());
// Skip to end of insert
i = Math.max(0, message.size() - startDiff);
}
}
// Insert a simple string
else {
// Insert it inline, assuming this might be user input, therefore escaping it
//depthPrint(limit, "insert string:", toInsert.toString());
message.set(i, insert(line, YamlParser.escape(toInsert.toString()), matcher.start(), matcher.end()));
}
break; // Maximum of one replacement`
}
if(!named) {
number++;
}
}
}
@@ -618,11 +624,12 @@ public class Message {
/**
* Replace all language LANGUAGE_VARIABLE_PATTERN in a message
* @throws ReplacementLimitReachedException when the limit is reached
*/
private void replaceLanguageVariables(Limit limit) {
private void replaceLanguageVariables(Limit limit) throws ReplacementLimitReachedException {
limit.depth++;
//depthPrint(limit, ">>> replaceLanguageVariables:", message, limit);
if(message == null || message.size() == 0 || limit.reached()) {
if(message == null || message.size() == 0) {
//depthPrint(limit, "quick return");
limit.depth--;
return;
@@ -663,10 +670,6 @@ public class Message {
// Insert message
int startDiff = message.size()-i;
List<String> insertMessage = insert.get(limit);
if(limit.reached()) {
limit.depth--;
return;
}
YamlParser.insertMessage(message, insertMessage, i, matcher.start(), matcher.end());
// Skip to end of insert
i = message.size()-startDiff;
@@ -681,56 +684,13 @@ public class Message {
return "Message(key:"+key+", message:"+message+")";
}
/**
* Class to store a limit
*/
private class Limit {
public int left;
public int depth;
public boolean notified = false;
public Message message;
public long started;
/**
* Set the initial limit
* @param count The limit to use
* @param message The message this limit is started for
*/
public Limit(int count, Message message) {
this.left = count;
this.depth = 0;
this.message = message;
this.started = System.currentTimeMillis();
}
/**
* Check if the limit is reached
* @return true if the limit is reached, otherwise false
*/
public boolean reached() {
boolean reached = left <= 0;
if(reached && !notified) {
notified = true;
error("Reached replacement limit, probably has replacements loops, problematic message key: "+message.key+", first characters of the message: "+getMessageStart(message, 200));
}
return reached;
}
@Override
public String toString() {
return "Limit(left: "+left+", notified: "+notified+", depth: "+depth+", message.key: "+message.key+")";
}
}
/**
* Get a start of the message with a maximum length
* @param message The message
* @param maximumLength The maximum length to return
* @return The start of the message with at most maximumLength characters
*/
private static String getMessageStart(Message message, int maximumLength) {
public static String getMessageStart(Message message, int maximumLength) {
String messageStart = "";
for(int i = 0; i < message.getRaw().size() && messageStart.length() < maximumLength; i++) {
messageStart += message.getRaw().get(i).substring(0, Math.min(maximumLength, message.getRaw().get(i).length()));
@@ -744,43 +704,6 @@ public class Message {
* @param message The message to print indented
*/
private static void depthPrint(Limit limit, Object... message) {
String indent = "";
for(int i = 0; i < limit.depth; i++) {
indent += " ";
}
Object[] iMessage = new Object[message.length+1];
iMessage[0] = indent;
System.arraycopy(message, 0, iMessage, 1, message.length);
info(iMessage);
}
/**
* Print information to the console
* @param message The message to print
*/
public static void info(Object... message) {
if(logger != null) {
logger.info(StringUtils.join(message, " "));
}
}
/**
* Print a warning to the console
* @param message The message to print
*/
public static void warn(Object... message) {
if(logger != null) {
logger.warning(StringUtils.join(message, " "));
}
}
/**
* Print an error to the console
* @param message The messagfe to print
*/
public static void error(Object... message) {
if(logger != null) {
logger.severe(StringUtils.join(message, " "));
}
Log.infoIndent(limit.depth, message);
}
}
@@ -0,0 +1,30 @@
package me.wiefferink.interactivemessenger.processing;
public class Replacement {
public static ReplacementProvider name(final String name, final Object replacement) {
return new ReplacementProvider() {
@Override
public Object provideReplacement(String variable) {
if(variable.equalsIgnoreCase(name)) {
return replacement;
} else {
return null;
}
}
};
}
public static ReplacementProvider prefix(final String prefix, final ReplacementProvider provider) {
return new ReplacementProvider() {
@Override
public Object provideReplacement(String variable) {
if(variable.startsWith(prefix)) {
return provider.provideReplacement(variable.substring(prefix.length()));
} else {
return null;
}
}
};
}
}
@@ -0,0 +1,13 @@
package me.wiefferink.interactivemessenger.processing;
public class ReplacementLimitReachedException extends Exception {
private Limit limit;
public ReplacementLimitReachedException(Limit limit) {
this.limit = limit;
}
public Limit getLimit() {
return limit;
}
}
@@ -7,7 +7,7 @@ public interface ReplacementProvider {
/**
* Get the replacement for a variable
* @param variable The variable to replace
* @return The replacement for the variable, or null if empty
* @return The replacement for the variable, or null if it has no replacement for it
*/
Object provideReplacement(String variable);
}
@@ -1,15 +1,27 @@
package me.wiefferink.interactivemessenger.source;
import com.google.common.base.Charsets;
import me.wiefferink.interactivemessenger.Log;
import me.wiefferink.interactivemessenger.processing.Message;
import me.wiefferink.interactivemessenger.translation.Transifex;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -60,6 +72,7 @@ public class LanguageManager implements MessageProvider {
message = defaultLanguage.get(key);
}
if(message == null) {
Log.warn("Did not find message '" + key + "' in the current or default language");
return new ArrayList<>();
}
return new ArrayList<>(message);
@@ -73,7 +86,7 @@ public class LanguageManager implements MessageProvider {
File langFolder;
if(!languageFolder.exists()) {
if(!languageFolder.mkdirs()) {
Message.warn("Could not create language directory: "+languageFolder.getAbsolutePath());
Log.warn("Could not create language directory: " + languageFolder.getAbsolutePath());
return;
}
}
@@ -102,14 +115,14 @@ public class LanguageManager implements MessageProvider {
output.write(bytes, 0, read);
}
} catch(IOException e) {
Message.warn("Something went wrong saving a default language file: "+targetFile.getAbsolutePath());
Log.warn("Something went wrong saving a default language file: " + targetFile.getAbsolutePath());
}
}
}
} catch(URISyntaxException e) {
Message.error("Failed to find location of jar file:", ExceptionUtils.getStackTrace(e));
Log.error("Failed to find location of jar file:", ExceptionUtils.getStackTrace(e));
} catch(IOException e) {
Message.error("Failed to read zip file:", ExceptionUtils.getStackTrace(e));
Log.error("Failed to read zip file:", ExceptionUtils.getStackTrace(e));
}
}
@@ -140,7 +153,7 @@ public class LanguageManager implements MessageProvider {
// Detect empty language files, happens when the YAML parsers prints an exception (it does return an empty YamlConfiguration though)
YamlConfiguration ymlFile = YamlConfiguration.loadConfiguration(reader);
if(ymlFile.getKeys(false).isEmpty()) {
Message.warn("Language file "+key+".yml has zero messages.");
Log.warn("Language file " + key + ".yml has zero messages.");
return result;
}
@@ -157,13 +170,13 @@ public class LanguageManager implements MessageProvider {
convertFromTransifex = true;
}
} catch(IOException e) {
Message.warn("Could not load language file: "+file.getAbsolutePath());
Log.warn("Could not load language file: " + file.getAbsolutePath());
}
// Do conversion (after block above closed the reader)
if(convertFromTransifex) {
if(!Transifex.convertFrom(file)) {
Message.warn("Failed to convert "+file.getName()+" from the Transifex layout to the AreaShop layout, check the errors above");
Log.warn("Failed to convert " + file.getName() + " from the Transifex layout to the AreaShop layout, check the errors above");
}
return loadLanguage(key, false);
}
@@ -1,11 +1,22 @@
package me.wiefferink.interactivemessenger.translation;
import me.wiefferink.interactivemessenger.processing.Message;
import me.wiefferink.interactivemessenger.Log;
import me.wiefferink.interactivemessenger.parsers.YamlParser;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* Convert to and from a format suitable for Transifex (online collaborative translation platform)
@@ -39,12 +50,12 @@ public class Transifex {
warn("Could not convert Transifix file, it does not exist");
return false;
}
Message.info("Transifex conversion of", inputFile.getAbsolutePath());
Log.info("Transifex conversion of", inputFile.getAbsolutePath());
File newFile = new File(inputFile.getAbsolutePath()+".new");
try(
BufferedReader reader = new BufferedReader(new FileReader(inputFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(newFile))
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), StandardCharsets.UTF_8))
) {
// Create target file
newFile.createNewFile();
@@ -133,7 +144,98 @@ public class Transifex {
oldFile.renameTo(inputFile); // Try to recover original file
return false;
}
Message.info("Converted "+inputFile.getName()+" from the Transifex layout to the AreaShop layout, original saved in "+oldFile.getName());
Log.info("Converted " + inputFile.getName() + " from the Transifex layout to the AreaShop layout, original saved in " + oldFile.getName());
return true;
}
/**
* Convert source language to format compatible for upload to transifex
* @param inputFile File to read the language file from
* @param outputFile File to output converted language file to
* @param languageCode Languagecode as used on Transifex
* @return true if it worked orrectly, otherwise false
*/
public static boolean exportTo(File inputFile, File outputFile, String languageCode) {
try(
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))
) {
// Header indicating the contained language to Transifex
writer.write("\"" + languageCode + "\":");
writer.newLine();
// Add all lines
String next = reader.readLine();
List<String> message = new ArrayList<>();
int lineCount = 0;
while(next != null) {
lineCount++;
String line = next;
next = reader.readLine();
// Strip UTF-8 BOM marker
if(lineCount == 1 && line.length() > 0 && line.charAt(0) == 0xFEFF) {
line = line.substring(1);
}
String trimmed = line.trim();
// Ignore comments
if(line.startsWith("#")) {
continue;
}
// Ignore empty lines
if(line.isEmpty()) {
continue;
}
// Part of a list of strings (an InteractiveMessage)
if(trimmed.startsWith("- ")) {
// Trim the list identifier
trimmed = trimmed.substring(2);
// Trim quotes from the message
if(trimmed.length() > 0 && trimmed.charAt(0) == '"' && trimmed.charAt(trimmed.length() - 1) == '"') {
trimmed = trimmed.substring(1, trimmed.length() - 1);
}
// Replace spaces in front of special lines by tabs (Transifex ignores spaces, while tabs show nicely, helping with easy translation)
if(YamlParser.isTaggedInteractive(trimmed)) {
trimmed = "\\t" + trimmed.replaceAll("^\\s+", "");
}
message.add(trimmed);
} else {
// Last message complete, write it to the result
if(message.size() > 0) {
String result = message.remove(0);
// Trim the " at the end
if(result.endsWith("\"")) {
result = result.substring(0, result.length() - 1);
} else {
result += " \"";
}
for(String messagePart : message) {
// Add newlines between parts
if(!result.endsWith("\"")) {
result += "\\n";
}
result += messagePart;
}
writer.write(" " + result + "\"");
writer.newLine();
}
// Start next message
message.clear();
message.add(trimmed);
}
}
} catch(IOException e) {
warn("Reformatting from '" + inputFile.getAbsolutePath() + "' to '" + outputFile.getAbsolutePath() + "' failed:", ExceptionUtils.getStackTrace(e));
return false;
}
return true;
}
@@ -142,6 +244,6 @@ public class Transifex {
* @param message The message to print
*/
private static void warn(String... message) {
Message.warn("[Transifex language file conversion]", StringUtils.join(message, " "));
Log.warn("[Transifex language file conversion]", StringUtils.join(message, " "));
}
}
@@ -0,0 +1,109 @@
package me.wiefferink.interactivemessenger.source;
import com.google.common.base.Charsets;
import me.wiefferink.interactivemessenger.Log;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class YAMLMessageProvider implements MessageProvider {
private Map<String, List<String>> messages;
/**
* Constructor
* @param file File to read the messages from
*/
public YAMLMessageProvider(File file) {
messages = loadLanguage(file);
}
/**
* Get the message for a certain key (result can be modified)
* @param key The key of the message to get
* @return The message as a list of strings
*/
@Override
public List<String> getMessage(String key) {
List<String> message = messages.get(key);
if(message == null) {
return new ArrayList<>();
}
return new ArrayList<>(message);
}
/**
* Loads the specified language
* @param file YAML rile to load from
* @return Map with the messages loaded from the file
*/
private Map<String, List<String>> loadLanguage(File file) {
HashMap<String, List<String>> result = new HashMap<>();
// Load the language file
try(
InputStreamReader reader = new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)
) {
// Detect staticEmpty language files, happens when the YAML parsers prints an exception (it does return an staticEmpty YamlConfiguration though)
YamlConfiguration ymlFile = YamlConfiguration.loadConfiguration(reader);
if(ymlFile.getKeys(false).isEmpty()) {
Log.warn("Language file has zero messages:", file.getAbsolutePath());
return result;
}
// Retrieve the messages from the YAML file and create the result
for(String messageKey : ymlFile.getKeys(false)) {
if(ymlFile.isList(messageKey)) {
result.put(messageKey, new ArrayList<>(ymlFile.getStringList(messageKey)));
} else {
result.put(messageKey, new ArrayList<>(Collections.singletonList(ymlFile.getString(messageKey))));
}
}
} catch(IOException e) {
Log.warn("Could not load language file: " + file.getAbsolutePath());
}
return result;
}
}
@@ -1,52 +0,0 @@
package me.wiefferink.interactivemessenger.testing;
import org.apache.commons.lang.StringUtils;
import java.util.List;
public class Log {
/**
* Print an information message to the console
* @param message The message to print
*/
public static void info(Object... message) {
System.out.println(StringUtils.join(message, " "));
}
/**
* Print an error to the console
* @param message The message to print
*/
public static void error(Object... message) {
System.err.println(StringUtils.join(message, " "));
}
/**
* Print a string indented with a certain number of spaces
* @param indent The number of indents (each indent is 2 spaces)
* @param print The string to print (will be split on newline to apply indentation)
*/
public static void printIndented(int indent, String print) {
String indentation = "";
for(int i = 0; i < indent; i++) {
indentation += " ";
}
String[] parts = print.split("\\r?\\n");
for(String part : parts) {
info(indentation+part);
}
}
/**
* Print a list of strings to the output with indentation
* @param indent The number of indents (each indent is 2 spaces)
* @param print The string to print (will be split on newline to apply indentation)
*/
public static void printIndented(int indent, List<String> print) {
for(String part : print) {
printIndented(indent, part);
}
}
}
@@ -4,10 +4,10 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import me.wiefferink.interactivemessenger.Log;
import me.wiefferink.interactivemessenger.generators.TellrawGenerator;
import me.wiefferink.interactivemessenger.message.InteractiveMessage;
import me.wiefferink.interactivemessenger.parsers.YamlParser;
import me.wiefferink.interactivemessenger.testing.Log;
import me.wiefferink.interactivemessenger.testing.parsergenerator.ParserGeneratorTestInput;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.Bukkit;
@@ -92,7 +92,7 @@ public class TestCase implements Comparable<TestCase> {
tellRawJsonOutput = gson.fromJson(jsonMessage, JsonElement.class);
testRun.console("Message structure:", parsedMessage);
testRun.console("Output line", line+":");
Log.printIndented(2, gson.toJson(tellRawJsonOutput));
Log.infoIndent(2, gson.toJson(tellRawJsonOutput));
result &= Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw "+testRun.getTester().getName()+" "+jsonMessage);
} catch(JsonSyntaxException e) {
result = false;
@@ -1,6 +1,6 @@
package me.wiefferink.interactivemessenger.testing.ingame;
import me.wiefferink.interactivemessenger.testing.Log;
import me.wiefferink.interactivemessenger.Log;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.bukkit.Bukkit;
@@ -9,14 +9,22 @@ import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -5,11 +5,11 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import junit.framework.TestCase;
import me.wiefferink.interactivemessenger.Log;
import me.wiefferink.interactivemessenger.generators.ConsoleGenerator;
import me.wiefferink.interactivemessenger.generators.TellrawGenerator;
import me.wiefferink.interactivemessenger.message.InteractiveMessage;
import me.wiefferink.interactivemessenger.parsers.YamlParser;
import me.wiefferink.interactivemessenger.testing.Log;
import me.wiefferink.interactivemessenger.testing.RunTests;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
@@ -57,11 +57,11 @@ public class ParserGeneratorTestCase extends TestCase {
}
Log.info(" Input:");
Log.printIndented(2, data.input);
Log.infoIndent(2, data.input);
Log.info(" Expected ConsoleGenerator output:");
Log.printIndented(2, data.expectedConsoleOutput);
Log.infoIndent(2, data.expectedConsoleOutput);
Log.info(" Expected TellrawGenerator output:");
Log.printIndented(2, data.expectedTellrawOutput);
Log.infoIndent(2, data.expectedTellrawOutput);
// Parse into InteractiveMessage
InteractiveMessage parsedMessage = YamlParser.parse(data.input);
@@ -70,7 +70,7 @@ public class ParserGeneratorTestCase extends TestCase {
// Generate result of ConsoleGenerator
String actualConsoleOutput = ConsoleGenerator.generate(parsedMessage);
Log.info(" Generated by ConsoleGenerator:");
Log.printIndented(2, actualConsoleOutput);
Log.infoIndent(2, actualConsoleOutput);
// Generate result of TellrawGenerator
List<String> actualTellrawOutputList = TellrawGenerator.generate(parsedMessage);
@@ -85,7 +85,7 @@ public class ParserGeneratorTestCase extends TestCase {
return;
}
Log.info(" Generated by TellrawGenerator:");
Log.printIndented(2, gson.toJson(actualTellrawOutputJson));
Log.infoIndent(2, gson.toJson(actualTellrawOutputJson));
// Parse expected output into Json
JsonElement expectedTellrawOutputJson;
@@ -1,7 +1,7 @@
package me.wiefferink.interactivemessenger.testing.parsergenerator;
import junit.framework.TestSuite;
import me.wiefferink.interactivemessenger.testing.Log;
import me.wiefferink.interactivemessenger.Log;
import java.io.File;
@@ -0,0 +1,188 @@
package me.wiefferink.interactivemessenger.testing.unit;
import me.wiefferink.interactivemessenger.processing.Limit;
import me.wiefferink.interactivemessenger.processing.Message;
import me.wiefferink.interactivemessenger.processing.ReplacementLimitReachedException;
import me.wiefferink.interactivemessenger.source.MessageProvider;
import me.wiefferink.interactivemessenger.source.YAMLMessageProvider;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
public class TestMessage {
// Max 10 seconds per method, to catch infinite recursion failures
@Rule
public Timeout globalTimeout = new Timeout(10 * 1000);
@BeforeClass
public static void initMessage() {
URL resource = TestMessage.class.getClassLoader().getResource("processing/messages.yml");
assertNotNull("Resource file messages.yml not found in the jar", resource);
MessageProvider provider = new YAMLMessageProvider(new File(resource.getFile()));
Message.init(provider, null);
}
@Test
public void staticEmpty() {
Message message = Message.empty();
assertTrue(message.isEmpty());
assertTrue(message.get().isEmpty());
assertTrue(message.getPlain().isEmpty());
assertTrue(message.getSingle().isEmpty());
assertTrue(message.getSingleRaw().isEmpty());
assertTrue(message.getRaw().isEmpty());
}
private void testFromString(String message, String... expected) {
assertEquals(Arrays.asList(expected), Message.fromString(message).getRaw());
}
@Test
public void staticFromString() {
testFromString(null);
testFromString("", "");
testFromString("a", "a");
testFromString("abc", "abc");
}
private void testFromList(List<String> message, String... expected) {
assertEquals(Arrays.asList(expected), Message.fromList(message).getRaw());
}
@Test
public void staticFromList() {
testFromList(null);
testFromList(Collections.<String>emptyList());
testFromList(Collections.singletonList("a"), "a");
testFromList(Arrays.asList("", ""), "", "");
testFromList(Arrays.asList("abc", "def"), "abc", "def");
testFromList(Arrays.asList("abc", "", "def"), "abc", "", "def");
}
private void testFromKey(String key, String... expected) {
assertEquals(Arrays.asList(expected), Message.fromKey(key).getRaw());
}
@Test
public void staticFromKey() {
testFromKey(null);
testFromKey("doestNotExist");
testFromKey("single", "Hello world!");
testFromKey("multiple", "Hello ", "world!");
testFromKey("multilineWithNewlines1", "Hello world!\nSecond line\n");
testFromKey("multilineWithNewlines2", "Hello world!\nSecond line");
testFromKey("multilineWithNewlines3", "Hello world!\nSecond line\n\n");
testFromKey("multilineWithoutNewlines1", "Hello world! More text\n");
testFromKey("multilineWithoutNewlines2", "Hello world! More text");
testFromKey("multilineWithoutNewlines3", "Hello world! More text\n\n");
}
@Test
public void isEmpty() {
assertTrue(Message.fromString(null).isEmpty());
assertTrue(Message.fromString("").isEmpty());
assertFalse(Message.fromString("a").isEmpty());
assertTrue(Message.fromList(new ArrayList<String>()).isEmpty());
assertTrue(Message.fromList(Collections.singletonList("")).isEmpty());
assertFalse(Message.fromList(Collections.singletonList("a")).isEmpty());
assertTrue(Message.fromList(Arrays.asList("", "")).isEmpty());
assertFalse(Message.fromList(Arrays.asList("a", "")).isEmpty());
assertFalse(Message.fromList(Arrays.asList("", "a")).isEmpty());
}
@Test
public void prefix() {
assertTrue(Message.empty().prefix().isEmpty());
assertTrue(Message.empty().prefix(false).isEmpty());
assertTrue(Message.empty().prefix(true).isEmpty());
assertTrue(Message.fromString("abc").prefix().getSingleRaw().length() > 3);
assertTrue(Message.fromString("abc").prefix().getSingleRaw().endsWith("abc"));
}
@Test
public void append() {
// Append a String
assertEquals(Collections.singletonList("abc"), Message.empty().append("abc").getRaw());
assertEquals(Arrays.asList("abc", "def"), Message.empty().append("abc").append("def").getRaw());
// Append a List
assertEquals(Collections.emptyList(), Message.empty().append(Collections.<String>emptyList()).getRaw());
assertEquals(Collections.singletonList("abc"), Message.empty().append(Collections.singletonList("abc")).getRaw());
assertEquals(Arrays.asList("a", "bc"), Message.empty().append(Arrays.asList("a", "bc")).getRaw());
assertEquals(Arrays.asList("a", "bc", "de", "f"), Message.empty().append(Arrays.asList("a", "bc")).append(Arrays.asList("de", "f")).getRaw());
// Append a Message
// TODO test if appended message has replacement already done? (they should be)
assertEquals(Collections.emptyList(), Message.empty().append(Message.empty()).getRaw());
assertEquals(Collections.singletonList("abc"), Message.empty().append(Message.fromString("abc")).getRaw());
assertEquals(Arrays.asList("a", "bc"), Message.empty().append(Message.fromList(Arrays.asList("a", "bc"))).getRaw());
assertEquals(Arrays.asList("a", "bc", "de", "f"), Message.empty().append(Message.fromList(Arrays.asList("a", "bc"))).append(Message.fromList(Arrays.asList("de", "f"))).getRaw());
}
@Test
public void prepend() {
// Append a String
assertEquals(Collections.singletonList("abc"), Message.empty().prepend("abc").getRaw());
assertEquals(Arrays.asList("def", "abc"), Message.empty().prepend("abc").prepend("def").getRaw());
// Append a List
assertEquals(Collections.emptyList(), Message.empty().prepend(Collections.<String>emptyList()).getRaw());
assertEquals(Collections.singletonList("abc"), Message.empty().prepend(Collections.singletonList("abc")).getRaw());
assertEquals(Arrays.asList("a", "bc"), Message.empty().prepend(Arrays.asList("a", "bc")).getRaw());
assertEquals(Arrays.asList("de", "f", "a", "bc"), Message.empty().prepend(Arrays.asList("a", "bc")).prepend(Arrays.asList("de", "f")).getRaw());
// Append a Message
// TODO test if prepended message has replacement already done? (they should be)
assertEquals(Collections.emptyList(), Message.empty().prepend(Message.empty()).getRaw());
assertEquals(Collections.singletonList("abc"), Message.empty().prepend(Message.fromString("abc")).getRaw());
assertEquals(Arrays.asList("a", "bc"), Message.empty().prepend(Message.fromList(Arrays.asList("a", "bc"))).getRaw());
assertEquals(Arrays.asList("de", "f", "a", "bc"), Message.empty().prepend(Message.fromList(Arrays.asList("a", "bc"))).prepend(Message.fromList(Arrays.asList("de", "f"))).getRaw());
}
private boolean reachesLimit(Message message) {
Limit limit = new Limit(100, message);
try {
message.doReplacements(limit);
System.out.println("reachesLimit: " + message.getKey() + " used " + (100 - limit.left));
return false;
} catch(ReplacementLimitReachedException e) {
return true;
}
}
@Test
public void replacementsLimit() {
// Language replacement
assertFalse(reachesLimit(Message.fromKey("limit-no")));
// Direct recursion
assertTrue(reachesLimit(Message.fromKey("limit-leftRecursive")));
assertTrue(reachesLimit(Message.fromKey("limit-rightRecursive")));
// Indirect recursion
assertTrue(reachesLimit(Message.fromKey("limit-leftIndirectRecursive")));
assertTrue(reachesLimit(Message.fromKey("limit-rightIndirectRecursive")));
// Argument recursion
assertTrue(reachesLimit(Message.fromKey("limit-arg").replacements(Message.fromKey("limit-arg"))));
}
}
@@ -0,0 +1,53 @@
package me.wiefferink.interactivemessenger.testing.unit;
import me.wiefferink.interactivemessenger.source.YAMLMessageProvider;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
public class TestYAMLMessageProvider {
private static YAMLMessageProvider provider;
@BeforeClass
public static void initMessage() {
URL resource = TestYAMLMessageProvider.class.getClassLoader().getResource("processing/messages.yml");
assertNotNull("Resource file messages.yml not found in the jar", resource);
provider = new YAMLMessageProvider(new File(resource.getFile()));
}
/**
* Compare the result of a MessageProvider with an expected list of values
* @param message Key of the message to test
* @param expected List of values that are expected
*/
private void test(String message, String... expected) {
assertEquals(Arrays.asList(expected), provider.getMessage(message));
}
@Test
public void simple() {
test("single", "Hello world!");
test("multiple", "Hello ", "world!");
}
@Test
public void multilineWithNewliens() {
test("multilineWithNewlines1", "Hello world!\nSecond line\n");
test("multilineWithNewlines2", "Hello world!\nSecond line");
test("multilineWithNewlines3", "Hello world!\nSecond line\n\n");
}
@Test
public void multilineWithoutNewliens() {
test("multilineWithoutNewlines1", "Hello world! More text\n");
test("multilineWithoutNewlines2", "Hello world! More text");
test("multilineWithoutNewlines3", "Hello world! More text\n\n");
}
}
@@ -0,0 +1,49 @@
########## MessageProvider
# Simple
single: "Hello world!"
multiple:
- "Hello "
- "world!"
# With newline, remove trailing blank lines, keep newline at the end
multilineWithNewlines1: |
Hello world!
Second line
# With newline, remove trailing blank lines, remove newline at the end
multilineWithNewlines2: |-
Hello world!
Second line
# With newline, with trailing blank lines, with newline at the end
multilineWithNewlines3: |+
Hello world!
Second line
# Without newline, remove trailing blank lines, keep newline at the end
multilineWithoutNewlines1: >
Hello world!
More text
# Without newline, remove trailing blank lines, remove newline at the end
multilineWithoutNewlines2: >-
Hello world!
More text
# Without newline, with trailing blank lines, with newline at the end
multilineWithoutNewlines3: >+
Hello world!
More text
# Empty line above is important
########## Message
limit-hello: "hello"
limit-world: "world"
limit-no: "A message with an insertion: %lang:limit-hello% %lang:limit-world%"
limit-leftRecursive: "%lang:limit-leftRecursive% Hello world!"
limit-rightRecursive: "Hello world! %lang:limit-rightRecursive%"
limit-leftIndirectRecursive: "%lang:limit-leftIndirectRecursiveLoop% Hello world!"
limit-rightIndirectRecursive: "Hello world! %lang:limit-rightIndirectRecursiveLoop%"
limit-leftIndirectRecursiveLoop: "%lang:limit-leftIndirectRecursive% Hello world!"
limit-rightIndirectRecursiveLoop: "Hello world! %lang:limit-rightIndirectRecursive%"
limit-arg: "argument: %0%"