001    /**
002     * Jetrix TetriNET Server
003     * Copyright (C) 2005  Emmanuel Bourg
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License
007     * as published by the Free Software Foundation; either version 2
008     * of the License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
018     */
019    
020    package net.jetrix.filter;
021    
022    import java.io.*;
023    import java.util.*;
024    
025    /**
026     * An improved lexicographic comparator handling a number in a name as a single
027     * character. Unlike the lexicographic order where "foo1.txt" < "foo10.txt" < "foo2.txt"
028     * here we have "foo1.txt" < "foo2.txt" < "foo10.txt".
029     *
030     * todo: move this code to commons-io or commons-lang
031     *
032     * @since 0.3
033     *
034     * @author Emmanuel Bourg
035     * @version $Revision: 794 $, $Date: 2009-02-17 20:08:39 +0100 (Tue, 17 Feb 2009) $
036     */
037    public class FilenameComparator implements Comparator<File>
038    {
039        public int compare(File file1, File file2)
040        {
041            String name1 = file1.getAbsolutePath();
042            String name2 = file2.getAbsolutePath();
043    
044            int index1 = 0;
045            int index2 = 0;
046    
047            while (true)
048            {
049                String token1 = getToken(name1, index1);
050                String token2 = getToken(name2, index2);
051    
052                if (token1 == null && token2 == null)
053                {
054                    // no more tokens for each name, they are equal
055                    return 0;
056                }
057    
058                if (token1 == null)
059                {
060                    // the first name is shorter, it goes first
061                    return -1;
062                }
063    
064                if (token2 == null)
065                {
066                    // the second name is shorter, it goes first
067                    return 1;
068                }
069    
070                int comp = compareToken(token1, token2);
071                if (comp == 0)
072                {
073                    // the tokens are equal, move to the next tokens
074                    index1 = index1 + token1.length();
075                    index2 = index2 + token2.length();
076                }
077                else
078                {
079                    return comp;
080                }
081            }
082        }
083    
084        /**
085         * Extract from the string  the next token starting at the specified index.
086         *
087         * @param string the string to parse
088         * @param index  the beginning of the token
089         */
090        String getToken(String string, int index)
091        {
092            if (string == null || string.length() == 0 || index == string.length())
093            {
094                return null;
095            }
096            else
097            {
098                // are we parsing a string or a number ?
099                boolean type = Character.isDigit(string.charAt(index));
100    
101                // move forward until a different character type is detected
102                int end = index + 1;
103                while (end < string.length() && Character.isDigit(string.charAt(end)) == type)
104                {
105                    end++;
106                }
107    
108                return string.substring(index, end);
109            }
110        }
111    
112        /**
113         * Tells if the specified string is a number.
114         */
115        boolean isNumber(String string)
116        {
117            if (string == null || string.length() == 0)
118            {
119                return false;
120            }
121            else
122            {
123                return Character.isDigit(string.charAt(0));
124            }
125        }
126    
127        /**
128         * Compare two tokens according to their types (string or number).
129         */
130        int compareToken(String token1, String token2)
131        {
132            if (isNumber(token1) && isNumber(token2))
133            {
134                return Integer.parseInt(token1) - Integer.parseInt(token2);
135            }
136            else
137            {
138                return token1.compareTo(token2);
139            }
140        }
141    }