001package org.andromda.utils.inflector;
002
003import java.util.ArrayList;
004import java.util.List;
005import java.util.regex.Matcher;
006import java.util.regex.Pattern;
007
008/**
009 * Language utility for transforming French words
010 *
011 * @author Cédric Vidal
012 */
013public class FrenchInflector
014{
015    private static final List<Inflection> plurals = new ArrayList<Inflection>();
016
017    private static final List<Pattern> uncountables = new ArrayList<Pattern>();
018
019    /**
020     *
021     */
022    static class Inflection
023    {
024        private final Pattern pattern;
025
026        private final String replace;
027
028        /**
029         * @param pattern
030         * @param replace
031         */
032        public Inflection(Pattern pattern, String replace)
033        {
034            super();
035            this.pattern = pattern;
036            this.replace = replace;
037        }
038
039        /**
040         * @param regexp
041         * @param replace
042         */
043        public Inflection(String regexp, String replace)
044        {
045            super();
046            this.pattern = Pattern.compile(regexp);
047            this.replace = replace;
048        }
049
050        /**
051         * @return pattern
052         */
053        public Pattern getPattern()
054        {
055            return pattern;
056        }
057
058        /**
059         * @return replace
060         */
061        public String getReplace()
062        {
063            return replace;
064        }
065    }
066
067    static
068    {
069        /*
070         * NON COMPOSED WORDS
071         */
072
073        uncountable(endsWith("(s|x|z)"));
074
075        /*
076         * -al words such as cheval -> chevaux (horse)
077         */
078        takeAnS(new String[]{"bal", "carnaval", "c?r?monial", "chacal",
079            "choral", "festival", "nopal", "pal", "r?cital", "r?gal",
080            "santal"});
081        plural(endsWith("(al)"), "$1aux");
082
083        /*
084         * -eau words such as rideau -> rideaux (curtain) -eu words such as ?meu ->
085         * ?meux (the bird)
086         */
087        takeAnS(new String[]{"landau", "sarrau", "bleu", "?meu", "emposieu",
088            "enfeu", "feu" /* adj */, "lieu" /* le poisson */, "pneu",
089            "richelieu", "schleu"});
090        plural(endsWith("(au|eu|eau)"), "$1$2x");
091
092        /*
093         * -ou words take an s except the following exceptions
094         */
095        takeAnX(new String[]{"bijou", "caillou", "chou", "genou", "hibou",
096            "joujou", "pou"});
097
098        /*
099         * -ail words take an S suche as portail -> portails except the
100         * following exceptions
101         */
102        ailIrregulars(new String[]{"b", "cor", "?m", "soupir", "trav",
103            "vant", "vitr"});
104
105        /*
106         * Exceptions that doesn't fall in any category
107         */
108        plural(endsWith("a?eul"), "$1a?eux");
109        plural(endsWith("ciel"), "$1cieux");
110        plural(endsWith("oeil"), "$1yeux");
111
112        /*
113         * COMPOSED WORDS Remains to be done. Requires type information only
114         * available from a dictionary
115         */
116    }
117
118    /**
119     * Add a pattern that if matched return the original word
120     *
121     * @param pattern
122     */
123    private static void uncountable(String pattern)
124    {
125        uncountables.add(Pattern.compile(pattern));
126    }
127
128    /**
129     * -ail words that don't follow the rule
130     *
131     * @param strings
132     */
133    private static void ailIrregulars(String[] strings)
134    {
135        for (String string : strings)
136        {
137            plural("(\\S*)" + string + "ail$", "$1" + string + "aux");
138        }
139    }
140
141    /**
142     * Words that take an S
143     *
144     * @param patterns
145     */
146    private static void takeAnS(String[] patterns)
147    {
148        for (String string : patterns)
149        {
150            plural(endsWith(string), "$1" + string + 's');
151        }
152    }
153
154    /**
155     * Words that take an X
156     *
157     * @param patterns
158     */
159    private static void takeAnX(String[] patterns)
160    {
161        for (String string : patterns)
162        {
163            plural(endsWith(string), "$1" + string + 'x');
164        }
165    }
166
167    /**
168     * More complex pluralizations
169     *
170     * @param pattern
171     * @param replace
172     */
173    private static void plural(String pattern, String replace)
174    {
175        plurals.add(new Inflection(pattern, replace));
176    }
177
178    /**
179     * @param end
180     * @return a pattern that matches words ending with the given parameter
181     */
182    private static String endsWith(String end)
183    {
184        return "(\\S*)" + end + '$';
185    }
186
187    /**
188     * Converts an French word to plural form
189     *
190     * @param str
191     * @return plural form
192     */
193    public static String pluralize(String str)
194    {
195
196        for (Pattern pattern : uncountables)
197        {
198            Matcher matcher = pattern.matcher(str);
199            if (matcher.matches())
200            {
201                return str;
202            }
203        }
204
205        List<Inflection> rules = FrenchInflector.getPluralRules();
206        for (Inflection inflection : rules)
207        {
208            Pattern pattern = inflection.getPattern();
209            String replace = inflection.getReplace();
210            Matcher matcher = pattern.matcher(str);
211            if (matcher.matches())
212            {
213                return matcher.replaceFirst(replace);
214            }
215        }
216        return str.replaceFirst("([\\w]+)([^s])$", "$1$2s");
217    }
218
219    /**
220     * Returns map of plural patterns
221     */
222    private static List<Inflection> getPluralRules()
223    {
224        return plurals;
225    }
226}