blob: 7abe5a23244cf3d4f479c9533a076d529f561b6c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.karaf.shell.commands;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.felix.gogo.commands.Argument;
import org.apache.felix.gogo.commands.Command;
import org.apache.felix.gogo.commands.Option;
import org.apache.karaf.shell.console.AbstractAction;
/**
* Sort lines of text
*
* @version $Rev: 722776 $ $Date: 2008-12-03 05:59:59 +0100 (Wed, 03 Dec 2008) $
*/
@Command(scope = "shell", name = "sort", description = "Writes sorted concatenation of all files to standard output.")
public class SortAction extends AbstractAction {
@Option(name = "-f", aliases = { "-ignore-case" }, description = "fold lower case to upper case characters", required = false, multiValued = false)
private boolean caseInsensitive;
@Option(name = "-r", aliases = { "--reverse" }, description = "reverse the result of comparisons", required = false, multiValued = false)
private boolean reverse;
@Option(name = "-u", aliases = { "--unique" }, description = "output only the first of an equal run", required = false, multiValued = false)
private boolean unique;
@Option(name = "-t", aliases = { "--field-separator" }, description = "use SEP instead of non-blank to blank transition", required = false, multiValued = false)
private String separator;
@Option(name = "-b", aliases = { "--ignore-leading-blanks" }, description = "ignore leading blanks", required = false, multiValued = false)
private boolean ignoreBlanks;
@Option(name = "-k", aliases = { "--key" }, description = "Fields to use for sorting separated by whitespaces", required = false, multiValued = true)
private List<String> sortFields;
@Option(name = "-n", aliases = { "--numeric-sort" }, description = "compare according to string numerical value", required = false, multiValued = false)
private boolean numeric;
@Argument(index = 0, name = "files", description = "A list of files separated by whitespaces", required = false, multiValued = true)
private List<String> paths;
public Object doExecute() throws Exception {
if (paths != null && paths.size() > 0) {
List<String> lines = new ArrayList<String>();
for (String filename : paths) {
BufferedReader reader;
// First try a URL
try {
URL url = new URL(filename);
log.info("Printing URL: " + url);
reader = new BufferedReader(new InputStreamReader(url.openStream()));
}
catch (MalformedURLException ignore) {
// They try a file
File file = new File(filename);
log.info("Printing file: " + file);
reader = new BufferedReader(new FileReader(file));
}
try {
read(reader, lines);
}
finally {
try {
reader.close();
} catch (IOException e) {
// Ignore
}
}
}
sort(lines, System.out);
}
else {
sort(System.in, System.out);
}
return null;
}
protected void read(BufferedReader r, List<String> lines) throws Exception {
for (String s = r.readLine(); s != null; s = r.readLine()) {
lines.add(s);
}
}
protected void sort(InputStream input, PrintStream out) throws Exception {
List<String> strings = new ArrayList<String>();
BufferedReader r = new BufferedReader(new InputStreamReader(input));
read(r, strings);
sort(strings, out);
}
protected void sort(List<String> strings, PrintStream out) throws Exception {
char sep = (separator == null || separator.length() == 0) ? '\0' : separator.charAt(0);
Collections.sort(strings, new SortComparator(caseInsensitive, reverse, ignoreBlanks, numeric, sep, sortFields));
String last = null;
for (String s : strings) {
if (last == null) {
last = s;
} else if (!unique || !s.equals(last)) {
out.println(s);
}
}
}
public static class SortComparator implements Comparator<String> {
private boolean caseInsensitive;
private boolean reverse;
private boolean ignoreBlanks;
private boolean numeric;
private char separator;
private List<Key> sortKeys;
private static Pattern fpPattern;
static {
final String Digits = "(\\p{Digit}+)";
final String HexDigits = "(\\p{XDigit}+)";
final String Exp = "[eE][+-]?" + Digits;
final String fpRegex = "([\\x00-\\x20]*[+-]?(NaN|Infinity|(((" + Digits + "(\\.)?(" + Digits + "?)(" + Exp + ")?)|(\\.(" + Digits + ")(" + Exp + ")?)|(((0[xX]" + HexDigits + "(\\.)?)|(0[xX]" + HexDigits + "?(\\.)" + HexDigits + "))[pP][+-]?" + Digits + "))" + "[fFdD]?))[\\x00-\\x20]*)(.*)";
fpPattern = Pattern.compile(fpRegex);
}
public SortComparator(boolean caseInsensitive,
boolean reverse,
boolean ignoreBlanks,
boolean numeric,
char separator,
List<String> sortFields) {
this.caseInsensitive = caseInsensitive;
this.reverse = reverse;
this.separator = separator;
this.ignoreBlanks = ignoreBlanks;
this.numeric = numeric;
if (sortFields == null || sortFields.size() == 0) {
sortFields = new ArrayList<String>();
sortFields.add("1");
}
sortKeys = new ArrayList<Key>();
for (String f : sortFields) {
sortKeys.add(new Key(f));
}
}
public int compare(String o1, String o2) {
int res = 0;
List<Integer> fi1 = getFieldIndexes(o1);
List<Integer> fi2 = getFieldIndexes(o2);
for (Key key : sortKeys) {
int[] k1 = getSortKey(o1, fi1, key);
int[] k2 = getSortKey(o2, fi2, key);
if (key.numeric) {
Double d1 = getDouble(o1, k1[0], k1[1]);
Double d2 = getDouble(o2, k2[0], k2[1]);
res = d1.compareTo(d2);
} else {
res = compareRegion(o1, k1[0], k1[1], o2, k2[0], k2[1], key.caseInsensitive);
}
if (res != 0) {
if (key.reverse) {
res = - res;
}
break;
}
}
return res;
}
protected Double getDouble(String s, int start, int end) {
Matcher m = fpPattern.matcher(s.substring(start, end));
m.find();
return new Double(s.substring(0, m.end(1)));
}
protected int compareRegion(String s1, int start1, int end1, String s2, int start2, int end2, boolean caseInsensitive) {
int n1 = end1, n2 = end2;
for (int i1 = start1, i2 = start2; i1 < end1 && i2 < n2; i1++, i2++) {
char c1 = s1.charAt(i1);
char c2 = s2.charAt(i2);
if (c1 != c2) {
if (caseInsensitive) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
return c1 - c2;
}
}
} else {
return c1 - c2;
}
}
}
return n1 - n2;
}
protected int[] getSortKey(String str, List<Integer> fields, Key key) {
int start;
int end;
if (key.startField * 2 <= fields.size()) {
start = fields.get((key.startField - 1) * 2);
if (key.ignoreBlanksStart) {
while (start < fields.get((key.startField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(start))) {
start++;
}
}
if (key.startChar > 0) {
start = Math.min(start + key.startChar - 1, fields.get((key.startField - 1) * 2 + 1));
}
} else {
start = 0;
}
if (key.endField > 0 && key.endField * 2 <= fields.size()) {
end = fields.get((key.endField - 1) * 2);
if (key.ignoreBlanksEnd) {
while (end < fields.get((key.endField - 1) * 2 + 1) && Character.isWhitespace(str.charAt(end))) {
end++;
}
}
if (key.endChar > 0) {
end = Math.min(end + key.endChar - 1, fields.get((key.endField - 1) * 2 + 1));
}
} else {
end = str.length();
}
return new int[] { start, end };
}
protected List<Integer> getFieldIndexes(String o) {
List<Integer> fields = new ArrayList<Integer>();
if (o.length() > 0) {
if (separator == '\0') {
int i = 0;
fields.add(0);
for (int idx = 1; idx < o.length(); idx++) {
if (Character.isWhitespace(o.charAt(idx)) && !Character.isWhitespace(o.charAt(idx - 1))) {
fields.add(idx - 1);
fields.add(idx);
}
}
fields.add(o.length() - 1);
} else {
int last = -1;
for (int idx = o.indexOf(separator); idx >= 0; idx = o.indexOf(separator, idx + 1)) {
if (last >= 0) {
fields.add(last);
fields.add(idx - 1);
} else if (idx > 0) {
fields.add(0);
fields.add(idx - 1);
}
last = idx + 1;
}
if (last < o.length()) {
fields.add(last < 0 ? 0 : last);
fields.add(o.length() - 1);
}
}
}
return fields;
}
public class Key {
int startField;
int startChar;
int endField;
int endChar;
boolean ignoreBlanksStart;
boolean ignoreBlanksEnd;
boolean caseInsensitive;
boolean reverse;
boolean numeric;
public Key(String str) {
boolean modifiers = false;
boolean startPart = true;
boolean inField = true;
boolean inChar = false;
for (char c : str.toCharArray()) {
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (!inField && !inChar) {
throw new IllegalArgumentException("Bad field syntax: " + str);
}
if (startPart) {
if (inChar) {
startChar = startChar * 10 + (c - '0');
} else {
startField = startField * 10 + (c - '0');
}
} else {
if (inChar) {
endChar = endChar * 10 + (c - '0');
} else {
endField = endField * 10 + (c - '0');
}
}
break;
case '.':
if (!inField) {
throw new IllegalArgumentException("Bad field syntax: " + str);
}
inField = false;
inChar = true;
break;
case 'n':
inField = false;
inChar = false;
modifiers = true;
numeric = true;
break;
case 'f':
inField = false;
inChar = false;
modifiers = true;
caseInsensitive = true;
break;
case 'r':
inField = false;
inChar = false;
modifiers = true;
reverse = true;
break;
case 'b':
inField = false;
inChar = false;
modifiers = true;
if (startPart) {
ignoreBlanksStart = true;
} else {
ignoreBlanksEnd = true;
}
break;
case ',':
inField = true;
inChar = false;
startPart = false;
break;
default:
throw new IllegalArgumentException("Bad field syntax: " + str);
}
}
if (!modifiers) {
ignoreBlanksStart = ignoreBlanksEnd = SortComparator.this.ignoreBlanks;
reverse = SortComparator.this.reverse;
caseInsensitive = SortComparator.this.caseInsensitive;
numeric = SortComparator.this.numeric;
}
if (startField < 1) {
throw new IllegalArgumentException("Bad field syntax: " + str);
}
}
}
}
}