org.josso.selfservices.password.generator.PasswordGeneratorImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.josso.selfservices.password.generator.PasswordGeneratorImpl.java

Source

/*
 * JOSSO: Java Open Single Sign-On
 *
 * Copyright 2004-2009, Atricore, Inc.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
package org.josso.selfservices.password.generator;

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.josso.selfservices.password.PasswordGenerator;
import org.josso.selfservices.ChallengeResponseCredential;
import org.josso.gateway.identity.SSOUser;

/**
 * It performs the actual password
 * generation. The idea of this class is to generate passwords that are
 * relatively easily to remember but at the same time complex enough. It
 * utilizes a predefined set of vowels and consonants that are brought together by
 * an iterative algorithm. The concept of dipthong is also used which can be
 * used as an additional flag for a consonant or a vowel. Typical examples of
 * dipthongs are <em>ei</em> for vowels and <em>th</em> for consonants.
 * <p/>
 * Various algorithms for random generation can be used for feeding the process
 * of password generation. This ensures unique password creation.
 * <p/>
 * By default two filters are registered in the PwGenerator. The first one is an
 * empty black list filter. It can be used to filter out forbidden predefined
 * passwords. The second one is based on regular expressions and uses frequently
 * utilized rules for filtering passwords such as the number of contained symbols,
 * digits and so on. See the help for a detailed description.
 * <p/>
 * <p/>
 * <p/>
 * <h3>
 *
 * @org.apache.xbean.XBean element="password-generator"
 *
 * @author unrz205
 */
public class PasswordGeneratorImpl
        implements IPwGenConstants, IPwGenCommandLineOptions, IPwGenRegEx, PasswordGenerator {

    // The class logger
    private static final Log log = LogFactory.getLog(PasswordGeneratorImpl.class);

    // A static list of predefined vowels and consonants dipthongs. Suitable for
    // English speaking people.
    // This can be exchanged or extended with a different one if needed.
    private static final PwElement[] PW_ELEMENTS = { new PwElement("a", VOWEL), //$NON-NLS-1$
            new PwElement("ae", VOWEL | DIPTHONG), //$NON-NLS-1$
            new PwElement("ah", VOWEL | DIPTHONG), //$NON-NLS-1$
            new PwElement("ai", VOWEL | DIPTHONG), //$NON-NLS-1$
            new PwElement("b", CONSONANT), new PwElement("c", CONSONANT), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("ch", CONSONANT | DIPTHONG), //$NON-NLS-1$
            new PwElement("d", CONSONANT), new PwElement("e", VOWEL), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("ee", VOWEL | DIPTHONG), //$NON-NLS-1$
            new PwElement("ei", VOWEL | DIPTHONG), //$NON-NLS-1$
            new PwElement("f", CONSONANT), new PwElement("g", CONSONANT), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("gh", CONSONANT | DIPTHONG | NOT_FIRST), //$NON-NLS-1$
            new PwElement("h", CONSONANT), new PwElement("i", VOWEL), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("ie", VOWEL | DIPTHONG), //$NON-NLS-1$
            new PwElement("j", CONSONANT), new PwElement("k", CONSONANT), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("l", CONSONANT), new PwElement("m", CONSONANT), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("n", CONSONANT), //$NON-NLS-1$
            new PwElement("ng", CONSONANT | DIPTHONG | NOT_FIRST), //$NON-NLS-1$
            new PwElement("o", VOWEL), new PwElement("oh", VOWEL | DIPTHONG), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("oo", VOWEL | DIPTHONG), //$NON-NLS-1$
            new PwElement("p", CONSONANT), //$NON-NLS-1$
            new PwElement("ph", CONSONANT | DIPTHONG), //$NON-NLS-1$
            new PwElement("qu", CONSONANT | DIPTHONG), //$NON-NLS-1$
            new PwElement("r", CONSONANT), new PwElement("s", CONSONANT), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("sh", CONSONANT | DIPTHONG), new PwElement("t", CONSONANT), //$NON-NLS-2$
            new PwElement("th", CONSONANT | DIPTHONG), //$NON-NLS-1$
            new PwElement("u", VOWEL), new PwElement("v", CONSONANT), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("w", CONSONANT), new PwElement("x", CONSONANT), //$NON-NLS-1$ //$NON-NLS-2$
            new PwElement("y", CONSONANT), new PwElement("z", CONSONANT) }; //$NON-NLS-1$ //$NON-NLS-2$

    // An instance of the Random number that would be used during the generation
    // process
    private Random random;

    private Map<String, IPasswordFilter> filters = new HashMap<String, IPasswordFilter>();;

    private static int DEFAULT_MAX_ATTEMPTS = 10000;

    private IRandomFactory randomFactory;

    // -------------------------------------------------< Configuration Propertyes >

    // Flags used during the process of password generation
    private int passwordFlags = 0;

    // The length of the password to be generated
    private int passwordLength = DEFAULT_PASSWORD_LENGTH;

    // Use simple random
    private boolean useSimpleRandom;

    private String secureRandomAlgorithm;

    private String secureRandomProvider;

    private boolean generateNumerals;

    private boolean generateCapitalLetters;

    private boolean includeAmbigousChars;

    private boolean includeSpecialSymbols;

    private boolean regexStartsNoSmallLetter;

    private boolean regexEndsNoSmallLetter;

    private boolean regexStartsNoUpperLetter;

    private boolean regexEndsNosUpperLetter;

    private boolean regexEndsNoDigit;

    private boolean regexStartsNoDigit;

    private boolean regexStartsNoSymbol;

    private boolean regexEndsNoSymbol;

    private boolean regexOnlyOneCapital;

    private boolean regexOnlyOneSymbol;

    private boolean regexAtLeastTwoSymbols;

    private boolean regexOnlyOneDigit;

    private boolean regexAtLeastTwoDigits;

    private int maxAttempts = DEFAULT_MAX_ATTEMPTS;

    /**
     * Constructor of the PasswordGeneratorImpl
     */
    public PasswordGeneratorImpl() {

        try {

            // We don't want to expose this to spring

            passwordFlags |= PW_UPPERS;
            log.debug(Messages.getString("PwGenerator.debug_UPPERCASE_ON"));

            passwordFlags |= PW_DIGITS;
            log.debug(Messages.getString("PwGenerator.debug_DIGITS_ON"));
            // passwordFlags |= PW_SYMBOLS;
            // passwordFlags |= PW_AMBIGUOUS;

            randomFactory = RandomFactory.getInstance();

            random = randomFactory.getSecureRandom();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            random = randomFactory.getRandom();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
            random = randomFactory.getRandom();
        }
    }

    public String generateClearPassword() {
        List<String> pwds = this.process(1);
        return pwds.get(0);
    }

    public String generateClearPassword(SSOUser user, Set<ChallengeResponseCredential> challenges) {
        log.debug("User and challenges ignored!");
        List<String> pwds = this.process(1);
        return pwds.get(0);
    }

    /**
     * This method logs some general info about the given settings and tries to
     * generate passwords with the given flags and given length. The method
     * return <em>null</em> if it does not manage to create a suitable
     * password within the <em>MAX_ATTEMPTS</em>.
     *
     * @param passwordLength the length of the password to be generated
     * @param passwordFlags  the settings for the particular password
     * @return a suitable password or <em>null</em> if such could not be
     *         generated
     */
    public String generatePassword(int passwordLength, int passwordFlags) {
        if (passwordLength <= 2) {
            passwordFlags &= ~PW_UPPERS;
            log.warn(Messages.getString("PwGenerator.WARN_PL_UPERCASE_OFF")); //$NON-NLS-1$
        }
        if (passwordLength <= 2) {
            passwordFlags &= ~PW_SYMBOLS;
            log.warn(Messages.getString("PwGenerator.WARN_PL_SYMBOLS_OFF")); //$NON-NLS-1$
        }
        if (passwordLength <= 1) {
            passwordFlags &= ~PW_DIGITS;
            log.warn(Messages.getString("PwGenerator.WARN_PL_DIGITS_OFF")); //$NON-NLS-1$
        }

        String password = null;
        for (int i = 0; i < getMaxAttempts(); i++) {
            password = phonemes(passwordLength, passwordFlags);
            Set<String> filterIDs = filters.keySet();

            for (Iterator<String> iter = filterIDs.iterator(); iter.hasNext();) {
                String element = (String) iter.next();
                IPasswordFilter filter = filters.get(element);
                password = filter.filter(passwordFlags, password);
                if (password == null)
                    break;
            }

            if (password != null)
                break;

            log.debug(Messages.getString("PwGenerator.debug_ATTEMPT") + i //$NON-NLS-1$
                    + Messages.getString("PwGenerator.debug_ATTEMPT_GENERATE") //$NON-NLS-1$
                    + passwordFlags);
        }

        return password;
    }

    /**
     * @param numberOfPasswords the number of passwords to be generated.
     * @return a list of passwords or <em>null</em> if no suitable passwords
     *         could be generated.
     */
    public List<String> process(int numberOfPasswords) {

        log.debug(Messages.getString("PwGenerator.PASSWORD_GENERATOR"));

        ArrayList<String> passwords = new ArrayList<String>();

        if (isUseSimpleRandom()) {
            random = randomFactory.getRandom();
            log.debug(Messages.getString("PwGenerator.NORMAL_RANDOM"));
        }

        try {
            random = randomFactory.getSecureRandom(getSecureRandomAlgorithm(), getSecureRandomProvider());
            log.debug(Messages.getString("PwGenerator.SEC_ALG") + getSecureRandomAlgorithm()
                    + Messages.getString("PwGenerator.PROV") + getSecureRandomProvider()
                    + Messages.getString("PwGenerator.DOR"));
        } catch (NoSuchAlgorithmException e) {
            log.error(Messages.getString("PwGenerator.ERROR") + e.getMessage()
                    + Messages.getString("PwGenerator.NEW_LINE"));
            log.debug(Messages.getString("PwGenerator.DEFAUL_RANDOM"));
        } catch (NoSuchProviderException e) {
            log.error(Messages.getString("PwGenerator.ERROR") + e.getMessage()
                    + Messages.getString("PwGenerator.NEW_LINE"));
            log.error(Messages.getString("PwGenerator.DEFAUL_RANDOM"));
        }

        if (isGenerateNumerals()) {
            passwordFlags |= PW_DIGITS;
            log.debug(Messages.getString("PwGenerator.DIGITS_ON"));
        } else {
            passwordFlags &= ~PW_DIGITS;
            log.debug(Messages.getString("PwGenerator.DIGITS_OFF"));
        }

        if (isGenerateCapitalLetters()) {
            passwordFlags |= PW_UPPERS;
            log.debug(Messages.getString("PwGenerator.UPPERCASE_ON"));
        } else {
            passwordFlags &= ~PW_UPPERS;
            log.debug(Messages.getString("PwGenerator.UPPERCASE_OFF"));
        }

        if (isIncludeAmbigousChars()) {
            passwordFlags |= PW_AMBIGUOUS;
            log.debug(Messages.getString("PwGenerator.AMBIGOUS_ON"));
        } else {
            passwordFlags &= ~PW_AMBIGUOUS;
            log.debug(Messages.getString("PwGenerator.AMBIGOUS_OFF"));
        }

        if (isIncludeSpecialSymbols()) {
            passwordFlags |= PW_SYMBOLS;
            log.debug(Messages.getString("PwGenerator.SYMBOLS_ON"));
        } else {
            passwordFlags &= ~PW_SYMBOLS;
            log.debug(Messages.getString("PwGenerator.SYMBOLS_OFF"));
        }

        if (isRegexStartsNoSmallLetter())
            passwordFlags |= REGEX_STARTS_NO_SMALL_LETTER_FLAG;

        if (isRegexEndsNoSmallLetter())
            passwordFlags |= REGEX_STARTS_NO_SMALL_LETTER_FLAG;

        if (isRegexStartsNoUpperLetter())
            passwordFlags |= REGEX_STARTS_NO_UPPER_LETTER_FLAG;

        if (isRegexEndsNosUpperLetter())
            passwordFlags |= REGEX_ENDS_NO_UPPER_LETTER_FLAG;

        if (isRegexEndsNoDigit())
            passwordFlags |= REGEX_ENDS_NO_DIGIT_FLAG;

        if (isRegexStartsNoDigit())
            passwordFlags |= REGEX_STARTS_NO_DIGIT_FLAG;

        if (isRegexStartsNoSymbol())
            passwordFlags |= REGEX_STARTS_NO_SYMBOL_FLAG;

        if (isRegexEndsNoSymbol())
            passwordFlags |= REGEX_ENDS_NO_SYMBOL_FLAG;

        if (isRegexOnlyOneCapital())
            passwordFlags |= REGEX_ONLY_1_CAPITAL_FLAG;

        if (isRegexOnlyOneSymbol())
            passwordFlags |= REGEX_ONLY_1_SYMBOL_FLAG;

        if (isRegexAtLeastTwoSymbols())
            passwordFlags |= REGEX_AT_LEAST_2_SYMBOLS_FLAG;

        if (isRegexOnlyOneDigit())
            passwordFlags |= REGEX_ONLY_1_DIGIT_FLAG;

        if (isRegexAtLeastTwoDigits())
            passwordFlags |= REGEX_AT_LEAST_2_DIGITS_FLAG;
        // -------------------------------------------------------------------

        log.debug(Messages.getString("PwGenerator.GENRIC_FLAGS"));

        int res = passwordFlags & PW_DIGITS;
        log.debug(Messages.getString("PwGenerator.DIGITS") + (res != 0));
        res = passwordFlags & PW_AMBIGUOUS;
        log.debug(Messages.getString("PwGenerator.AMBIGOUS") + (res != 0));
        res = passwordFlags & PW_SYMBOLS;
        log.debug(Messages.getString("PwGenerator.SYMBOLS") + (res != 0));
        res = passwordFlags & PW_UPPERS;
        log.debug(Messages.getString("PwGenerator.UPPERS") + (res != 0));
        log.debug(Messages.getString("PwGenerator.SEPARATOR"));

        log.debug(Messages.getString("PwGenerator.GENERATING") + numberOfPasswords
                + Messages.getString("PwGenerator.PW_LENGTH") + passwordLength);
        log.debug(Messages.getString("PwGenerator.PW"));

        int i;
        for (i = 0; i < numberOfPasswords; i++) {
            String password = generatePassword(passwordLength, passwordFlags);
            if (password != null)
                passwords.add(password);
        }
        return passwords;
    }

    /**
     * Adds a password filter to the registry
     *
     * @param filter the filter instance to be registered
     * @return the registered instance
     */
    public IPasswordFilter addFilter(IPasswordFilter filter) {
        return filters.put(filter.getId(), filter);
    }

    /**
     * Removes a filter from the registry by instance search
     *
     * @param filter the instance of the filter
     * @return the removed instance
     */
    public IPasswordFilter removeFilter(IPasswordFilter filter) {
        return filters.remove(filter.getId());
    }

    /**
     * Removes a filter from the registry by identifier search
     *
     * @param id the identifier of the filter
     * @return the removed instance
     */
    public IPasswordFilter removeFilter(String id) {
        return filters.remove(id);
    }

    // ------------------------------------------------------------< Configuration >

    public Collection<IPasswordFilter> getFilters() {
        return filters.values();
    }

    /**
     * @org.apache.xbean.Property alias="filters" nestedType="org.josso.selfservices.password.generator.IPasswordFilter"
     * @param filters
     */
    public void setFilters(Collection<IPasswordFilter> filters) {
        for (IPasswordFilter filter : filters) {
            this.filters.put(filter.getId(), filter);
        }
    }

    public int getPasswordLength() {
        return passwordLength;
    }

    public void setPasswordLength(int passwordLength) {
        this.passwordLength = passwordLength;
    }

    public boolean isUseSimpleRandom() {
        return useSimpleRandom;
    }

    public void setUseSimpleRandom(boolean useSimpleRandom) {
        this.useSimpleRandom = useSimpleRandom;
    }

    public String getSecureRandomAlgorithm() {
        return secureRandomAlgorithm;
    }

    public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
        this.secureRandomAlgorithm = secureRandomAlgorithm;
    }

    public String getSecureRandomProvider() {
        return secureRandomProvider;
    }

    public void setSecureRandomProvider(String secureRandomProvider) {
        this.secureRandomProvider = secureRandomProvider;
    }

    public boolean isGenerateNumerals() {
        return generateNumerals;
    }

    public void setGenerateNumerals(boolean generateNumerals) {
        this.generateNumerals = generateNumerals;
    }

    public boolean isGenerateCapitalLetters() {
        return generateCapitalLetters;
    }

    public void setGenerateCapitalLetters(boolean generateCapitalLetters) {
        this.generateCapitalLetters = generateCapitalLetters;
    }

    public boolean isIncludeAmbigousChars() {
        return includeAmbigousChars;
    }

    public void setIncludeAmbigousChars(boolean includeAmbigousChars) {
        this.includeAmbigousChars = includeAmbigousChars;
    }

    public boolean isIncludeSpecialSymbols() {
        return includeSpecialSymbols;
    }

    public void setIncludeSpecialSymbols(boolean includeSpecialSymbols) {
        this.includeSpecialSymbols = includeSpecialSymbols;
    }

    public int getMaxAttempts() {
        return maxAttempts;
    }

    public void setMaxAttempts(int maxAttempts) {
        this.maxAttempts = maxAttempts;
    }

    public boolean isRegexStartsNoSmallLetter() {
        return regexStartsNoSmallLetter;
    }

    public void setRegexStartsNoSmallLetter(boolean regexStartsNoSmallLetter) {
        this.regexStartsNoSmallLetter = regexStartsNoSmallLetter;
    }

    public boolean isRegexEndsNoSmallLetter() {
        return regexEndsNoSmallLetter;
    }

    public void setRegexEndsNoSmallLetter(boolean regexEndsNoSmallLetter) {
        this.regexEndsNoSmallLetter = regexEndsNoSmallLetter;
    }

    public boolean isRegexStartsNoUpperLetter() {
        return regexStartsNoUpperLetter;
    }

    public void setRegexStartsNoUpperLetter(boolean regexStartsNoUpperLetter) {
        this.regexStartsNoUpperLetter = regexStartsNoUpperLetter;
    }

    public boolean isRegexEndsNosUpperLetter() {
        return regexEndsNosUpperLetter;
    }

    public void setRegexEndsNosUpperLetter(boolean regexEndsNosUpperLetter) {
        this.regexEndsNosUpperLetter = regexEndsNosUpperLetter;
    }

    public boolean isRegexEndsNoDigit() {
        return regexEndsNoDigit;
    }

    public void setRegexEndsNoDigit(boolean regexEndsNoDigit) {
        this.regexEndsNoDigit = regexEndsNoDigit;
    }

    public boolean isRegexStartsNoDigit() {
        return regexStartsNoDigit;
    }

    public void setRegexStartsNoDigit(boolean regexStartsNoDigit) {
        this.regexStartsNoDigit = regexStartsNoDigit;
    }

    public boolean isRegexStartsNoSymbol() {
        return regexStartsNoSymbol;
    }

    public void setRegexStartsNoSymbol(boolean regexStartsNoSymbol) {
        this.regexStartsNoSymbol = regexStartsNoSymbol;
    }

    public boolean isRegexEndsNoSymbol() {
        return regexEndsNoSymbol;
    }

    public void setRegexEndsNoSymbol(boolean regexEndsNoSymbol) {
        this.regexEndsNoSymbol = regexEndsNoSymbol;
    }

    public boolean isRegexOnlyOneCapital() {
        return regexOnlyOneCapital;
    }

    public void setRegexOnlyOneCapital(boolean regexOnlyOneCapital) {
        this.regexOnlyOneCapital = regexOnlyOneCapital;
    }

    public boolean isRegexOnlyOneSymbol() {
        return regexOnlyOneSymbol;
    }

    public void setRegexOnlyOneSymbol(boolean regexOnlyOneSymbol) {
        this.regexOnlyOneSymbol = regexOnlyOneSymbol;
    }

    public boolean isRegexAtLeastTwoSymbols() {
        return regexAtLeastTwoSymbols;
    }

    public void setRegexAtLeastTwoSymbols(boolean regexAtLeastTwoSymbols) {
        this.regexAtLeastTwoSymbols = regexAtLeastTwoSymbols;
    }

    public boolean isRegexOnlyOneDigit() {
        return regexOnlyOneDigit;
    }

    public void setRegexOnlyOneDigit(boolean regexOnlyOneDigit) {
        this.regexOnlyOneDigit = regexOnlyOneDigit;
    }

    public boolean isRegexAtLeastTwoDigits() {
        return regexAtLeastTwoDigits;
    }

    public void setRegexAtLeastTwoDigits(boolean regexAtLeastTwoDigits) {
        this.regexAtLeastTwoDigits = regexAtLeastTwoDigits;
    }

    // ---------------------------------------------------------< Utils >

    /**
     * The real password generation is performed in this method
     *
     * @param size     the length of the password
     * @param pw_flags the settings for the password
     * @return the newly created password
     */
    private String phonemes(int size, int pw_flags) {
        int c, i, len, flags, feature_flags;
        int prev, should_be;
        boolean first;
        String str;
        char ch;
        StringBuffer buf = new StringBuffer();

        do {
            buf.delete(0, buf.length());
            feature_flags = pw_flags;
            c = 0;
            prev = 0;
            should_be = 0;
            first = true;
            should_be = random.nextBoolean() ? VOWEL : CONSONANT;

            while (c < size) {

                i = random.nextInt(PW_ELEMENTS.length);
                str = PW_ELEMENTS[i].getValue();
                len = str.length();
                flags = PW_ELEMENTS[i].getType();
                /* Filter on the basic type of the next element */
                if ((flags & should_be) == 0) {
                    continue;
                }
                /* Handle the NOT_FIRST flag */
                if (first && ((flags & NOT_FIRST) != 0))
                    continue;
                /* Don't allow VOWEL followed a Vowel/Dipthong pair */
                if (((prev & VOWEL) != 0) && ((flags & VOWEL) != 0) && ((flags & DIPTHONG) != 0))
                    continue;
                /* Don't allow us to overflow the buffer */
                if (len > size - c)
                    continue;
                /*
                 * OK, we found an element which matches our criteria, let's do
                 * it!
                 */
                buf.append(str);

                /* Handle PW_UPPERS */
                if ((pw_flags & PW_UPPERS) != 0) {
                    if ((first || ((flags & CONSONANT) != 0)) && (random.nextInt(10) < 2)) {
                        int lastChar = buf.length() - 1;
                        buf.setCharAt(lastChar, Character.toUpperCase(buf.charAt(lastChar)));
                        feature_flags &= ~PW_UPPERS;
                    }
                }

                c += len;
                /* Handle the AMBIGUOUS flag */
                if ((pw_flags & PW_AMBIGUOUS) != 0) {
                    int k = -1;
                    for (int j = 0; j < PW_AMBIGUOUS_SYMBOLS.length(); j++) {
                        k = buf.indexOf(String.valueOf(PW_AMBIGUOUS_SYMBOLS.charAt(j)));
                        if (k != -1)
                            break;
                    }
                    if (k != -1) {
                        buf.delete(k, buf.length());
                        c = buf.length();
                    }
                }

                /* Time to stop? */
                if (c >= size)
                    break;

                /*
                 * Handle PW_DIGITS
                 */
                if ((pw_flags & PW_DIGITS) != 0) {
                    if (!first && (random.nextInt(10) < 3)) {
                        do {
                            ch = (new Integer(random.nextInt(10))).toString().charAt(0);
                        } while (((pw_flags & PW_AMBIGUOUS) != 0) && (PW_AMBIGUOUS_SYMBOLS.indexOf(ch) != -1));
                        c++;
                        buf = buf.append(ch);
                        feature_flags &= ~PW_DIGITS;

                        first = true;
                        prev = 0;
                        should_be = random.nextBoolean() ? VOWEL : CONSONANT;
                        continue;
                    }
                }

                /*
                 * OK, figure out what the next element should be
                 */
                if (should_be == CONSONANT) {
                    should_be = VOWEL;
                } else { /* should_be == VOWEL */
                    if (((prev & VOWEL) != 0) || ((flags & DIPTHONG) != 0) || (random.nextInt(10) > 3))
                        should_be = CONSONANT;
                    else
                        should_be = VOWEL;
                }
                prev = flags;
                first = false;

                /* Handle PW_SYMBOLS */
                if ((pw_flags & PW_SYMBOLS) != 0) {
                    if (!first && (random.nextInt(10) < 2)) {
                        do {
                            ch = PW_SPECIAL_SYMBOLS.charAt(random.nextInt(PW_SPECIAL_SYMBOLS.length()));
                        } while (((pw_flags & PW_AMBIGUOUS) != 0) && (PW_AMBIGUOUS_SYMBOLS.indexOf(ch) != -1));
                        c++;
                        buf = buf.append(ch);
                        feature_flags &= ~PW_SYMBOLS;
                    }
                }

            }
        } while ((feature_flags & (PW_UPPERS | PW_DIGITS | PW_SYMBOLS)) != 0);

        return buf.toString();
    }

}