/*
 * @(#)Wildcard.java
 *
 * Copyright (c) 2002-2003 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */

/*
 * Classname: Wildcard
 *
 * Version: 1.0
 *
 * Author: Alex Usun
 */
package com.jscape.util;

import java.io.Serializable;


/**
 * The simple wildcard matcher. Accepts two wildcards:
 * <ul>
 * <li><code>*</code> matches any sequence of none, one or more characters
 * <li><code>?</code> matches any one character
 * </ul>
 *
 * @author Alex Usun
 * @version 1.0
 */
public class Wildcard
        implements Serializable {

    /**
     * Matches any sequence of none, one or more characters
     */
    public static final char ASTERISK_CHAR = '*';

    public static final String ASTERISK = "*";

    /**
     * Matches any one character
     */
    public static final char QUESTION_CHAR = '?';

    public static final String QUESTION = "?";

    private String wildcard;

    public static boolean isWildcard(String str) {
        return str.contains(ASTERISK) || str.contains(QUESTION);
    }

    public static String toRegularExpression(String wildcard) {
        StringBuilder buffer = new StringBuilder(wildcard.length());
        buffer.append('^');

        for (int i = 0, size = wildcard.length(); i < size; ++i) {
            char c = wildcard.charAt(i);
            switch (c) {
                case '*':
                    buffer.append(".*");
                    break;
                case '?':
                    buffer.append(".");
                    break;
                // escape special regexp-characters
                case '(':
                case ')':
                case '[':
                case ']':
                case '$':
                case '^':
                case '.':
                case '{':
                case '}':
                case '|':
                case '\\':
                    buffer.append("\\");
                    buffer.append(c);
                    break;
                default:
                    buffer.append(c);
                    break;
            }
        }

        buffer.append('$');

        return buffer.toString();
    }

    /**
     * Creates a new wildcard instance.
     *
     * @param wildcard the wildcard string
     */
    public Wildcard(String wildcard) {
        Assert.isValidString(wildcard);
        this.wildcard = wildcard;
    }

    /**
     * Returns the wildcard string.
     *
     * @return the wildcard string
     */
    public String getWildcard() {
        return this.wildcard;
    }

    /**
     * Tests whether or not the specified string matches this wildcard.
     *
     * @param str the string to be tested
     *
     * @return <code>true</code> if and only if the string matches the wildcard;
     *         <code>false</code> otherwise
     */
    public boolean matches(String str) {
        Assert.notNull(str);

        return matches(str, 0, this.wildcard, 0);
    }

    /**
     * Tests whether or not the specified string matches the wildcard.
     *
     * @param str         the string to be tested
     * @param strIdx      the string starting offset
     * @param wildcard    the wildcard string
     * @param wildcardIdx the wildcard starting offset
     *
     * @return <code>true</code> if and only if the string matches the wildcard;
     *         <code>false</code> otherwise
     */
    private boolean matches(String str, int strIdx, String wildcard, int wildcardIdx) {
        // check bounds
        if (wildcardIdx >= wildcard.length()) {
            return (strIdx >= str.length());
        } else if (strIdx >= str.length()) {
            return (wildcard.charAt(wildcardIdx) == ASTERISK_CHAR);
        }

        final char currentWildcard = wildcard.charAt(wildcardIdx);

        switch (currentWildcard) {
            case ASTERISK_CHAR:

                // check for terminal symbol
                if (++wildcardIdx >= wildcard.length()) {
                    return true;
                }

                char nextWildcard = wildcard.charAt(wildcardIdx);

                // go through every subsequent apperance of the next character
                // and so if the rest of the regex matches
                int nextMatch = str.indexOf(nextWildcard, strIdx);

                while (nextMatch != -1) {
                    if (matches(str, nextMatch, wildcard, wildcardIdx)) {
                        return true;
                    }

                    nextMatch = str.indexOf(nextWildcard, nextMatch + 1);
                }

                return false;
            case QUESTION_CHAR:   // this matches anything
                return matches(str, ++strIdx, wildcard, ++wildcardIdx);
            default:
                return (currentWildcard == str.charAt(strIdx)) && matches(str, ++strIdx, wildcard, ++wildcardIdx);
        }
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        final Wildcard that = (Wildcard) o;

        return this.wildcard.equals(that.wildcard);
    }

    public int hashCode() {
        return this.wildcard.hashCode();
    }

    public String toString() {
        return this.wildcard;
    }

}
