ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java

165 lines
4.8 KiB
Java

/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.Range;
/**
* Used to parse ranges in CommandPacket.
*
* @author Kristian
*/
class RangeParser {
/**
* Parse a range from a given text.
* @param text - the text.
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
return getRanges(new ArrayDeque<String>(Arrays.asList(text)), legalRange);
}
/**
* Parse ranges from an array of elements.
* @param args - array of elements.
* @param offset - beginning offset.
* @param lastIndex - the last index of the array to read.
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
List<String> tokens = tokenizeInput(input);
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
for (int i = 0; i < tokens.size(); i++) {
Range<Integer> range;
String current = tokens.get(i);
String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null;
// Yoda equality is done for null-safety
if ("-".equals(current)) {
throw new IllegalArgumentException("A hyphen must appear between two numbers.");
} else if ("-".equals(next)) {
if (i + 2 >= tokens.size())
throw new IllegalArgumentException("Cannot form a range without a upper limit.");
// This is a proper range
range = Range.closed(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2)));
ranges.add(range);
// Skip the two next tokens
i += 2;
} else {
// Just a single number
range = Range.singleton(Integer.parseInt(current));
ranges.add(range);
}
// Validate ranges
if (!legalRange.encloses(range)) {
throw new IllegalArgumentException(range + " is not in the range " + range.toString());
}
}
return simplify(ranges, legalRange.upperEndpoint());
}
/**
* Simplify a list of ranges by assuming a maximum value.
* @param ranges - the list of ranges to simplify.
* @param maximum - the maximum value (minimum value is always 0).
* @return A simplified list of ranges.
*/
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
List<Range<Integer>> result = new ArrayList<Range<Integer>>();
boolean[] set = new boolean[maximum + 1];
int start = -1;
// Set every ID
for (Range<Integer> range : ranges) {
for (int id : ContiguousSet.create(range, DiscreteDomain.integers())) {
set[id] = true;
}
}
// Generate ranges from this set
for (int i = 0; i <= set.length; i++) {
if (i < set.length && set[i]) {
if (start < 0) {
start = i;
}
} else {
if (start >= 0) {
result.add(Range.closed(start, i - 1));
start = -1;
}
}
}
return result;
}
private static List<String> tokenizeInput(Deque<String> input) {
List<String> tokens = new ArrayList<String>();
// Tokenize the input
while (!input.isEmpty()) {
StringBuilder number = new StringBuilder();
String text = input.peek();
for (int j = 0; j < text.length(); j++) {
char current = text.charAt(j);
if (Character.isDigit(current)) {
number.append(current);
} else if (Character.isWhitespace(current)) {
// That's ok
} else if (current == '-') {
// Add the number token first
if (number.length() > 0) {
tokens.add(number.toString());
number.setLength(0);
}
tokens.add(Character.toString(current));
} else {
// We're no longer dealing with integers - quit
return tokens;
}
}
// Add the number token, if it hasn't already
if (number.length() > 0)
tokens.add(number.toString());
input.poll();
}
return tokens;
}
}