root/usr/src/lib/libslp/javalib/com/sun/slp/DATable.java
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2001,2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 */

//  DATable.java:     Interface for DATables.
//  Author:           James Kempf
//  Created On:       Mon May 11 13:46:02 1998
//  Last Modified By: James Kempf
//  Last Modified On: Mon Feb 22 15:47:37 1999
//  Update Count:     53
//


package com.sun.slp;

/**
 * DATable is an abstract class that provides the interface for DA
 * and scope discovery. A variety of implementations are possible.
 * The getDATable() method creates the right one from a subclass.
 *
 * @author James Kempf
 */

import java.util.*;
import java.net.*;

abstract class DATable extends Object {

    protected static DATable daTable;
    protected static SLPConfig conf;

    // System property naming the DATable implementation class to use.

    final static String DA_TABLE_CLASS_PROP = "sun.net.slp.DATableClass";

    // SA only scopes property.

    final static String SA_ONLY_SCOPES_PROP = "sun.net.slp.SAOnlyScopes";

    // Hashtable key for multicast scopes.

    final static String MULTICAST_KEY = "&&**^^MULTICASTxxxKEY^^**&&";

    // Hashtable key for DA equivalence classes.

    final static String UNICAST_KEY = "&&**^^UNICASTxxxKEY^^**&&";

    /**
     * A record for all DAs supporting exactly the same set of scopes.
     *
     * @author James Kempf
     */


    public static class DARecord extends Object {

        // The scopes supported.

        Vector scopes = null;           // String scope names

        Vector daAddresses = new Vector();  // InetAddress DA addresses

    }

    /**
     * Return a hashtable containing two entries:
     *
     * MULTICAST_KEY - Vector of scopes from the incoming vector that are not
     * supported by any known DA.
     *
     * UNICAST_KEY - Vector of DATable.DARecord objects containing
     * equivalence classes of DAs that all support the same set of scopes.
     * Only DAs supporting one or more scopes in the incoming vector
     * are returned.
     *
     * Note that the equivalence classes don't necessarily mean that the
     * set of scopes are mutually exclusive. For example, if DA1 supports
     * scopes A, B, and C; and DA2 supports scopes C and D, then they
     * are in separate equivalence classes even though they both support
     * C. But if DA2 supports A, B, and C; then it is in the same equivalence
     * class.
     *
     * @param scopes The scopes for which DAs are required.
     * @return A Hashtable with the multicast scopes and DAAddresses.
     */

    abstract Hashtable findDAScopes(Vector scopes)
        throws ServiceLocationException;

    /**
     * Remove a DA by address.
     *
     * @param address The host address of the DA.
     * @param scopes The scopes.
     * @return True if removed, false if not.
     */

    abstract boolean removeDA(InetAddress address, Vector scopes);

    /**
     * Return a vector of scopes that the SA or UA client should use.
     * Note that if no DAs are around, SA adverts must be used to
     * find SAs. We must sort through the returned DAs and apply
     * the scope prioritization algorithm to them.
     *
     * @return Vector of scopes for the SA or UA client to use.
     */

    synchronized Vector findScopes() throws ServiceLocationException {

        // First, get the DA addresses v.s. scopes table from the DAtable.
        //  This will also include DA addresses from the configuration file,
        //  if any. We don't filter on any scopes, since we want all of
        //  them. We are only interested in v2 scopes here.

        Vector scopes = new Vector();
        Hashtable daRec = daTable.findDAScopes(scopes);
        Vector daEquivClasses = (Vector)daRec.get(UNICAST_KEY);

        if (daEquivClasses != null) {

            // Go through the equivalence classes and pull out scopes.

            int i, n = daEquivClasses.size();

            for (i = 0; i < n; i++) {
                DARecord rec = (DARecord)daEquivClasses.elementAt(i);
                Vector v = rec.scopes;

                int j, m = v.size();

                for (j = 0; j < m; j++) {
                    Object s = v.elementAt(j);

                    // Unicast scopes take precedence over multicast scopes,
                    //  so insert them at the beginning of the vector.

                    if (!scopes.contains(s)) {
                        scopes.addElement(s);

                    }
                }
            }
        }

        return scopes;
    }

    /**
     * Get the right DA table implementation. The property
     * sun.net.slp.DATableClass determines the class.
     *
     * @return The DATable object for this process' SLP requests.
     */


    static DATable getDATable() {

        // Return it right up front if we have it.

        if (daTable != null) {
            return daTable;

        }

        conf = SLPConfig.getSLPConfig();

        // Link and instantiate it.

        daTable = linkAndInstantiateFromProp();

        return daTable;

    }

    // Link and instantiate the class in the property.

    static protected DATable linkAndInstantiateFromProp() {

        // Get the property.

        String className = System.getProperty(DA_TABLE_CLASS_PROP);

        if (className == null) {
            Assert.slpassert(false,
                          "no_da_table",
                          new Object[] {DA_TABLE_CLASS_PROP});
        }

        Class tclass = null;

        // Link the class and instantiate the object.

        try {

            tclass = Class.forName(className);
            daTable = (DATable)tclass.newInstance();
            return daTable;

        } catch (ClassNotFoundException ex) {

            Assert.slpassert(false,
                          "no_da_table_class",
                          new Object[] {className});

        } catch (InstantiationException ex) {

            Assert.slpassert(false,
                          "instantiation_exception",
                          new Object[] {className});

        } catch (IllegalAccessException ex) {

            Assert.slpassert(false,
                          "access_exception",
                          new Object[] {className});

        }

        // We won't reach this point, since the assertions will capture
        //  any errors and kill the program.

        return null;
    }

    //
    // Utility functions for DA filtering and handling scopes.
    //

    // Filter scopes, removing any not on the filter list if inVector is
    //  false and removing any in the filter list if inVector is true.

    public static void
        filterScopes(Vector scopes, Vector filter, boolean inVector) {

        int i = 0;

        // Null or empty filter vector means that all should be accepted.

        if (filter != null && !(filter.size() <= 0)) {

            while (i < scopes.size()) {
                String scope = (String)scopes.elementAt(i);

                if ((!inVector && !filter.contains(scope)) ||
                    (inVector && filter.contains(scope))) {
                    scopes.removeElementAt(i);

                } else {
                    i++;

                }
            }
        }
    }

    // Add a new address to the equivalence class.

    static boolean addToEquivClass(String daaddr, Vector scopes, Vector ret) {

        // Create the InetAddress object.

        InetAddress addr = null;

        try {

            addr = InetAddress.getByName(daaddr);

        } catch (UnknownHostException ex) {

            if (conf.traceAll()) {
                conf.writeLog("unknown_da_address",
                              new Object[] {daaddr});

            }

            return false;
        }

        // Go through the existing vector.

        int i, n = ret.size();
        boolean equivalent = false;
        DARecord rec = null;

    outer: for (i = 0; i < n && equivalent == false; i++) {
        rec = (DARecord)ret.elementAt(i);
        Vector dascopes = rec.scopes;

        int j, m = dascopes.size();

        for (j = 0; j < m; j++) {
            String scope = (String)dascopes.elementAt(j);

            if (!scopes.contains(scope)) {
                continue outer;

            }
        }

        equivalent = true;
    }

        // Make a new record if not equivalent.

        if (!equivalent) {
            rec = new DATable.DARecord();
            rec.scopes = (Vector)scopes.clone();

            ret.addElement(rec);

        }


        // Add to record. Optimize, by putting the local address at the
        //  beginning of the vector.

        Vector interfaces = conf.getInterfaces();

        if (interfaces.contains(addr)) {
            rec.daAddresses.insertElementAt(addr, 0);

        } else {
            rec.daAddresses.addElement(addr);

        }

        return true;
    }

    /**
     * Validate the scope names. We check that they are all strings,
     * that none are the empty string. In addition, we collate to
     * remove duplicates, and lower case.
     */

    static void validateScopes(Vector scopes, Locale locale)
        throws ServiceLocationException {

        // Check for empty vector.

        if (scopes == null || scopes.size() <= 0) {
            throw
                new ServiceLocationException(
                                ServiceLocationException.PARSE_ERROR,
                                "no_scope_vector",
                                new Object[0]);
        }

        // Check for all strings and none empty.

        int i;
        Hashtable ht = new Hashtable();

        for (i = 0; i < scopes.size(); i++) {
            Object o = scopes.elementAt(i);

            if (!(o instanceof String)) {
                throw
                    new ServiceLocationException(
                                ServiceLocationException.PARSE_ERROR,
                                "non_string_element",
                                new Object[] {scopes});
            }

            String str = (String)o;

            if (str.length() <= 0) {
                throw
                    new ServiceLocationException(
                                ServiceLocationException.PARSE_ERROR,
                                "null_element",
                                new Object[] {scopes});
            }

            // Lower case, trim.

            str = str.toLowerCase(locale).trim();

            // Squeeze out spaces.

            StringBuffer buf = new StringBuffer();
            StringTokenizer tk =
                new StringTokenizer(str, ServiceLocationAttribute.WHITESPACE);
            String tok = null;

            while (tk.hasMoreTokens()) {

                // Add a single embedded whitespace for each group found.

                if (tok != null) {
                    buf.append(" ");

                }

                tok = tk.nextToken();
                buf.append(tok);
            }

            str = buf.toString();

            // If it wasn't already seen, put it into the hashtable.

            if (ht.get(str) == null) {
                ht.put(str, str);
                scopes.setElementAt(str, i);

            } else {
                /*
                 *  Must decrement the index 'i' otherwise the next iteration
                 *  around the loop will miss the element immediately after
                 *  the element removed.
                 *
                 *  WARNING: Do not use 'i' again until the loop has
                 *           iterated as it may, after decrementing,
                 *           be negative.
                 */
                scopes.removeElementAt(i);
                i--;
                continue;
            }
        }
    }

}