package com.comphenix.xp.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bukkit.configuration.ConfigurationSection;
import com.comphenix.xp.messages.Message;
import com.comphenix.xp.parser.primitives.StringParser;
import com.comphenix.xp.parser.text.ParameterParser;
import com.google.common.collect.Lists;
/**
* Represents a message parser.
* <p>
* Note that this parser, unlike most other parsers, may MODIFY any configuration section
* given to it. To avoid polluting the configuration file, pass it cloned sections (using
* the Utility.cloneSection() method).
* @author Kristian
*
*/
public class MessagesParser extends ConfigurationParser<List<Message>> {
private static final String MESSAGES_SETTING = "messages";
// For backwards compatibility
private static final String MESSAGE_TEXT_SETTING = "message";
private static final String MESSAGE_CHANNEL_SETTING = "channels";
private boolean consumeElements;
// Parsers
private StringListParser listParser;
private ParameterParser<String> channelsParser = new ParameterParser<String>(new StringParser());
/**
* Constructs a message parser with a default string list parser.
* @param consumeElements - whether or not to remove successfully read key-value pairs.
*/
public MessagesParser(boolean consumeElements) {
this(consumeElements, new StringListParser());
}
/**
* Constructs a message parser with a default string list parser.
* @param consumeElements - whether or not to remove successfully read key-value pairs.
* @param listParser - a list parser used to read the channel list.
*/
public MessagesParser(boolean consumeElements, StringListParser listParser) {
this.listParser = listParser;
this.consumeElements = consumeElements;
}
/**
* Use the default key names to parse a configuration section with messages.
* @param input - the configuration section to parse.
* @return List of parsed messages, or an empty list if no messages could be found.
*/
public List<Message> parse(ConfigurationSection input) throws ParsingException {
List<Message> result = new ArrayList<Message>();
List<Message> list = parse(input, MESSAGES_SETTING);
// Damn backwards compatibility
List<Message> backwards = parse(input, MESSAGE_TEXT_SETTING, MESSAGE_CHANNEL_SETTING);
if (list != null && list.size() > 0)
result.addAll(list);
if (backwards != null && backwards.size() > 0)
result.addAll(backwards);
return result;
}
// The more dynamic implementation
@Override
public List<Message> parse(ConfigurationSection input, String key) throws ParsingException {
List<Message> result = new ArrayList<Message>();
// Look for the key manually. This is really to preseve backwards compatbility.
for (String sub : input.getKeys(false).toArray(new String[0])) {
String enumed = Utility.getEnumName(sub);
// Note that we may also remove the element when it has been found
if (enumed.equalsIgnoreCase(key)) {
// This should be a configuration section itself
Object rawValue = input.get(sub);
if (rawValue instanceof ConfigurationSection) {
ConfigurationSection section = (ConfigurationSection) rawValue;
// Add every message in the list
for (String channelList : section.getKeys(false)) {
List<String> channels = channelsParser.parse(channelList);
String text = section.getString(channelList);
if (text != null)
result.add(new Message(text, channels));
else
throw ParsingException.fromFormat("The channel(s) %s has no message.", channelList);
}
} else {
throw ParsingException.fromFormat("Cannot parse multimessage - must be a dictionary of values.");
}
// It has now been successfully read. Remove it?
if (consumeElements) {
input.set(sub, null);
}
}
}
// Empty list if no message can be found
return result;
}
/**
* Converts the given configuration section into a message list.
* <p>
* Note that this method is used to support the old message syntax of:
* <pre>
* {@code
* mobs:
* zombie:
* default: 10
* message: 'A message'
* channels: [LOCAL]
* }
* </pre>
* @param input - source configuration section.
* @param messageKey - name of the message key.
* @param channelKey - the channels to broadcast this message.
* @return A list containing the parsed message, or an empty list if no message is found.
* @throws ParsingException - an error occured in parsing the message.
*/
public List<Message> parse(ConfigurationSection input, String messageKey, String channelKey) throws ParsingException {
// We don't care about the default value
return parse(input, messageKey, channelKey, Collections.<Message>emptyList(), true);
}
/**
* Converts the given configuration section into a message list.
* <p>
* Note that this method is used to support the old message syntax of:
* <pre>
* {@code
* mobs:
* zombie:
* default: 10
* message: 'A message'
* channels: [LOCAL]
* }
* </pre>
* @param input - source configuration section.
* @param messageKey - name of the message key.
* @param channelKey - the channels to broadcast this message.
* @return A list containing the parsed message, or an empty list if no message is found.
* @throws ParsingException - an error occured in parsing the message.
*/
public List<Message> parse(ConfigurationSection input, String messageKey, String channelKey, Message defaultValue) {
try {
return parse(input, messageKey, channelKey, Lists.newArrayList(defaultValue), false);
} catch (ParsingException e) {
// Should never happen
throw new IllegalStateException("A parsing exception occured unexpectedly.", e);
}
}
// Common implementation - simplifies things a bit
private List<Message> parse(ConfigurationSection input, String messageKey, String channelKey, List<Message> defaultValue, boolean throwIfError) throws ParsingException {
List<Message> result = new ArrayList<Message>();
String text = null;
List<String> channels = null;
for (String sub : input.getKeys(false).toArray(new String[0])) {
String enumed = Utility.getEnumName(sub);
// Note that we also remove each element that is found
if (enumed.equalsIgnoreCase(messageKey)) {
text = input.getString(sub);
// Distinguish between not found and invalid
if (text == null && throwIfError)
throw ParsingException.fromFormat("Unable to read message.");
} else if (enumed.equalsIgnoreCase(channelKey)) {
if (throwIfError)
channels = listParser.parse(input, sub);
else
channels = listParser.parseSafe(input, sub);
} else {
continue;
}
// An element was successfully read. Remove it?
if (consumeElements) {
input.set(sub, null);
}
}
// Handle missing message key
if (text != null) {
result.add(new Message(text, channels));
return result;
} else {
return defaultValue;
}
}
/**
* Returns whether or not to remove successfully read key-value pairs.
* @return TRUE if successfully read elements are removed, FALSE otherwise.
*/
public boolean isConsumeElements() {
return consumeElements;
}
}