KeyManipulator.java

package emissary.directory;

import emissary.place.IServiceProviderPlace;

import java.io.Serializable;
import javax.annotation.Nullable;

/**
 * A class of utility methods for manipulating dictionary keys. Keys are stored in the dictionary with the following
 * format:
 *
 * <code>dataType.serviceName.serviceType.location$expense</code>
 * 
 * Example: UNKNOWN.servicename.ID.tcp://host.dom.com:8001/FilePlace$5050
 * 
 * <table>
 * <caption>For the example key above the return values are as follows:</caption>
 * <tr>
 * <td>routine</td>
 * <td>return value</td>
 * </tr>
 * <tr>
 * <td>dataType</td>
 * <td>UNKOWN</td>
 * </tr>
 * <tr>
 * <td>serviceName</td>
 * <td>servicename</td>
 * </tr>
 * <tr>
 * <td>serviceType</td>
 * <td>ID</td>
 * </tr>
 * <tr>
 * <td>serviceHost</td>
 * <td>host.dom.com:8001</td>
 * </tr>
 * <tr>
 * <td>serviceHostURL</td>
 * <td>http://host.dom.com:8001/</td>
 * </tr>
 * <tr>
 * <td>serviceLocation</td>
 * <td>http://host.dom.com:8001/FilePlace</td>
 * </tr>
 * <tr>
 * <td>expense</td>
 * <td>5050</td>
 * </tr>
 * <tr>
 * <td>serviceClassname</td>
 * <td>FilePlace</td>
 * </tr>
 * <tr>
 * <td>dataID</td>
 * <td>UNKOWN::ID</td>
 * </tr>
 * <tr>
 * <td>defaultDirectoryKey</td>
 * <td>*.*.*.http://host.dom.com:8001/DirectoryPlace</td>
 * </tr>
 * </table>
 *
 */
public class KeyManipulator implements Serializable {

    // Serializable
    static final long serialVersionUID = 2456659383313218695L;

    /**
     * How we separate portions of the key
     */
    public static final char SEPARATOR = '.';
    public static final char HOSTSEPARATOR = ':';
    public static final char CLASSSEPARATOR = '/';
    public static final String DOLLAR = "$";
    public static final String DATAIDSEPARATOR = "::";
    public static final String WILDCARD_THREE = "*.*.*.";

    /**
     * How many portions (separated by SEPARATOR) are in a complete key
     */
    public static final int NUMTUPLES = 4;

    private static final String doubleSlash = "//";

    /**
     * Make a key from parts
     *
     * @param dataType the first part of the key
     * @param serviceName the second part of the key
     * @param serviceType the third part of the key
     * @param serviceLocation the fourth part of the key
     */
    public static String makeKey(final String dataType, final String serviceName, final String serviceType, final String serviceLocation) {
        return dataType + SEPARATOR + serviceName + SEPARATOR + serviceType + SEPARATOR + serviceLocation;
    }

    /**
     * Return the data type field from a dictionary formatted key.
     */
    public static String getDataType(final String key) {
        final int firstSeparator = key.indexOf(SEPARATOR);

        if (firstSeparator >= 0) {
            return key.substring(0, firstSeparator);
        }
        return "";
    }

    public static String getDataId(final String key) {
        return getDataType(key) + DATAIDSEPARATOR + getServiceType(key);
    }

    public static String getServiceTypeFromDataId(final String dataid) {
        final int pos = dataid.indexOf(DATAIDSEPARATOR);
        if (pos > -1) {
            return dataid.substring(pos + DATAIDSEPARATOR.length());
        }
        return "";
    }

    /**
     * Performs wildcard (? | *) string matching for dictionary key searches.
     */
    public static boolean gmatch(final String s, final String p) {
        return gmatch2(s.toCharArray(), p.toCharArray(), 0, 0);
    }

    /**
     * Performs wildcard (? | *) character array matching for dictionary key searches.
     */
    public static boolean gmatch(final char[] s, final char[] p) {
        return gmatch2(s, p, 0, 0);
    }

    private static boolean gmatch2(final char[] s, final char[] p, final int spos, final int ppos) {
        // fastest interpreted version
        if (p.length == ppos) {
            return s.length == spos;
        }
        if (s.length == spos) {
            return false;
        }
        final char scc = s[spos];
        final char c = p[ppos];
        if (c == '?') {
            if (scc > 0) {
                return gmatch2(s, p, spos + 1, ppos + 1);
            }
            return false;
        } else if (c == '*') {
            final int ppos2 = ppos + 1;
            if (p.length == ppos2) {
                return true;
            }
            int spos2 = spos;
            while (s.length > ++spos2) {
                if (p[ppos2] == s[spos2]) {
                    if (gmatch2(s, p, spos2, ppos2)) {
                        return true;
                    }
                }
            }
            return false;
        } else if (c == scc) {
            return gmatch2(s, p, spos + 1, ppos + 1);
        } else {
            return false;
        }
    }

    /**
     * Return whether the key passed in is complete or not This is useful in determining if the key must be wildcarded for a
     * query.
     *
     * @param key the key to check
     */
    public static boolean isKeyComplete(final String key) {
        if (numTuplesInKey(key) < KeyManipulator.NUMTUPLES) {
            return false;
        }
        if (getServiceHost(key).length() == 0) {
            return false;
        }
        return true;
    }

    /**
     * Return the number of tuples in the Key passed in
     */
    public static int numTuplesInKey(final String key) {
        int count = 0;
        final byte[] keyBytes = key.getBytes();

        for (int i = 0; i < keyBytes.length; i++) {
            if (keyBytes[i] == KeyManipulator.SEPARATOR) {
                count++;
            }
            // Dont count separators in the hostname portion
            if ((i > 0) && (keyBytes[i] == CLASSSEPARATOR) && (keyBytes[i - 1] == CLASSSEPARATOR)) {
                break;
            }
        }

        // There is one more tuple than separators x.x.x.x
        return count + 1;
    }

    /**
     * Attempt to set out some rules for a key to allow validation
     *
     * @param key the putative key
     * @return true if key is valid
     */
    public static boolean isValid(@Nullable final String key) {
        return (key != null) && (numTuplesInKey(key) == 4);
    }

    /**
     * Returns the class name from a dictionary formatted key
     */
    public static String getServiceClassname(final String key) {
        final String location = getServiceLocation(key);
        final int sep = location.lastIndexOf(CLASSSEPARATOR);

        if (sep >= 0) {
            return location.substring(sep + 1);
        }
        return "";
    }

    /**
     * Returns the hostname:port from a dictionary formatted key
     */
    public static String getServiceHost(final String key) {
        final String location = getServiceLocation(key);
        final int ds = location.indexOf(doubleSlash);

        if (ds > -1) {
            final int cs = location.indexOf(CLASSSEPARATOR, ds + 2);

            if (cs > -1) {
                return location.substring(ds + 2, cs);
            }
            return "";
        }
        return "";
    }

    /**
     * Returns the protocol://hostname:port/ from a dictionary formatted key
     */
    public static String getServiceHostUrl(final String key) {
        final String location = getServiceLocation(key);
        final int ds = location.lastIndexOf(CLASSSEPARATOR);

        if (ds > -1) {
            return location.substring(0, ds + 1);
        }
        return "";
    }

    /**
     * Returns true if k1 and k2 are local to each other
     *
     * @param k1 key for first place
     * @param k2 key for second place
     * @return true if local to each other
     */
    public static boolean isLocalTo(final String k1, final String k2) {
        return getServiceHostUrl(k1).equals(getServiceHostUrl(k2));
    }

    /**
     * Returns the service location (host:port/className) field from a dictionary formatted key.
     */
    public static String getServiceLocation(final String key) {
        final int firstSeparator = key.indexOf(SEPARATOR);
        final int secondSeparator = key.indexOf(SEPARATOR, firstSeparator + 1);
        final int thirdSeparator = key.indexOf(SEPARATOR, secondSeparator + 1);
        final int fourthSeparator = key.indexOf(DOLLAR, thirdSeparator + 1);

        if ((thirdSeparator >= 0)) {
            if (fourthSeparator > 0) {
                return key.substring(thirdSeparator + 1, fourthSeparator);
            }
            return key.substring(thirdSeparator + 1);
        }
        return "";
    }

    /**
     * Return the expense of the service
     */
    public static int getExpense(final String key) {
        return getExpense(key, -1);
    }

    public static int getExpense(final String key, final int dflt) {
        final int pos = key.lastIndexOf(DOLLAR);
        int expense = dflt;
        try {
            expense = Integer.parseInt(key.substring(pos + 1));
        } catch (NumberFormatException e) {
            // It's optional...
        }
        return expense;
    }

    /**
     * Add expense record onto a key
     */
    public static String addExpense(final String key, final int expense) {
        if (getExpense(key, -99) == expense) {
            return key;
        }

        if (key.indexOf(DOLLAR) > -1) {
            return key.substring(0, key.lastIndexOf(DOLLAR) + 1) + expense;
        }

        return key + DOLLAR + expense;
    }

    /**
     * Strip the expense from a key if present
     *
     * @param key any key
     * @return the modified key
     */
    public static String removeExpense(final String key) {
        final int pos = key.indexOf(DOLLAR);
        if (pos != -1) {
            return key.substring(0, pos);
        }
        return key;
    }

    /**
     * Returns the service name field from a dictionary formatted key.
     */
    public static String getServiceName(final String key) {
        final int firstSeparator = key.indexOf(SEPARATOR);
        final int secondSeparator = key.indexOf(SEPARATOR, firstSeparator + 1);

        if ((firstSeparator >= 0) && (secondSeparator >= 0)) {
            return key.substring(firstSeparator + 1, secondSeparator);
        }
        return "";
    }

    /**
     * Returns the service type field from a dictionary formatted key.
     */
    public static String getServiceType(final String key) {
        final int firstSeparator = key.indexOf(SEPARATOR);
        final int secondSeparator = key.indexOf(SEPARATOR, firstSeparator + 1);
        final int thirdSeparator = key.indexOf(SEPARATOR, secondSeparator + 1);

        if ((secondSeparator >= 0) && (thirdSeparator >= 0)) {
            return key.substring(secondSeparator + 1, thirdSeparator);
        }
        return "";
    }

    /**
     * Add in the remote overhead to the "to" key if it was a move
     *
     * @param from the key we came from
     * @param to the key we went to
     * @return the &quot;to&quot; key with remote overhead or as it was
     */
    public static String addRemoteCostIfMoved(final String from, final String to) {

        // If no move, just return the "to" key
        if (getServiceHost(from).equals(getServiceHost(to))) {
            return to;
        }

        // Add remote overhead to the "to" key
        return addExpense(to, getExpense(to) + IDirectoryPlace.REMOTE_EXPENSE_OVERHEAD);
    }

    /**
     * Mangle origKey such that the host on proxyKey becomes a proxy for it
     *
     * @param origKey the original key
     * @param proxyKey the key representing the place that wants to proxy
     * @param dfltExp default expense to use if origKey has none or -1 for no dflt
     * @return a key with the original data type, service type and service name and expense but the new place location from
     *         proxyKey
     */
    public static String makeProxyKey(final String origKey, final String proxyKey, final int dfltExp) {
        if (isLocalTo(origKey, proxyKey) && dfltExp > -1) {
            return addExpense(origKey, getExpense(origKey, dfltExp));
        }
        final int newExp = getExpense(origKey, dfltExp);
        return getDataType(origKey) + SEPARATOR + getServiceName(origKey) + SEPARATOR + getServiceType(origKey) + SEPARATOR
                + getServiceLocation(proxyKey) + (newExp > -1 ? (DOLLAR + newExp) : "");
    }

    /**
     * Build a key to the directory of the place represented by the key
     *
     * @param key the key of the place to find a directory for
     */
    public static String getDefaultDirectoryKey(final String key) {
        return WILDCARD_THREE + KeyManipulator.getServiceHostUrl(key) + "DirectoryPlace";
    }


    public static String getHostMatchKey(final String key) {
        return WILDCARD_THREE + KeyManipulator.getServiceHostUrl(key) + "*";
    }

    public static String makeSproutKey(final String placeKey) {
        return "*.*." + IServiceProviderPlace.SPROUT_KEY + "." + getServiceLocation(placeKey) + DOLLAR + "0";
    }

    /** This class is not meant to be instantiated. */
    private KeyManipulator() {}
}