    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.codec.language.bm;
    019
    020import org.apache.commons.codec.EncoderException;
    021import org.apache.commons.codec.StringEncoder;
    022
    023/**
    024 * Encodes strings into their Beider-Morse phonetic encoding.
    025 * <p>
    026 * Beider-Morse phonetic encodings are optimised for family names. However, they may be useful for a wide range of
    027 * words.
    028 * <p>
    029 * This encoder is intentionally mutable to allow dynamic configuration through bean properties. As such, it is mutable,
    030 * and may not be thread-safe. If you require a guaranteed thread-safe encoding then use {@link PhoneticEngine}
    031 * directly.
    032 * <p>
    033 * <b>Encoding overview</b>
    034 * <p>
    035 * Beider-Morse phonetic encodings is a multi-step process. Firstly, a table of rules is consulted to guess what
    036 * language the word comes from. For example, if it ends in "<code>ault</code>" then it infers that the word is French.
    037 * Next, the word is translated into a phonetic representation using a language-specific phonetics table. Some runs of
    038 * letters can be pronounced in multiple ways, and a single run of letters may be potentially broken up into phonemes at
    039 * different places, so this stage results in a set of possible language-specific phonetic representations. Lastly, this
    040 * language-specific phonetic representation is processed by a table of rules that re-writes it phonetically taking into
    041 * account systematic pronunciation differences between languages, to move it towards a pan-indo-european phonetic
    042 * representation. Again, sometimes there are multiple ways this could be done and sometimes things that can be
    043 * pronounced in several ways in the source language have only one way to represent them in this average phonetic
    044 * language, so the result is again a set of phonetic spellings.
    045 * <p>
    046 * Some names are treated as having multiple parts. This can be due to two things. Firstly, they may be hyphenated. In
    047 * this case, each individual hyphenated word is encoded, and then these are combined end-to-end for the final encoding.
    048 * Secondly, some names have standard prefixes, for example, "<code>Mac/Mc</code>" in Scottish (English) names. As
    049 * sometimes it is ambiguous whether the prefix is intended or is an accident of the spelling, the word is encoded once
    050 * with the prefix and once without it. The resulting encoding contains one and then the other result.
    051 * <p>
    052 * <b>Encoding format</b>
    053 * <p>
    054 * Individual phonetic spellings of an input word are represented in upper- and lower-case roman characters. Where there
    055 * are multiple possible phonetic representations, these are joined with a pipe (<code>|</code>) character. If multiple
    056 * hyphenated words where found, or if the word may contain a name prefix, each encoded word is placed in elipses and
    057 * these blocks are then joined with hyphens. For example, "<code>d'ortley</code>" has a possible prefix. The form
    058 * without prefix encodes to "<code>ortlaj|ortlej</code>", while the form with prefix encodes to "
    059 * <code>dortlaj|dortlej</code>". Thus, the full, combined encoding is "<code>(ortlaj|ortlej)-(dortlaj|dortlej)</code>".
    060 * <p>
    061 * The encoded forms are often quite a bit longer than the input strings. This is because a single input may have many
    062 * potential phonetic interpretations. For example, "<code>Renault</code>" encodes to "
    063 * <code>rYnDlt|rYnalt|rYnult|rinDlt|rinalt|rinult</code>". The <code>APPROX</code> rules will tend to produce larger
    064 * encodings as they consider a wider range of possible, approximate phonetic interpretations of the original word.
    065 * Down-stream applications may wish to further process the encoding for indexing or lookup purposes, for example, by
    066 * splitting on pipe (<code>|</code>) and indexing under each of these alternatives.
    067 * <p>
    068 * <b>Note</b>: this version of the Beider-Morse encoding is equivalent with v3.4 of the reference implementation.
    069 * </p>
    070 * @see <a href="http://stevemorse.org/phonetics/bmpm.htm">Beider-Morse Phonetic Matching</a>
    071 * @see <a href="http://stevemorse.org/phoneticinfo.htm">Reference implementation</a>
    072 *
    073 * <p>
    074 * This class is Not ThreadSafe
    075 * </p>
    076 * @since 1.6
    077 * @version $Id$
    078 */
    079public class BeiderMorseEncoder implements StringEncoder {
    080    // Implementation note: This class is a spring-friendly facade to PhoneticEngine. It allows read/write configuration
    081    // of an immutable PhoneticEngine instance that will be delegated to for the actual encoding.
    082
    083    // a cached object
    084    private PhoneticEngine engine = new PhoneticEngine(NameType.GENERIC, RuleType.APPROX, true);
    085
    086    @Override
    087    public Object encode(final Object source) throws EncoderException {
    088        if (!(source instanceof String)) {
    089            throw new EncoderException("BeiderMorseEncoder encode parameter is not of type String");
    090        }
    091        return encode((String) source);
    092    }
    093
    094    @Override
    095    public String encode(final String source) throws EncoderException {
    096        if (source == null) {
    097            return null;
    098        }
    099        return this.engine.encode(source);
    100    }
    101
    102    /**
    103     * Gets the name type currently in operation.
    104     *
    105     * @return the NameType currently being used
    106     */
    107    public NameType getNameType() {
    108        return this.engine.getNameType();
    109    }
    110
    111    /**
    112     * Gets the rule type currently in operation.
    113     *
    114     * @return the RuleType currently being used
    115     */
    116    public RuleType getRuleType() {
    117        return this.engine.getRuleType();
    118    }
    119
    120    /**
    121     * Discovers if multiple possible encodings are concatenated.
    122     *
    123     * @return true if multiple encodings are concatenated, false if just the first one is returned
    124     */
    125    public boolean isConcat() {
    126        return this.engine.isConcat();
    127    }
    128
    129    /**
    130     * Sets how multiple possible phonetic encodings are combined.
    131     *
    132     * @param concat
    133     *            true if multiple encodings are to be combined with a '|', false if just the first one is
    134     *            to be considered
    135     */
    136    public void setConcat(final boolean concat) {
    137        this.engine = new PhoneticEngine(this.engine.getNameType(),
    138                                         this.engine.getRuleType(),
    139                                         concat,
    140                                         this.engine.getMaxPhonemes());
    141    }
    142
    143    /**
    144     * Sets the type of name. Use {@link NameType#GENERIC} unless you specifically want phonetic encodings
    145     * optimized for Ashkenazi or Sephardic Jewish family names.
    146     *
    147     * @param nameType
    148     *            the NameType in use
    149     */
    150    public void setNameType(final NameType nameType) {
    151        this.engine = new PhoneticEngine(nameType,
    152                                         this.engine.getRuleType(),
    153                                         this.engine.isConcat(),
    154                                         this.engine.getMaxPhonemes());
    155    }
    156
    157    /**
    158     * Sets the rule type to apply. This will widen or narrow the range of phonetic encodings considered.
    159     *
    160     * @param ruleType
    161     *            {@link RuleType#APPROX} or {@link RuleType#EXACT} for approximate or exact phonetic matches
    162     */
    163    public void setRuleType(final RuleType ruleType) {
    164        this.engine = new PhoneticEngine(this.engine.getNameType(),
    165                                         ruleType,
    166                                         this.engine.isConcat(),
    167                                         this.engine.getMaxPhonemes());
    168    }
    169
    170    /**
    171     * Sets the number of maximum of phonemes that shall be considered by the engine.
    172     *
    173     * @param maxPhonemes
    174     *            the maximum number of phonemes returned by the engine
    175     * @since 1.7
    176     */
    177    public void setMaxPhonemes(final int maxPhonemes) {
    178        this.engine = new PhoneticEngine(this.engine.getNameType(),
    179                                         this.engine.getRuleType(),
    180                                         this.engine.isConcat(),
    181                                         maxPhonemes);
    182    }
    183
    184}