/* * 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.tools.ant.filters; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.types.Parameter; /** * <p> * Sort a file before and/or after the file. * </p> * * <p> * Examples: * </p> * * <pre> * <copy todir="build"> * <fileset dir="input" includes="*.txt"/> * <filterchain> * <sortfilter/> * </filterchain> * </copy> * </pre> * * <p> * Sort all files <code>*.txt</code> from <i>src</i> location and copy * them into <i>build</i> location. The lines of each file are sorted * in ascendant order comparing the lines via the * <code>String.compareTo(Object o)</code> method. * </p> * * <pre> * <copy todir="build"> * <fileset dir="input" includes="*.txt"/> * <filterchain> * <sortfilter reverse="true"/> * </filterchain> * </copy> * </pre> * * <p> * Sort all files <code>*.txt</code> from <i>src</i> location into reverse * order and copy them into <i>build</i> location. If reverse parameter has * value <code>true</code> (default value), then the output line of the files * will be in ascendant order. * </p> * * <pre> * <copy todir="build"> * <fileset dir="input" includes="*.txt"/> * <filterchain> * <filterreader classname="org.apache.tools.ant.filters.SortFilter"> * <param name="comparator" value="org.apache.tools.ant.filters.EvenFirstCmp"/> * </filterreader> * </filterchain> * </copy> * </pre> * * <p> * Sort all files <code>*.txt</code> from <i>src</i> location using as * sorting criterium <code>EvenFirstCmp</code> class, that sorts the file * lines putting even lines first then odd lines for example. The modified files * are copied into <i>build</i> location. The <code>EvenFirstCmp</code>, * has to an instanciable class via <code>Class.newInstance()</code>, * therefore in case of inner class has to be <em>static</em>. It also has to * implement <code>java.util.Comparator</code> interface, for example: * </p> * * <pre> * package org.apache.tools.ant.filters; * ...(omitted) * public final class EvenFirstCmp implements <b>Comparator</b> { * public int compare(Object o1, Object o2) { * ...(omitted) * } * } * </pre> * * <p>The example above is equivalent to:</p> * * <blockquote><pre> * <componentdef name="evenfirst" * classname="org.apache.tools.ant.filters.EvenFirstCmp"/> * <copy todir="build"> * <fileset dir="input" includes="*.txt"/> * <filterchain> * <sortfilter> * <evenfirst/> * </sortfilter> * </filterchain> * </copy> * </pre></blockquote> * * <p> If parameter <code>comparator</code> is present, then * <code>reverse</code> parameter will not be taken into account. </p> * * @since Ant 1.8.0 */ public final class SortFilter extends BaseParamFilterReader implements ChainableReader { /** Parameter name for reverse order. */ private static final String REVERSE_KEY = "reverse"; /** * Parameter name for specifying the comparator criteria via class that * implement <code>java.util.Comparator</code> interface. */ private static final String COMPARATOR_KEY = "comparator"; /** * Instance of comparator class to be used for sorting. */ private Comparator<? super String> comparator = null; /** * Controls if the sorting process will be in ascendant/descendant order. If * If has value <code>true</code>, then the line of the file will be * sorted on descendant order. Default value: <code>false</code>. It will * be considered only if <code>comparator</code> is <code>null</code>. */ private boolean reverse; /** * Stores the lines to be sorted. */ private List<String> lines; /** * Remaining line to be read from this filter, or <code>null</code> if the * next call to <code>read()</code> should read the original stream to * find the next matching line. */ private String line = null; private Iterator<String> iterator = null; /** * Constructor for "dummy" instances. * * @see BaseFilterReader#BaseFilterReader() */ public SortFilter() { super(); } /** * Creates a new filtered reader. * * @param in * A Reader object providing the underlying stream. Must not be * <code>null</code>. */ public SortFilter(final Reader in) { super(in); } /** * Returns the next character in the filtered stream. If the desired number * of lines have already been read, the resulting stream is effectively at * an end. Otherwise, the next character from the underlying stream is read * and returned. * * @return the next character in the resulting stream, or -1 if the end of * the resulting stream has been reached * * @exception IOException * if the underlying stream throws an IOException during * reading */ public int read() throws IOException { if (!getInitialized()) { initialize(); setInitialized(true); } int ch = -1; if (line != null) { /* * We are on the state: "reading the current line", lines are * already sorted */ ch = line.charAt(0); if (line.length() == 1) { line = null; } else { line = line.substring(1); } } else { if (lines == null) { // We read all lines and sort them lines = new ArrayList<String>(); for (line = readLine(); line != null; line = readLine()) { lines.add(line); } sort(); iterator = lines.iterator(); } if (iterator.hasNext()) { line = (String) iterator.next(); } else { line = null; lines = null; iterator = null; } if (line != null) { return read(); } } return ch; } /** * Creates a new SortReader using the passed in Reader for instantiation. * * @param rdr * A Reader object providing the underlying stream. Must not be * <code>null</code>. * * @return a new filter based on this configuration, but filtering the * specified reader */ public Reader chain(final Reader rdr) { SortFilter newFilter = new SortFilter(rdr); newFilter.setReverse(isReverse()); newFilter.setComparator(getComparator()); newFilter.setInitialized(true); return newFilter; } /** * Returns <code>true</code> if the sorting process will be in reverse * order, otherwise the sorting process will be in ascendant order. * * @return <code>true</code> if the sorting process will be in reverse * order, otherwise the sorting process will be in ascendant order. */ public boolean isReverse() { return reverse; } /** * Sets the sorting process will be in ascendant (<code>reverse=false</code>) * or to descendant (<code>reverse=true</code>). * * @param reverse * Boolean representing reverse ordering process. */ public void setReverse(boolean reverse) { this.reverse = reverse; } /** * Returns the comparator to be used for sorting. * * @return the comparator */ public Comparator<? super String> getComparator() { return comparator; } /** * Set the comparator to be used as sorting criterium. * * @param comparator * the comparator to set */ public void setComparator(Comparator<? super String> comparator) { this.comparator = comparator; } /** * Set the comparator to be used as sorting criterion as nested element. * * @param comparator * the comparator to set */ public void add(Comparator<? super String> comparator) { if (this.comparator != null && comparator != null) { throw new BuildException("can't have more than one comparator"); } setComparator(comparator); } /** * Scans the parameters list */ private void initialize() throws IOException { // get parameters Parameter[] params = getParameters(); if (params != null) { for (int i = 0; i < params.length; i++) { final String paramName = params[i].getName(); if (REVERSE_KEY.equals(paramName)) { setReverse(Boolean.valueOf(params[i].getValue()) .booleanValue()); continue; } if (COMPARATOR_KEY.equals(paramName)) { try { String className = (String) params[i].getValue(); @SuppressWarnings("unchecked") final Comparator<? super String> comparatorInstance = (Comparator<? super String>) (Class .forName(className).newInstance()); setComparator(comparatorInstance); continue; } catch (InstantiationException e) { throw new BuildException(e); } catch (IllegalAccessException e) { /* * Probably a inner non-static class, this this case is * not considered */ throw new BuildException(e); } catch (ClassNotFoundException e) { throw new BuildException(e); } catch (ClassCastException e) { throw new BuildException("Value of comparator attribute" + " should implement" + " java.util.Comparator" + " interface"); } catch (Exception e) { throw new BuildException(e); } } } } } /** * Sorts the read lines (<code>lines</code>) according to the sorting * criteria defined by the user. * */ private void sort() { if (comparator == null) { if (reverse) { Collections.sort(lines, new Comparator<String>() { public int compare(String s1, String s2) { return (-s1.compareTo(s2)); //NOSONAR } }); } else { Collections.sort(lines); } } else { Collections.sort(lines, comparator); } } }