| /* |
| * 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); |
| } |
| } |
| } |
| } |
| |
| } |