root/usr/src/lib/libslp/javalib/com/sun/slp/ServiceStoreInMemory.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.
 *
 */

//  ServiceStoreInMemory.java: An in-memory implementation
//                             of the service store.
//  Author:           James Kempf
//  Created On:       Mon Oct 20 12:36:35 1997
//  Last Modified By: James Kempf
//  Last Modified On: Tue Mar  2 15:32:23 1999
//  Update Count:     472
//

package com.sun.slp;

import java.util.*;
import java.io.*;

/**
 * The ServiceStoreInMemory class implements the ServiceStore interface
 * on in-memory data structures.
 * <details of those structures here>
 *
 * @author James Kempf
 */

class ServiceStoreInMemory extends Object implements ServiceStore {

    /**
     * The BVCollector interface allows various
     * data structures to collect stuff from the BtreeVector.
     *
     * @author James Kempf
     */

    private interface BVCollector {

        // Set the return value.

        abstract void setReturn(ServiceRecordInMemory rec);

    }

    /**
     * The ParserBVCollector class implements a BtreeVector
     * collector for the parser.
     *
     * @author James Kempf
     */

    private class ParserBVCollector extends Object implements BVCollector {

        Parser.ParserRecord prReturns = null;
        private Vector scopes = null;

        ParserBVCollector(Vector scopes) {
            this.scopes = scopes;

        }

        public void setReturn(ServiceRecordInMemory rec) {

            Hashtable services = prReturns.services;
            Hashtable signatures = prReturns.signatures;
            ServiceURL surl = rec.getServiceURL();

            // Add if we don't already have it.

            if (services.get(surl) == null) {
                Vector s = (Vector)rec.getScopes().clone();

                DATable.filterScopes(s, scopes, false);

                // Need to adjust lifetime to reflect the time to live. Don't
                //  set the lifetime if it has already expired.

                long lifetime =
                    (rec.getExpirationTime() -
                     System.currentTimeMillis()) / 1000;

                if (lifetime > 0) {
                    ServiceURL url =
                        new ServiceURL(surl.toString(), (int)lifetime);

                    services.put(surl, s);

                    Hashtable sig = rec.getURLSignature();

                    if (sig != null) {
                        signatures.put(url, sig);

                    }
                }
            }
        }
    }

    /**
     * The AttributeBVCollector class implements a BtreeVector
     * collector for the collecting attribute values by type.
     *
     * @author James Kempf
     */

    private class AttributeBVCollector extends Object implements BVCollector {

        private Hashtable alreadySeen = new Hashtable();
                                                // records already seen.
        private Vector attrTags = null; // tags to match against records
        private Hashtable ht = new Hashtable(); // for collecting attributes.
        private Vector ret = null;              // for returns.

        AttributeBVCollector(Vector attrTags, Vector ret) {
            this.attrTags = attrTags;
            this.ret = ret;

        }

        public void setReturn(ServiceRecordInMemory rec) {

            // If we've got it already, then don't add again.

            if (alreadySeen.get(rec) == null) {
                alreadySeen.put(rec, rec);

                try {
                    findMatchingAttributes(rec, attrTags, ht, ret);

                } catch (ServiceLocationException ex) {

                    Assert.slpassert(false,
                                  "ssim_attrbvc_botch",
                                  new Object[] {ex.getMessage()});
                }
            }
        }
    }

    /**
     * The ScopeBVCollector class implements a BtreeVector
     * collector for the collecting records if scopes match.
     *
     * @author James Kempf
     */

    private class ScopeBVCollector extends Object implements BVCollector {

        private Hashtable alreadySeen = new Hashtable();
                                                // for those we've seen
        private Vector records = null;          // for returns.
        private Vector scopes = null;           // the scopes we're looking for

        ScopeBVCollector(Vector records, Vector scopes) {
            this.records = records;
            this.scopes = scopes;

        }

        public void setReturn(ServiceRecordInMemory rec) {

            // If we've got it already, then don't add.

            if (alreadySeen.get(rec) == null) {
                alreadySeen.put(rec, rec);

                if (scopes == null) {
                    records.addElement(rec);

                } else {

                    // Check scopes.

                    int i;
                    Vector rscopes = rec.getScopes();
                    int len = scopes.size();

                    for (i = 0; i < len; i++) {
                        if (rscopes.contains(scopes.elementAt(i))) {
                            records.addElement(rec);
                            break;

                        }
                    }
                }
            }
        }
    }

    /**
     * The AllBVCollector class implements a BtreeVector
     * collector for collecting all records.
     *
     * @author James Kempf
     */

    private class AllBVCollector extends Object implements BVCollector {

        private Vector records = null;                  // for returns.

        AllBVCollector(Vector records) {
            this.records = records;

        }

        public void setReturn(ServiceRecordInMemory rec) {

            // If we've got it already, then don't add.

            if (!records.contains(rec)) {
                records.addElement(rec);

            }
        }
    }

    /**
     * The List class implements a linked list for storing records
     * in the BtreeVector structure.
     *
     * @author James Kempf
     */

    private class List extends Object {

        ServiceRecordInMemory record = null;
        List next = null;
        List prev = null;

        // Create a new list object.

        List(ServiceRecordInMemory record) {
            this.record = record;

        }

        // Insert a new record after this one. Return the new
        //  record.

        synchronized List insertAfter(ServiceRecordInMemory record) {
            List newRec = new List(record);
            newRec.next = next;
            newRec.prev = this;

            if (next != null) {
                next.prev = newRec;

            }

            this.next = newRec;

            return newRec;

        }

        // Delete this record from the list.

        synchronized void delete() {

            if (next != null) {
                next.prev = prev;
            }

            if (prev != null) {
                prev.next = next;

            }

            prev = null;
            next = null;

        }
    }

    /**
     * The RegRecord class implements a record with the value for the
     * record buckets. It is used as elements in BtreeVector.
     *
     * @author James Kempf
     */

    private class RegRecord extends Object {

        Object value = null;            // the value for these registrations.
        List head = new List(null);     // head of the list always null,
                                        //  never changes.
        // Construct a new one.

        RegRecord(Object value) {
            this.value = value;

        }

        // Add a new record to the buckets, return new element.

        List add(ServiceRecordInMemory rec) {

            return head.insertAfter(rec);

        }

        // For every element in record's list, set the return value in the
        // returns object. Since deletions may have removed everything
        // from this record, return true only if something was there.

        boolean setReturn(BVCollector returns) {

            boolean match = false;
            List l = head;

            for (l = l.next; l != null; l = l.next) {
                ServiceRecordInMemory rec = l.record;
                returns.setReturn(rec);
                match = true;

            }

            return match;
        }

        public String toString() {
            return "<RegRecord value="+value+"list="+head.next+">";

        }
    }

    /**
     * The BtreeVector class stores registrations in sorted order. The
     * Quicksort algorithm is used to insert items and search for something.
     *
     * @author James Kempf
     */

    private class BtreeVector extends Object {

        // Contains the sorted vector.

        private Vector contents = new Vector();

        public String toString() {
            return "<BtreeVector "+contents.toString()+">";

        }

        // Return the contents as a sorted vector of RegRecord.
        //  Note that this doesn't return a copy, so
        //  the vector can be side-effected.

        Vector getContents() {
            return contents;

        }

        // Add the entire contents of the vector to the return record.

        boolean getAll(BVCollector returns) {

            int i, n = contents.size();
            boolean match = false;

            for (i = 0; i < n; i++) {
                RegRecord rec = (RegRecord)contents.elementAt(i);

                match = match | rec.setReturn(returns);
            }

            return match;
        }

        // Add a new record to this vector. We also garbage collect any
        // records that are empty. Return the list object added.

        List add(Object value, ServiceRecordInMemory record) {
            RegRecord rec = walkVector(value, true);  // an update...

            // Add the record to this one.

            return rec.add(record);

        }


        // Add only if no element in the vector matches the tag.

        boolean matchDoesNotContain(Object pattern, BVCollector returns) {

            // Go through the vector, putting in anything that isn't equal.

            int i, n = contents.size();
            Vector noMatch = new Vector();
            boolean match = false;

            for (i = 0; i < n; i++) {
                RegRecord rec = (RegRecord)contents.elementAt(i);

                if (!compareEqual(rec.value, pattern)) {

                    // Add to prospective returns.

                    noMatch.addElement(rec);

                }

            }

            // If we got this far, there are some no matches.

            n = noMatch.size();

            for (i = 0; i < n; i++) {
                RegRecord rec = (RegRecord)noMatch.elementAt(i);

                match = match | rec.setReturn(returns);

            }

            return match;

        }

        boolean
            matchEqual(Object pattern, BVCollector returns) {

            boolean match = false;

            // We can't walk the vector if the value is an AttributePattern,
            //  because equals doesn't apply.

            if (pattern instanceof AttributePattern) {
                int i, n = contents.size();

                for (i = 0; i < n; i++) {
                    RegRecord rec = (RegRecord)contents.elementAt(i);
                    AttributeString val = (AttributeString)rec.value;
                    AttributePattern pat = (AttributePattern)pattern;

                    if (pat.match(val)) {
                        match = match | rec.setReturn(returns);

                    }
                }
            } else {
                RegRecord rec = walkVector(pattern, false);
                                                        // not an update...

                // If nothing came back, return false.

                if (rec == null) {
                    match = false;

                } else {

                    // Otherwise set returns in the vector.

                    match = rec.setReturn(returns);

                }
            }

            return match;
        }

        boolean
            matchNotEqual(Object pattern, BVCollector returns) {

            // Go through the vector, putting in anything that isn't equal.

            int i, n = contents.size();
            boolean match = false;

            for (i = 0; i < n; i++) {
                RegRecord rec = (RegRecord)contents.elementAt(i);

                if (!compareEqual(rec.value, pattern)) {
                    match = match | rec.setReturn(returns);

                }
            }

            return match;
        }

        boolean
            matchLessEqual(Object pattern,
                           BVCollector returns) {

            // Go through the vector, putting in anything that is
            // less than or equal.

            int i, n = contents.size();
            boolean match = false;

            for (i = 0; i < n; i++) {
                RegRecord rec = (RegRecord)contents.elementAt(i);

                if (!compareLessEqual(rec.value, pattern)) {
                    break;

                }

                match = match | rec.setReturn(returns);
            }

            return match;
        }

        boolean
            matchNotLessEqual(Object pattern,
                              BVCollector returns) {
            // Go through the vector, putting in anything that is not
            // less than or equal. Start at the top.

            int i, n = contents.size();
            boolean match = false;

            for (i = n - 1; i >= 0; i--) {
                RegRecord rec = (RegRecord)contents.elementAt(i);

                if (compareLessEqual(rec.value, pattern)) {
                    break;

                }

                match = match | rec.setReturn(returns);
            }

            return match;
        }

        boolean
            matchGreaterEqual(Object pattern,
                              BVCollector returns) {
            // Go through the vector, putting in anything that is greater
            // than or equal. Start at the top.

            int i, n = contents.size();
            boolean match = false;

            for (i = n - 1; i >= 0; i--) {
                RegRecord rec = (RegRecord)contents.elementAt(i);

                if (!compareGreaterEqual(rec.value, pattern)) {
                    break;

                }

                match = match | rec.setReturn(returns);
            }

            return match;
        }

        boolean
            matchNotGreaterEqual(Object pattern,
                                 BVCollector returns) {
            // Go through the vector, putting in anything that is not
            // than or equal.

            int i, n = contents.size();
            boolean match = false;

            for (i = 0; i < n; i++) {
                RegRecord rec = (RegRecord)contents.elementAt(i);

                if (compareGreaterEqual(rec.value, pattern)) {
                    break;

                }

                match = match | rec.setReturn(returns);
            }

            return match;
        }

        // Binary tree walk the vector, performing the operation. Note that
        //  we use dynamic typing heavily here to get maximum code reuse.

        private RegRecord
            walkVector(Object pattern, boolean update) {

            // Get the starting set of indicies.

            int size = contents.size();
            int middle = size / 2;
            int top = size - 1;
            int bottom = 0;
            RegRecord rec = null;

            top = (top < 0 ? 0:top);

            while (size > 0) {

                // Get the one at the current middle.

                rec = (RegRecord)contents.elementAt(middle);

                // Garbage Collection.
                //  If it was null, then delete. But only if we're
                //  inserting. We leave it alone on lookup.

                if (update) {
                    if (rec.head.next == null) {

                        contents.removeElementAt(middle);

                        size = size - 1;
                        middle = bottom + (size / 2);
                        top = top - 1;

                        top = (top < 0 ? 0:top);

                        continue;
                    }
                }

                // Compare value to record, if equal, return record.
                //  code.

                if (compareEqual(rec.value, pattern)) {
                    return rec;

                } else if (compareLessEqual(pattern, rec.value)) {

                    // Recalculate index. We move left, because the value is
                    // less that the value in the vector, so an equal value
                    // must be to the left. Note that the top is not in the
                    // interval because it has already been checked and
                    // found wanting.

                    top = middle;
                    size = (top - bottom);
                    middle = top - (size / 2);
                    middle = (middle < 0 ? 0:middle);

                    if (middle == top) {

                        // Neither top nor middle are in the interval,
                        // so size is zero. We need to compare with bottom.

                        rec = null;
                        RegRecord trec = (RegRecord)contents.elementAt(bottom);

                        if (update) {
                            rec = new RegRecord(pattern);

                            // If the pattern is equal to bottom, return it.
                            // If the pattern is less than or equal to bottom,
                            // we insert it at bottom. If it is greater
                            // than or equal, we insert it at middle.

                            if (compareEqual(trec.value, pattern)) {
                                return trec;

                            } else if (compareLessEqual(pattern, trec.value)) {

                                // Pattern is less than bottom, so insert
                                // at bottom.

                                contents.insertElementAt(rec, bottom);

                            } else {
                                contents.insertElementAt(rec, middle);

                            }
                        } else {

                            // If it equals bottom, then return bottom rec.

                            if (compareEqual(trec.value, pattern)) {
                                rec = trec;

                            }
                        }

                        break;

                    }

                } else if (compareGreaterEqual(pattern, rec.value)) {

                    // Recalculate index. We move right, because the value is
                    // greater that the value in the vector, so an equal
                    // value must be to the right. Note that the top is not
                    // in the interval because it has already been checked
                    // and found wanting.

                    bottom = middle;
                    size = (top - bottom);
                    middle = bottom + (size / 2);

                    if (middle == bottom) {

                        // Neither bottom nor middle is in the interval,
                        // so size is zero. We need to compare with top.

                        rec = null;
                        RegRecord trec = (RegRecord)contents.elementAt(top);

                        if (update) {
                            rec = new RegRecord(pattern);

                            // If the pattern is equal to the top, we
                            // return the top. If the pattern is greater
                            // then top, we insert it after top, else we
                            // insert it at top.

                            if (compareEqual(trec.value, pattern)) {
                                return trec;

                            } else if (compareGreaterEqual(pattern,
                                                           trec.value)) {

                                // Pattern is greater than top, so insert
                                // after top.

                                int i = top + 1;

                                if (i >= contents.size()) {
                                    contents.addElement(rec);

                                } else {
                                    contents.insertElementAt(rec, i);

                                }
                            } else {

                                // Pattern is less than top, so insert at
                                // top, causing top to move up.

                                contents.insertElementAt(rec, top);

                            }
                        } else {

                            // If it equals top, then return top rec.

                            if (compareEqual(trec.value, pattern)) {
                                rec = trec;

                            }
                        }

                        break;

                    }
                }
            }

            // Take care of update where vector is empty or cleaned out.

            if (update && rec == null) {
                rec = new RegRecord(pattern);

                Assert.slpassert((contents.size() == 0),
                              "ssim_btree_botch",
                              new Object[0]);

                contents.addElement(rec);
            }

            return rec;
        }

        // Add any registrations that match the pattern.

        boolean
            compareEqual(Object target, Object pattern) {

            if (target instanceof Integer ||
                target instanceof Boolean ||
                target instanceof Opaque ||
                target instanceof Long) {
                if (pattern.equals(target)) {
                    return true;

                }

            } else if (target instanceof AttributeString) {

                // If the pattern is an AttributePattern instead of an
                // AttributeString, the subclass method will get invoked.

                if (((AttributeString)pattern).match(
                                                (AttributeString)target)) {
                    return true;

                }

            } else {
                Assert.slpassert(false,
                              "ssim_unk_qtype",
                              new Object[] {pattern.getClass().getName()});
            }

            return false;

        }

        // Add any registrations that are less than or equal to the pattern.

        boolean
            compareLessEqual(Object target, Object pattern) {

            if (target instanceof Integer) {
                if (((Integer)target).intValue() <=
                    ((Integer)pattern).intValue()) {
                    return true;

                }

            } else if (target instanceof AttributeString) {

                if (((AttributeString)target).lessEqual(
                                                (AttributeString)pattern)) {
                    return true;

                }

            } else if (target instanceof Long) {
                if (((Long)target).longValue() <=
                    ((Long)pattern).longValue()) {
                    return true;

                }

            } else if (target instanceof Boolean ||
                       target instanceof Opaque) {
                if (target.toString().compareTo(pattern.toString()) <= 0) {
                    return true;

                }
            } else {
                Assert.slpassert(false,
                              "ssim_unk_qtype",
                              new Object[] {target.getClass().getName()});
            }

            return false;

        }

        // Add any registrations that are greater than or equal to the pattern.

        boolean
            compareGreaterEqual(Object target, Object pattern) {

            if (target instanceof Integer) {
                if (((Integer)target).intValue() >=
                    ((Integer)pattern).intValue()) {
                    return true;

                }

            } else if (target instanceof AttributeString) {

                if (((AttributeString)target).greaterEqual(
                                                (AttributeString)pattern)) {
                    return true;

                }

            } else if (target instanceof Long) {
                if (((Long)target).longValue() >=
                    ((Long)pattern).longValue()) {
                    return true;

                }

            } else if (target instanceof Boolean ||
                       target instanceof Opaque) {
                if (target.toString().compareTo(pattern.toString()) >= 0) {
                    return true;

                }

            } else {
                Assert.slpassert(false,
                              "ssim_unk_qtype",
                              new Object[] {target.getClass().getName()});
            }

            return false;

        }
    }

    /**
     * The InMemoryEvaluator evaluates queries for ServiceStoreInMemory.
     *
     * @author James Kempf
     */

    private class InMemoryEvaluator implements Parser.QueryEvaluator {

        private Hashtable attrLevel;    // Sorted attribute table.
        private BtreeVector attrLevelNot;   // Used for universal negation.
        private Vector inScopes;            // Input scopes.
        private ParserBVCollector returns;  // For gathering results.

        InMemoryEvaluator(Hashtable ht,
                          BtreeVector btv,
                          Vector nscopes) {
            attrLevel = ht;
            attrLevelNot = btv;
            inScopes = nscopes;
            returns = new ParserBVCollector(inScopes);


        }

        // Evaluate the query by matching the attribute tag and
        //  value, using the operator. If invert is true, then
        //  return records that do NOT match.

        public boolean
            evaluate(AttributeString tag,
                     char op,
                     Object pattern,
                     boolean invert,
                     Parser.ParserRecord prReturns)
            throws ServiceLocationException {

            boolean match = false;
            returns.prReturns = prReturns;

            // If inversion is on, then gather all from the
            //  table of registrations that do NOT have this
            //  attribute.

            if (invert) {
                match = attrLevelNot.matchDoesNotContain(tag, returns);

            }

            // Find the table of classes v.s. sorted value vectors.

            Hashtable ttable = (Hashtable)attrLevel.get(tag);

            // If attribute not present, then simply return.

            if (ttable == null) {

                return match;

            }

            // If operator is present, then return all.

            if (op == Parser.PRESENT) {

                // ...but only if invert isn't on.

                if (!invert) {

                    // We use attrLevelNot to get all, because it
                    //  will also pick up keywords. There are
                    //  no keywords in attrLevel because keywords
                    //  don't have any values.

                    match = attrLevelNot.matchEqual(tag, returns);

                }

                return match;
            }

            // We know that the type table is fully initialized with
            //  BtreeVectors for each type.

            // Get the pattern's class. Pattern will not be null because
            //  the parser has checked for it and PRESENT has been
            //  filtered out above.

            Class pclass = pattern.getClass();
            String typeKey = pclass.getName();

            // If the class is AttributePattern, then use AttributeString
            //  instead.

            if (pattern instanceof AttributePattern) {
                typeKey = pclass.getSuperclass().getName();

            }

            // If invert is on, collect those whose types don't match as
            //  well.

            if (invert) {
                Enumeration en = ttable.keys();

                while (en.hasMoreElements()) {
                    String key = (String)en.nextElement();

                    // Only record if the type does NOT match.

                    if (!key.equals(typeKey)) {
                        BtreeVector bvec = (BtreeVector)ttable.get(key);

                        match = match | bvec.getAll(returns);

                    }
                }
            }

            // Get the sorted value vector corresponding to the value class.

            BtreeVector bvec = (BtreeVector)ttable.get(typeKey);

            // Do the appropriate thing for the operator.

            switch (op) {

            case Parser.EQUAL:

                if (!invert) {
                    match = bvec.matchEqual(pattern, returns);

                } else {
                    match = bvec.matchNotEqual(pattern, returns);

                }
                break;

            case Parser.LESS:

                // Note that we've filtered out Opaque, Boolean, and wildcarded
                // strings before calling this method.

                if (!invert) {
                    match = bvec.matchLessEqual(pattern, returns);

                } else {
                    match = bvec.matchNotLessEqual(pattern, returns);

                }
                break;

            case Parser.GREATER:

                // Note that we've filtered out Opaque and Boolean
                // before calling this method.

                if (!invert) {
                    match = bvec.matchGreaterEqual(pattern, returns);

                } else {
                    match = bvec.matchNotGreaterEqual(pattern, returns);

                }
                break;

            default:
                Assert.slpassert(false,
                              "ssim_unk_qop",
                              new Object[] {Character.valueOf((char)op)});
            }

            return match;
        }
    }

    /**
     * The ServiceRecordInMemory class implements the
     * ServiceStore.ServiceRecord interface on in-memory data structures.
     * Each property is implemented as an instance variable.
     *
     * @author James Kempf
     */

    private class ServiceRecordInMemory extends Object
        implements ServiceStore.ServiceRecord {

        private ServiceURL serviceURL = null;   // the service URL
        private Vector attrList = null;         // the attribute list
        private Locale locale = null;           // the locale
        private long timeToDie = 0;             // when the record should die.
        private Vector scopes = null;           // the scopes
        private Hashtable urlSig = null;
                                // URL signature block list, if any.
        private Hashtable attrSig = null;
                                // Attribute signature block list, if any.

        // Create a ServiceStoreInMemory record.

        ServiceRecordInMemory(ServiceURL surl, Vector alist,
                              Vector nscopes, Locale loc,
                              Hashtable nurlSig,
                              Hashtable nattrSig) {

            // All need to be nonnull.

            Assert.nonNullParameter(surl, "surl");
            Assert.nonNullParameter(alist, "alist");
            Assert.nonNullParameter(nscopes, "nscopes");
            Assert.nonNullParameter(loc, "loc");

            serviceURL = surl;
            attrList = attributeVectorToServerAttribute(alist, loc);
            scopes = nscopes;
            locale = loc;
            urlSig = nurlSig;
            attrSig = nattrSig;

            int lifetime = serviceURL.getLifetime();

            timeToDie = lifetime * 1000 + System.currentTimeMillis();
        }

        /**
         * Return the ServiceURL for the record.
         *
         * @return The record's service URL.
         */

        public final ServiceURL getServiceURL() {
            return serviceURL;

        }

        /**
         * Return the Vector of ServerAttribute objects for the record.
         *
         * @return Vector of ServerAttribute objects for the record.
         */

        public final Vector getAttrList() {
            return attrList;

        }

        /**
         * Return the locale of the registration.
         *
         * @return The locale of the registration.
         */

        public final Locale getLocale() {
            return locale;

        }

        /**
         * Return the Vector of scopes in which the record is registered.
         *
         * @return Vector of strings with scope names.
         */

        public final Vector getScopes() {
            return scopes;

        }

        /**
         * Return the expiration time for the record. This informs the
         * service store when the record should expire and be removed
         * from the table.
         *
         * @return The expiration time for the record.
         */

        public long getExpirationTime() {
            return timeToDie;

        }

        /**
         * Return the URL signature list.
         *
         * @return URL signature block list.
         */

        public Hashtable getURLSignature() {
            return urlSig;

        }

        /**
         * Return the attribute signature list.
         *
         * @return Attribute signature list.
         */

        public Hashtable getAttrSignature() {
            return attrSig;

        }


        //
        // Package-local methods.

        final void setAttrList(Vector newList) {
            attrList = newList;

        }

        final void setScopes(Vector newScopes) {
            scopes = newScopes;

        }

        final void setURLSignature(Hashtable nauth) {
            urlSig = nauth;

        }

        final void setAttrSignature(Hashtable nauth) {
            attrSig = nauth;

        }

        public String toString() {

            String ret = "{";

            ret +=
                serviceURL + ", " + locale + ", " + attrList + ", " +
                scopes + ", " + locale + ", " + urlSig + ", " + attrSig;

            ret += "}";

            return ret;
        }

        // Convert a vector of ServiceLocationAttribute objects to
        // ServerAttibutes.

        private Vector
            attributeVectorToServerAttribute(Vector attrs, Locale locale) {
            int i, n = attrs.size();
            Vector v = new Vector();

            for (i = 0; i < n; i++) {
                ServiceLocationAttribute attr =
                    (ServiceLocationAttribute)attrs.elementAt(i);

                v.addElement(new ServerAttribute(attr, locale));
            }

            return v;
        }

    }

    /**
     * A record for scopeTypeLangTable table,
     *
     * @author James Kempf
     */

    private class STLRecord extends Object {

        Hashtable attrValueSort = new Hashtable();
                                // Table of attributes, sorted by value.
        BtreeVector attrSort = new BtreeVector();       // Btree of attributes.
        boolean isAbstract = false;
                                // True if the record is for an abstract
                                //  type.
        STLRecord(boolean isAbstract) {
            this.isAbstract = isAbstract;

        }
    }

    //
    // ServiceStoreInMemory instance variables.
    //

    // ServiceStoreInMemory maintains an invaraint that the record for a
    //  particular URL, set of scopes, and locale is the same object
    //  (pointer-wise) regardless of where it is inserted into the table.
    //  So it can be compared with ==.

    // The scopeTypeLangTable
    //
    //  Keys for this table are scope/service type/lang tag. Values are
    //  STLRecord objects. The STLRecord.attrValueSort field is a Hashtable
    //  where all registrations *having* the attribute tag keys in the
    //  table are contained. This table is used in queries for positive
    //  logical expressions. The STLRecord.attrSort field is a BtreeVector
    //  keyed by attribute. It is used for negative queries to find all
    //  records not having a particular attribute and to find all
    //  registrations. The STLRecord.isAbstract field tells whether the record
    //  is for an abstract type name.
    //
    //  The values in the STLRecord.attrValueSort hashtable are themselves
    //  hashtables. These hashtables are keyed by one of the type keys below,
    //  with  the values being BtreeVector objects. The BtreeVector objects
    //  contain sorted lists of RegRecord objects for Integer,
    //  AttributeString, Boolean, and Opaque types. All records having
    //  values equal to the value in the RegRecord are put into a list
    //  on the RegRecord. There is no STLRecord.attrValueSort
    //  hashtable for keyword attributes because they have no values.
    //  The parser evaluator must use the STLRecord.attrSort hashtable when a
    //  present operator is encountered (the only valid operator with a
    //  keyword).
    //
    //  The values in the STLRecord.attrSort BtreeVector are RegRecord
    //  objects with all records having that attribute tag being on the
    //  RegRecord list.

    // Keys for the various types.

    private final static String INTEGER_TYPE = "java.lang.Integer";
    private final static String ATTRIBUTE_STRING_TYPE =
        "com.sun.slp.AttributeString";
    private final static String BOOLEAN_TYPE = "java.lang.Boolean";
    private final static String OPAQUE_TYPE = "com.sun.slp.Opaque";

    private Hashtable scopeTypeLangTable = new Hashtable();

    //  The urlScopeLangTable
    //
    //  Keys for this table are service url as a string. We don't use
    //  the service URL itself because the hash code depends on the
    //  current service type rather than the original, and we need
    //  to be able to distinguish for a non-service: URL if a
    //  registration comes in with a different service type from the
    //  original. Values are hashtables with key being scope name,
    //  values are hashtables with lang tag key. Ultimate values are
    //  a vector of List objects for lists in which List.record is
    //  inserted. This table is used to perform deletions and for
    //  finding the attributes associated with a particular URL.

    private Hashtable urlScopeLangTable = new Hashtable();

    //  The sstLocales Table
    //
    //  The scope/service type v.s. number of languages. Keys are
    //  the type/scope, values are a hashtable keyed by lang tag.
    //  Values in the lang tag table are Integer objects giving
    //  the number of registrations for that type/scope in the
    //  given locale.

    private Hashtable sstLocales = new Hashtable();

    // A queue of records sorted according to expiration time.

    BtreeVector ageOutQueue = new BtreeVector();

    // Constants that indicate whether there are any registrations.

    private final static int NO_REGS = 0;
    private final static int NO_REGS_IN_LOCALE = 1;
    private final static int REGS_IN_LOCALE = 2;

    // Boot time. For DAAdvert timestamps.

    private long bootTime = SLPConfig.currentSLPTime();

    //
    // ServiceStore Interface Methods.
    //

    /**
     * Return the time since the last stateless reboot
     * of the ServiceStore.
     *
     * @return A Long giving the time since the last stateless reboot,
     *         in NTP format.
     */

    public long getStateTimestamp() {

        return bootTime;

    }

    /**
     * Age out all records whose time has expired.
     *
     * @param deleted A Vector for return of ServiceStore.Service records
     *               containing deleted services.
     * @return The time interval until another table walk must be done,
     *         in milliseconds.
     *
     */

    synchronized public long ageOut(Vector deleted) {

        // Get the ageOut queue and remove all records whose
        // time has popped.

        SLPConfig conf = SLPConfig.getSLPConfig();
        boolean traceDrop = conf.traceDrop();
        Vector queue = ageOutQueue.getContents();

        // Go through the queue, dropping records that
        //  have expired.

        int i;

        for (i = 0; i < queue.size(); i++) {
            RegRecord qRec = (RegRecord)queue.elementAt(i);
            long exTime = ((Long)(qRec.value)).longValue();
            long time = System.currentTimeMillis();

            // Break out when none expire now.

            if (exTime > time) {
                break;

            }

            // Remove the element from the queue.

            /*
             *  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.
             */
            queue.removeElementAt(i);
            i--;

            // Deregister all on this list. We
            // take specific care to save the next
            // list element before we deregister, otherwise
            // it will be gone after the deregister.

            List l = qRec.head.next;

            while (l != null) {
                ServiceRecordInMemory rec = l.record;
                ServiceURL url = rec.getServiceURL();
                Vector scopes = rec.getScopes();
                Locale locale = rec.getLocale();

                if (traceDrop) {
                    conf.writeLog("ssim_ageout",
                                  new Object[] {
                        url,
                            rec.getAttrList(),
                            scopes,
                            locale,
                            rec.getURLSignature(),
                            rec.getAttrSignature(),
                            Long.toString(time),
                            Long.toString(exTime)});
                }

                // Save the record for the service table, in case more
                // processing needed.

                deleted.addElement(rec);

                // Save l.next NOW before deregisterInternal() removes it!

                l = l.next;

                String lang = locale.getLanguage();

                deregisterInternal(url, scopes, lang);

            }
        }

        // Calculate the new sleep time. If there's anything in the vector,
        // then use element 0, because the vector is sorted by time
        // and that will be minimum. Otherwise, use the maximum.

        long newSleepy = Defaults.lMaxSleepTime;

        if (queue.size() > 0) {
            RegRecord rec = (RegRecord)queue.elementAt(0);

            newSleepy =
                ((Long)(rec.value)).longValue() - System.currentTimeMillis();

            newSleepy = (newSleepy > 0 ? newSleepy:0);
                                                // it will wake right up, but
                                                // so what?

        }

        return newSleepy;

    }

    /**
     * Create a new registration with the given parameters.
     *
     * @param url The ServiceURL.
     * @param attrs The Vector of ServiceLocationAttribute objects.
     * @param locale The Locale.
     * @param scopes Vector of scopes in which this record is registered.
     * @param urlSig auth block Hashtable for URL signature, or null if none.
     * @param attrSig auth block Hashtable for URL signature, or null if none.
     * @return True if there is an already existing registration that
     *         this one replaced.
     * @exception ServiceLocationException Thrown if any
     *                  error occurs during registration or if the table
     *                  requires a network connection that failed. This
     *                  includes timeout failures.
     */

    synchronized public boolean
        register(ServiceURL url, Vector attrs,
                 Vector scopes, Locale locale,
                 Hashtable urlSig, Hashtable attrSig)
        throws ServiceLocationException {

        boolean existing = false;

        String lang = locale.getLanguage();

        // Find an existing record, in any set of scopes having this language.

        ServiceRecordInMemory rec = findExistingRecord(url, null, lang);

        // Deregister from existing scopes, if there is an existing record.

        if (rec != null) {
            if (urlSig != null) {
                // Ensure that the rereg SPI set and the record's SPI set are
                // equivalent. We need only check the URL sigs here, since
                // this operation is equivalent to a dereg followed by a reg,
                // and dereg requires only URL auth blocks.

                Enumeration spis = urlSig.keys();
                while (spis.hasMoreElements()) {
                    Object spi = spis.nextElement();
                    if (rec.urlSig.remove(spi) == null) {
                        throw new ServiceLocationException(
                                ServiceLocationException.AUTHENTICATION_FAILED,
                                "not_all_spis_present",
                                new Object[] {spi});
                    }
                }
                if (rec.urlSig.size() != 0) {
                    // not all required SPIs were present in SrvReg
                    throw new ServiceLocationException(
                                ServiceLocationException.AUTHENTICATION_FAILED,
                                "not_all_spis_present",
                                new Object[] {rec.urlSig.keys()});
                }
            }

            deregisterInternal(url, rec.getScopes(), lang);
            existing = true;

        }

        // Create a new record to register.

        rec = new ServiceRecordInMemory(url, attrs, scopes,
                                        locale, urlSig, attrSig);

        // Add new registration.

        registerInternal(rec);

        return existing;

    }

    /**
     * Deregister a ServiceURL from the database for every locale
     * and every scope. There will be only one record for each URL
     * and locale deregistered, regardless of the number of scopes in
     * which the URL was registered, since the attributes will be the
     * same in each scope if the locale is the same.
     *
     * @param url The ServiceURL
     * @param scopes Vector of scopes.
     * @param urlSig The URL signature, if any.
     * @exception ServiceLocationException Thrown if the
     *                  ServiceStore does not contain the URL, or if any
     *                  error occurs during the operation, or if the table
     *                  requires a network connection that failed. This
     *                  includes timeout failures.
     */

    synchronized public void
        deregister(ServiceURL url, Vector scopes, Hashtable urlSig)
        throws ServiceLocationException {

        // Find existing record. Any locale will do.

        ServiceRecordInMemory oldRec =
            findExistingRecord(url, scopes, null);

        // Error if none.

        if (oldRec == null) {
            throw
                new ServiceLocationException(
                                ServiceLocationException.INVALID_REGISTRATION,
                                "ssim_no_rec",
                                new Object[] {url});

        }

        // verify that the dereg SPI set and the record's SPI set are
        // equivalent
        if (urlSig != null) {
            Enumeration spis = urlSig.keys();
            while (spis.hasMoreElements()) {
                Object spi = spis.nextElement();
                if (oldRec.urlSig.remove(spi) == null) {
                    throw new ServiceLocationException(
                                ServiceLocationException.AUTHENTICATION_FAILED,
                                "not_all_spis_present",
                                new Object[] {spi});
                }
            }
            if (oldRec.urlSig.size() != 0) {
                // not all required SPIs were present in SrvDereg
                throw new ServiceLocationException(
                                ServiceLocationException.AUTHENTICATION_FAILED,
                                "not_all_spis_present",
                                new Object[] {oldRec.urlSig.keys()});
            }
        }

        /*
         * Deregister the URL for all locales. Use the recorded service URL
         * because the one passed by the client is possibly incomplete e.g.
         * lacking the service type.
         */

        deregisterInternal(oldRec.getServiceURL(), scopes, null);

    }

    /**
     * Update the service registration with the new parameters, adding
     * attributes and updating the service URL's lifetime.
     *
     * @param url The ServiceURL.
     * @param attrs The Vector of ServiceLocationAttribute objects.
     * @param locale The Locale.
     * @param scopes Vector of scopes in which this record is registered.
     * @exception ServiceLocationException Thrown if any
     *                  error occurs during registration or if the table
     *                  requires a network connection that failed. This
     *                  includes timeout failures.
     */

    synchronized public void
        updateRegistration(ServiceURL url, Vector attrs,
                           Vector scopes, Locale locale)
        throws ServiceLocationException {

        String lang = locale.getLanguage();
        ServiceRecordInMemory oldRec =
            findExistingRecord(url, scopes, lang);

        // Error if none.

        if (oldRec == null) {
            throw
                new ServiceLocationException(
                                ServiceLocationException.INVALID_UPDATE,
                                "ssim_no_rec",
                                new Object[] {url});

        }

        // If this is a nonServiceURL, check whether it's registered
        //  under a different service type.

        ServiceType type = url.getServiceType();

        if (!type.isServiceURL()) {
            checkForExistingUnderOtherServiceType(url, scopes);

        }

        // Deregister the URL in this locale.

        deregisterInternal(url, scopes, lang);

        // Create a new record to update.

        ServiceRecordInMemory rec =
            new ServiceRecordInMemory(url, attrs, scopes,
                                      locale, null, null);

        // Merge old record into new.

        mergeOldRecordIntoNew(oldRec, rec);

        // Add the new record.

        registerInternal(rec);
    }

    /**
     * Delete the attributes from the ServiceURL object's table entries.
     * Delete for every locale that has the attributes and every scope.
     * Note that the attribute tags must be lower-cased in the locale of
     * the registration, not in the locale of the request.
     *
     * @param url The ServiceURL.
     * @param scopes Vector of scopes.
     * @param attrTags The Vector of String
     *                  objects specifying the attribute tags of
     *                  the attributes to delete.
     * @param locale Locale of the request.
     * @exception ServiceLocationException Thrown if the
     *                  ServiceStore does not contain the URL or if any
     *                  error occurs during the operation or if the table
     *                  requires a network connection that failed. This
     *                  includes timeout failures.
     */

    synchronized public void
        deleteAttributes(ServiceURL url,
                         Vector scopes,
                         Vector attrTags,
                         Locale locale)
        throws ServiceLocationException {

        String lang = SLPConfig.localeToLangTag(locale);

        // Get the scope level from urlScopeLangTable.

        Hashtable scopeLevel =
            (Hashtable)urlScopeLangTable.get(url.toString());

        // Error if no old record to update.

        if (scopeLevel == null) {
            throw
                new ServiceLocationException(
                                ServiceLocationException.INVALID_REGISTRATION,
                                "ssim_no_rec",
                                new Object[] {url});

        }

        // Check existing records to be sure that the scopes
        //  match. Attributes must be the same across
        //  scopes.

        checkScopeStatus(url,
                         scopes,
                         ServiceLocationException.INVALID_REGISTRATION);

        // Create attribute patterns for the default locale. This
        //  is an optimization. Only Turkish differs in lower
        //  case from the default. If there are any other exceptions,
        //  we need to move this into the loop.

        Vector attrPatterns =
            stringVectorToAttributePattern(attrTags, Defaults.locale);

        // Look through the language table for this language at scope level.

        Enumeration en = scopeLevel.keys();

        Assert.slpassert(en.hasMoreElements(),
                      "ssim_empty_scope_table",
                      new Object[] {url});

        Hashtable ht = new Hashtable();
        boolean foundIt = false;

        while (en.hasMoreElements()) {
            String scope = (String)en.nextElement();
            Hashtable langLevel = (Hashtable)scopeLevel.get(scope);
            Enumeration een = langLevel.keys();

            Assert.slpassert(een.hasMoreElements(),
                          "ssim_empty_lang_table",
                          new Object[] {url});

            // Find the list of records for this language.

            Vector listVec = (Vector)langLevel.get(lang);

            if (listVec == null) {
                continue;

            }

            foundIt = true;

            List elem = (List)listVec.elementAt(0);
            ServiceRecordInMemory rec = elem.record;
            Locale loc = rec.getLocale();

            // If we've done this one already, go on.

            if (ht.get(rec) != null) {
                continue;

            }

            ht.put(rec, rec);

            // Delete old registration.

            deregisterInternal(url, rec.getScopes(), lang);

            // Delete attributes from this record.

            // If the locale is Turkish, then use the Turkish patterns.

            if (loc.getLanguage().equals("tr")) {
                Vector turkishTags =
                    stringVectorToAttributePattern(attrTags, loc);

                deleteAttributes(rec, turkishTags);

            } else {
                deleteAttributes(rec, attrPatterns);

            }

            // Reregister the record.

            registerInternal(rec);
        }

        // If no record found, report error.

        if (!foundIt) {
            throw
                new ServiceLocationException(
                                ServiceLocationException.INVALID_REGISTRATION,
                                "ssim_no_rec_locale",
                                new Object[] {url, locale});

        }

    }

    /**
     * Return a Vector of String containing the service types for this
     * scope and naming authority. If there are none, an empty vector is
     * returned.
     *
     * @param namingAuthority The namingAuthority, or "*" if for all.
     * @param scopes The scope names.
     * @return A Vector of String objects that are the type names, or
     *          an empty vector if there are none.
     * @exception ServiceLocationException Thrown if any
     *                  error occurs during the operation or if the table
     *                  requires a network connection that failed. This
     *                  includes timeout failures.
     */

    synchronized public Vector
        findServiceTypes(String namingAuthority, Vector scopes)
        throws ServiceLocationException {

        Vector ret = new Vector();
        Enumeration keys = scopeTypeLangTable.keys();
        boolean isWildCard = namingAuthority.equals("*");
        boolean isIANA = (namingAuthority.length() <= 0);

        // Get all the keys in the table, look for scope.

        while (keys.hasMoreElements()) {
            String sstKey = (String)keys.nextElement();

            // Check whether this is an abstract type entry.
            //  If so, then we ignore it, because we only
            //  want full type names in the return.

            if (isAbstractTypeRecord(sstKey)) {
                continue;

            }

            // If the scope matches then check the naming authority.

            String keyScope = keyScope(sstKey);

            if (scopes.contains(keyScope)) {
                String keyType = keyServiceType(sstKey);

                // If not already there, see if we should add this one to the
                //  vector.

                if (!ret.contains(keyType)) {
                    ServiceType type = new ServiceType(keyType);

                    // If wildcard, then simply add it to the vector.

                    if (isWildCard) {
                        ret.addElement(type.toString());

                    } else {

                        // Check naming authority.

                        String na = type.getNamingAuthority();

                        if (type.isNADefault() && isIANA) { // check for IANA..
                            ret.addElement(type.toString());

                        } else if (namingAuthority.equals(na)) { // Not IANA..
                            ret.addElement(type.toString());

                        }
                    }
                }
            }
        }

        return ret;
    }

    /**
     * Return a Hashtable with the key FS_SERVICES matched to the
     * hashtable of ServiceURL objects as key and a vector
     * of their scopes as value, and the key FS_SIGTABLE
     * matched to a hashtable with ServiceURL objects as key
     * and the auth block Hashtable for the URL (if any) for value. The
     * returned service URLs will match the service type, scope, query,
     * and locale. If there are no signatures, the FS_SIGTABLE
     * key returns null. If there are no
     * registrations in any locale, FS_SERVICES is bound to an
     * empty table.
     *
     * @param serviceType The service type name.
     * @param scope The scope name.
     * @param query The query, with any escaped characters as yet unprocessed.
     * @param locale The locale in which to lowercase query and search.
     * @return A Hashtable with the key FS_SERVICES matched to the
     *         hashtable of ServiceURL objects as key and a vector
     *         of their scopes as value, and the key FS_SIGTABLE
     *         matched to a hashtable with ServiceURL objects as key
     *         and the auth block Hashtable for the URL (if any) for value.
     *         If there are no registrations in any locale, FS_SERVICES
     *        is bound to an empty table.
     * @exception ServiceLocationException Thrown if a parse error occurs
     *                  during query parsing or if any
     *                  error occurs during the operation or if the table
     *                  requires a network connection that failed. This
     *                  includes timeout failures.
     */

    synchronized public Hashtable
        findServices(String serviceType,
                     Vector scopes,
                     String query,
                     Locale locale)
        throws ServiceLocationException {

        String lang = locale.getLanguage();
        Parser.ParserRecord ret = new Parser.ParserRecord();
        Hashtable services = null;
        Hashtable signatures = null;
        int i, n = scopes.size();
        int len = 0;

        // Get the services and signatures tables.

        services = ret.services;
        signatures = ret.signatures;

        // Remove leading and trailing spaces.

        query = query.trim();
        len = query.length();

        // Check whether there are any registrations for this type/scope/
        //  language tag and, if not, whether there are others.
        //  in another language, but not this one.

        int regStatus = languageSupported(serviceType, scopes, lang);

        if (regStatus == NO_REGS_IN_LOCALE) {
            throw
                new ServiceLocationException(
                        ServiceLocationException.LANGUAGE_NOT_SUPPORTED,
                        "ssim_lang_unsup",
                        new Object[] {locale});

        } else if (regStatus == REGS_IN_LOCALE) {

            // Only do query if regs exist.

            for (i = 0; i < n; i++) {
                String scope = (String)scopes.elementAt(i);
                String sstKey =
                    makeScopeTypeLangKey(scope, serviceType, lang);
                STLRecord regRecs =
                    (STLRecord)scopeTypeLangTable.get(sstKey);

                // If no record for this combo of service type and
                //  scope, continue.

                if (regRecs == null) {
                    continue;
                }

                // Special case if the query string is empty. This
                //  indicates that all registrations should be returned.

                if (len <= 0) {
                    BtreeVector bvec = regRecs.attrSort;
                    ParserBVCollector collector =
                        new ParserBVCollector(scopes);
                    collector.prReturns = ret;

                    // Use the BtreeVector.getAll() method to get all
                    //  registrations. We will end up revisiting some
                    //  list elements because there will be ones
                    //  for multiple attributes, but that will be
                    //  filtered in the BVCollector.setReturn() method.

                    bvec.getAll(collector);

                } else {

                    // Otherwise, use the LDAPv3 parser to evaluate.

                    InMemoryEvaluator ev =
                        new InMemoryEvaluator(regRecs.attrValueSort,
                                              regRecs.attrSort,
                                              scopes);

                    Parser.parseAndEvaluateQuery(query, ev, locale, ret);

                }
            }
        }

        // Create return hashtable.

        Hashtable ht = new Hashtable();

        // Set up return hashtable.

        ht.put(ServiceStore.FS_SERVICES, services);

        // Put in signatures if there.

        if (signatures.size() > 0) {
            ht.put(ServiceStore.FS_SIGTABLE, signatures);

        }

        return ht;
    }

    /**
     * Return a Hashtable with key FA_ATTRIBUTES matched to the
     * vector of ServiceLocationAttribute objects and key FA_SIG
     * matched to the auth block Hashtable for the attributes (if any)
     * The attribute objects will have tags matching the tags in
     * the input parameter vector. If there are no registrations in any locale,
     * FA_ATTRIBUTES is an empty vector.
     *
     * @param url The ServiceURL for which the records should be returned.
     * @param scopes The scope names for which to search.
     * @param attrTags The Vector of String
     *                  objects containing the attribute tags.
     * @param locale The locale in which to lower case tags and search.
     * @return A Hashtable with a vector of ServiceLocationAttribute objects
     *         as the key and the auth block Hashtable for the attributes
     *         (if any) as the value.
     *         If there are no registrations in any locale, FA_ATTRIBUTES
     *         is an empty vector.
     * @exception ServiceLocationException Thrown if any
     *                  error occurs during the operation or if the table
     *                  requires a network connection that failed. This
     *                  includes timeout failures. An error should be
     *                  thrown if the tag vector is for a partial request
     *                  and any of the scopes are protected.
     */

    synchronized public Hashtable
        findAttributes(ServiceURL url,
                       Vector scopes,
                       Vector attrTags,
                       Locale locale)
        throws ServiceLocationException {

        Hashtable ht = new Hashtable();
        Vector ret = new Vector();
        String lang = locale.getLanguage();
        Hashtable sig = null;

        // Check whether there are any registrations for this scope/type
        //  language and, if not, whether there are others.
        //  in another language, but not this one.

        int regStatus =
            languageSupported(url.getServiceType().toString(), scopes, lang);

        if (regStatus == NO_REGS_IN_LOCALE) {
            throw
                new ServiceLocationException(
                        ServiceLocationException.LANGUAGE_NOT_SUPPORTED,
                        "ssim_lang_unsup",
                        new Object[] {locale});

        } else if (regStatus == REGS_IN_LOCALE) {

            // Only if there are any regs at all.

            // Process string tags into pattern objects. Note that, here,
            //  the patterns are locale specific because the locale of
            //  the request determines how the attribute tags are lower
            //  cased.

            attrTags = stringVectorToAttributePattern(attrTags, locale);

            // Return attributes from the matching URL record.

            Hashtable scopeLevel =
                (Hashtable)urlScopeLangTable.get(url.toString());

            // If nothing there, then simply return. The URL isn't
            //  registered.

            if (scopeLevel != null) {

                // We reuse ht here for attributes.

                int i, n = scopes.size();

                for (i = 0; i < n; i++) {
                    String scope = (String)scopes.elementAt(i);
                    Hashtable langLevel =
                        (Hashtable)scopeLevel.get(scope);

                    // If no registration in this scope, continue.

                    if (langLevel == null) {
                        continue;
                    }

                    // Get the vector of lists.

                    Vector listVec = (Vector)langLevel.get(lang);

                    // If no registration in this locale, continue.

                    if (listVec == null) {
                        continue;

                    }

                    // Get the service record.

                    List elem = (List)listVec.elementAt(0);
                    ServiceRecordInMemory rec = elem.record;

                    // Once we've found *the* URL record, we can leave the loop
                    //  because there is only one record per locale.

                    findMatchingAttributes(rec, attrTags, ht, ret);

                    // Clear out the hashtable. We reuse it for the return.

                    ht.clear();

                    // Store the return vector and the signatures, if any.

                    ht.put(ServiceStore.FA_ATTRIBUTES, ret);

                    sig = rec.getAttrSignature();

                    if (sig != null) {
                        ht.put(ServiceStore.FA_SIG, sig);

                    }

                    break;

                }
            }
        }

        // Put in the empty vector, in case there are no regs at all.

        if (ht.size() <= 0) {
            ht.put(ServiceStore.FA_ATTRIBUTES, ret);

        }

        return ht;
    }

    /**
     * Return a Vector of ServiceLocationAttribute objects with attribute tags
     * matching the tags in the input parameter vector for all service URL's
     * of the service type. If there are no registrations
     * in any locale, an empty vector is returned.
     *
     * @param serviceType The service type name.
     * @param scopes The scope names for which to search.
     * @param attrTags The Vector of String
     *                  objects containing the attribute tags.
     * @param locale The locale in which to lower case tags.
     * @return A Vector of ServiceLocationAttribute objects matching the query.
     *         If no match occurs but there are registrations
     *        in other locales, null is returned. If there are no registrations
     *         in any locale, an empty vector is returned.
     * @exception ServiceLocationException Thrown if any
     *           error occurs during the operation or if the table
     *           requires a network connection that failed. This
     *           includes timeout failures. An error should also be
     *            signalled if any of the scopes are protected.
     */

    synchronized public Vector
        findAttributes(String serviceType,
                       Vector scopes,
                       Vector attrTags,
                       Locale locale)
        throws ServiceLocationException {

        String lang = locale.getLanguage();
        Vector ret = new Vector();

        // Check whether there are any registrations for this type/scope/
        //  language and, if not, whether there are others.
        //  in another language, but not this one.

        int regStatus = languageSupported(serviceType, scopes, lang);

        if (regStatus == NO_REGS_IN_LOCALE) {
            throw
                new ServiceLocationException(
                        ServiceLocationException.LANGUAGE_NOT_SUPPORTED,
                        "ssim_lang_unsup",
                        new Object[] {locale});

        } else if (regStatus == REGS_IN_LOCALE) {

            // Process string tags into pattern objects. Note that, here,
            //  the patterns are locale specific because the locale of
            //  the request determines how the attribute tags are lower
            //  cased.

            attrTags = stringVectorToAttributePattern(attrTags, locale);
            int len = attrTags.size();

            // Make a collector for accessing the BtreeVector.

            BVCollector collector =
                new AttributeBVCollector(attrTags, ret);
            int i, n = scopes.size();

            for (i = 0; i < n; i++) {
                String scope = (String)scopes.elementAt(i);
                String sstKey =
                    makeScopeTypeLangKey(scope, serviceType, lang);
                STLRecord regRecs = (STLRecord)scopeTypeLangTable.get(sstKey);

                // If no service type and scope, go to next scope.

                if (regRecs == null) {
                    continue;
                }

                // Get BtreeVector with all attributes for searching.

                BtreeVector bvec = regRecs.attrSort;

                // If there are no tags, then simply return everything in
                //  the BtreeVector.

                if (len <= 0) {
                    bvec.getAll(collector);

                } else {

                    // Use Btree vector to match the attribute tag patterns,
                    //  returning matching records.

                    int j;

                    for (j = 0; j < len; j++) {
                        AttributePattern pat =
                            (AttributePattern)attrTags.elementAt(j);

                        bvec.matchEqual(pat, collector);

                    }
                }
            }
        }

        return ret;
    }

    /**
     * Obtain the record matching the service URL and locale.
     *
     * @param URL The service record to match.
     * @param locale The locale of the record.
     * @return The ServiceRecord object, or null if none.
     */

    synchronized public ServiceStore.ServiceRecord
        getServiceRecord(ServiceURL URL, Locale locale) {

        if (URL == null || locale == null) {
            return null;

        }

        // Search in all scopes.

        return findExistingRecord(URL,
                                  null,
                                  SLPConfig.localeToLangTag(locale));

    }

    /**
     * Obtains service records with scopes matching from vector scopes.
     * If scopes is null, then returns all records.
     *
     * @param scopes Vector of scopes to match.
     * @return Enumeration   Of ServiceRecord Objects.
     */
    synchronized public Enumeration getServiceRecordsByScope(Vector scopes) {

        // Use a scope collector.

        Vector records = new Vector();
        BVCollector collector =
            new ScopeBVCollector(records, scopes);

        Enumeration keys = scopeTypeLangTable.keys();

        while (keys.hasMoreElements()) {
            String sstKey = (String)keys.nextElement();
            STLRecord regRecs = (STLRecord)scopeTypeLangTable.get(sstKey);

            // Get all records.

            BtreeVector bvec = regRecs.attrSort;
            bvec.getAll(collector);

        }

        return records.elements();
    }


    /**
     * Dump the service store to the log.
     *
     */

    synchronized public void dumpServiceStore() {

        SLPConfig conf = SLPConfig.getSLPConfig();

        conf.writeLogLine("ssim_dump_start",
                          new Object[] {this});

        Enumeration keys = scopeTypeLangTable.keys();

        while (keys.hasMoreElements()) {
            String sstKey = (String)keys.nextElement();
            STLRecord regRec = (STLRecord)scopeTypeLangTable.get(sstKey);

            // If the service type is abstract, then skip it. It will be
            //  displayed when the concrete type is.

            if (regRec.isAbstract) {
                continue;

            }

            // Get all records.

            BtreeVector bvec = regRec.attrSort;
            Vector vReturns = new Vector();
            BVCollector collector = new AllBVCollector(vReturns);

            bvec.getAll(collector);

            // Now write them out.

            int i, n = vReturns.size();

            for (i = 0; i < n; i++) {
                ServiceRecordInMemory rec =
                    (ServiceRecordInMemory)vReturns.elementAt(i);

                writeRecordToLog(conf, rec);
            }
        }

        conf.writeLog("ssim_dump_end",
                      new Object[] {this});
    }

    //
    // Protected/private methods.
    //

    // Register the record without any preliminaries. We assume that
    //  any old records have been removed and merged into this one,
    //  as necessary.

    private void registerInternal(ServiceRecordInMemory rec) {

        ServiceURL surl = rec.getServiceURL();
        ServiceType type = surl.getServiceType();
        String serviceType = type.toString();
        String abstractTypeName = type.getAbstractTypeName();
        Locale locale = rec.getLocale();
        String lang = locale.getLanguage();
        Vector scopes = rec.getScopes();

        // Make one age out queue entry. It will go into
        //  all scopes, but that's OK.

        List ageOutElem = addToAgeOutQueue(rec);

        // Go through all scopes.

        int i, n = scopes.size();

        for (i = 0; i < n; i++) {
            String scope = (String)scopes.elementAt(i);

            // Initialize the urltable list vector for this URL.

            Vector listVec =
                initializeURLScopeLangTableVector(surl, scope, lang);

            // Add to scope/type/lang table.

            addRecordToScopeTypeLangTable(scope,
                                          serviceType,
                                          lang,
                                          false,
                                          rec,
                                          listVec);

            // Add a new service type/scope record for this locale.

            addTypeLocale(serviceType, scope, lang);

            // Add ageOut record, so that it gets deleted when
            //  the record does.

            listVec.addElement(ageOutElem);

            // If the type is an abstract type, then add
            //  separate records.

            if (type.isAbstractType()) {
                addRecordToScopeTypeLangTable(scope,
                                              abstractTypeName,
                                              lang,
                                              true,
                                              rec,
                                              listVec);
                addTypeLocale(abstractTypeName, scope, lang);

            }
        }
    }

    // Create a urlScopeLangTable record for this URL.

    private Vector
        initializeURLScopeLangTableVector(ServiceURL url,
                                          String scope,
                                          String lang) {

        // Get scope level, creating if new.

        Hashtable scopeLevel =
            (Hashtable)urlScopeLangTable.get(url.toString());

        if (scopeLevel == null) {
            scopeLevel = new Hashtable();
            urlScopeLangTable.put(url.toString(), scopeLevel);

        }

        // Get lang level, creating if new.

        Hashtable langLevel =
            (Hashtable)scopeLevel.get(scope);

        if (langLevel == null) {
            langLevel = new Hashtable();
            scopeLevel.put(scope, langLevel);

        }

        // Check whether there's anything already there.
        //  Bug if so.

        Assert.slpassert(langLevel.get(lang) == null,
                      "ssim_url_lang_botch",
                      new Object[] {lang,
                                        url,
                                        scope});

        // Add a new list vector, and return it.

        Vector listVec = new Vector();

        langLevel.put(lang, listVec);

        return listVec;

    }

    // Add a record to the scope/type/language table.

    private void
        addRecordToScopeTypeLangTable(String scope,
                                      String serviceType,
                                      String lang,
                                      boolean isAbstract,
                                      ServiceRecordInMemory rec,
                                      Vector listVec) {

        // Make key for scope/type/language table.

        String stlKey = makeScopeTypeLangKey(scope, serviceType, lang);

        // Get record for scope/type/lang.

        STLRecord trec = (STLRecord)scopeTypeLangTable.get(stlKey);

        // If it's not there, make it.

        if (trec == null) {
            trec = new STLRecord(isAbstract);
            scopeTypeLangTable.put(stlKey, trec);

        }

        // Otherwise, add record to all.

        addRecordToAttrValueSort(trec.attrValueSort, rec, listVec);
        addRecordToAttrSort(trec.attrSort, rec, listVec);

    }

    // Add a new record into the attr value table.

    private void
        addRecordToAttrValueSort(Hashtable table,
                                 ServiceRecordInMemory rec,
                                 Vector listVec) {

        Vector attrList = rec.getAttrList();
        int i, n = attrList.size();

        // Go through the attribute list.

        for (i = 0; i < n; i++) {
            ServerAttribute attr =
                (ServerAttribute)attrList.elementAt(i);
            AttributeString tag = attr.idPattern;
            Vector values = attr.values;

            // If a type table record exists, use it. Otherwise,
            //  create a newly initialized one.

            Hashtable ttable = (Hashtable)table.get(tag);

            if (ttable == null) {
                ttable = makeAttrTypeTable();
                table.put(tag, ttable);

            }

            // Get the class of values.

            String typeKey = null;

            if (values == null) {

                // We're done, since there are no attributes to add.

                continue;

            } else {
                Object val = values.elementAt(0);

                typeKey = val.getClass().getName();
            }

            // Get the BtreeVector.

            BtreeVector bvec =
                (BtreeVector)ttable.get(typeKey);

            // Insert a record for each value.

            int j, m = values.size();

            for (j = 0; j < m; j++) {
                List elem = bvec.add(values.elementAt(j), rec);

                // Put the element into the deletion table.

                listVec.addElement(elem);
            }
        }
    }

    // Return a newly initialized attribute type table. It will
    //  have a hash for each allowed type, with a new BtreeVector
    //  attached.

    private Hashtable makeAttrTypeTable() {

        Hashtable ret = new Hashtable();

        ret.put(INTEGER_TYPE, new BtreeVector());
        ret.put(ATTRIBUTE_STRING_TYPE, new BtreeVector());
        ret.put(BOOLEAN_TYPE, new BtreeVector());
        ret.put(OPAQUE_TYPE, new BtreeVector());

        return ret;
    }

    // Add a new record into the attrs table.

    private void
        addRecordToAttrSort(BtreeVector table,
                            ServiceRecordInMemory rec,
                            Vector listVec) {

        Vector attrList = rec.getAttrList();
        int i, n = attrList.size();

        // If no attributes, then add with empty string as
        //  the attribute tag.

        if (n <= 0) {
            List elem =
                table.add(new AttributeString("", rec.getLocale()), rec);

            listVec.addElement(elem);

            return;
        }

        // Iterate through the attribute list, adding to the
        //  BtreeVector with attribute as the sort key.

        for (i = 0; i < n; i++) {
            ServerAttribute attr =
                (ServerAttribute)attrList.elementAt(i);

            List elem = table.add(attr.idPattern, rec);

            // Save for deletion.

            listVec.addElement(elem);

        }
    }

    // Add a record to the ageOut queue.

    private List addToAgeOutQueue(ServiceRecordInMemory rec) {

        Long exTime = Long.valueOf(rec.getExpirationTime());
        return ageOutQueue.add(exTime, rec);

    }

    // Remove the URL record from the database.

    private void
        deregisterInternal(ServiceURL url, Vector scopes, String lang) {

        ServiceType type = url.getServiceType();

        // To deregister, we only need to find the Vector of List objects
        //  containing the places where this registration is hooked into
        //  lists and unhook them. Garbage collection of other structures
        //  is handled during insertion or in deregisterTypeLocale(),
        //  if there are no more registrations at all.

        // Find the scope table..

        Hashtable scopeLangTable =
            (Hashtable)urlScopeLangTable.get(url.toString());

        // If it's not there, then maybe not registered.

        if (scopeLangTable == null) {
            return;

        }

        // For each scope, find the lang table.

        int i, n = scopes.size();

        for (i = 0; i < n; i++) {
            String scope = (String)scopes.elementAt(i);

            Hashtable langTable = (Hashtable)scopeLangTable.get(scope);

            if (langTable == null) {
                continue;
            }

            // If the locale is non-null, then just deregister from this
            //  locale.

            if (lang != null) {
                deregisterFromLocale(langTable, lang);

                // Record the deletion in the scope/type table, and
                //  also the number of regs table.

                deleteTypeLocale(type.toString(), scope, lang);

                // Check for abstract type as well.

                if (type.isAbstractType()) {
                    deleteTypeLocale(type.getAbstractTypeName(), scope, lang);

                }

            } else {

                // Otherwise, deregister all languages.

                Enumeration en = langTable.keys();

                while (en.hasMoreElements()) {
                    lang = (String)en.nextElement();

                    deregisterFromLocale(langTable, lang);

                    // Record the deletion in the scope/type table, and
                    //  also the number of regs table.

                    deleteTypeLocale(type.toString(), scope, lang);

                    // Check for abstract type as well.

                    if (type.isAbstractType()) {
                        deleteTypeLocale(type.getAbstractTypeName(),
                                         scope,
                                         lang);

                    }
                }
            }

            // If the table is empty, then remove the lang table.

            if (langTable.size() <= 0) {
                scopeLangTable.remove(scope);

            }
        }

        // If all languages were deleted, delete the
        //  urlScopeLangTable record. Other GC handled in
        //  deleteTypeLocale().

        if (scopeLangTable.size() <= 0) {
            urlScopeLangTable.remove(url.toString());

        }

    }

    // Deregister a single locale from the language table.

    private void deregisterFromLocale(Hashtable langTable, String lang) {

        // Get the Vector containing the list of registrations.

        Vector regList = (Vector)langTable.get(lang);

        Assert.slpassert(regList != null,
                      "ssim_null_reg_vector",
                      new Object[] {lang});

        // Walk down the list of registrations and unhook them from
        //  their respective lists.

        int i, n = regList.size();

        for (i = 0; i < n; i++) {
            List elem = (List)regList.elementAt(i);

            elem.delete();

        }

        // Remove the locale record.

        langTable.remove(lang);
    }

    // Find an existing record matching the URL by searching in all scopes.
    //  The record will be the same for all scopes in the same language.
    //  If locale is null, return any. If there are none, return null.

    private ServiceRecordInMemory
        findExistingRecord(ServiceURL surl, Vector scopes, String lang) {

        ServiceRecordInMemory rec = null;

        // Look in urlScopeLangTable.

        Hashtable scopeLevel =
            (Hashtable)urlScopeLangTable.get(surl.toString());

        if (scopeLevel != null) {

            // If scopes is null, then perform the search for all
            //  scopes in the table. Otherwise perform it for
            //  all scopes incoming.

            Enumeration en = null;

            if (scopes == null) {
                en = scopeLevel.keys();

            } else {
                en = scopes.elements();

            }

            while (en.hasMoreElements()) {
                String scope = (String)en.nextElement();
                Hashtable langLevel = (Hashtable)scopeLevel.get(scope);

                // If no langLevel table, continue searching.

                if (langLevel == null) {
                    continue;

                }

                Vector listVec = null;

                // Use lang tag if we have it, otherwise, pick arbitrary.

                if (lang != null) {
                    listVec = (Vector)langLevel.get(lang);

                } else {
                    Enumeration llen = langLevel.elements();

                    listVec = (Vector)llen.nextElement();

                }

                // If none for this locale, try the next scope.

                if (listVec == null) {
                    continue;

                }

                // Select out the record.

                List elem = (List)listVec.elementAt(0);

                rec = elem.record;
                break;

            }
        }

        return rec;
    }

    // Find attributes matching the record and place the matching attributes
    //  into the vector. Use the hashtable for collation.

    static void
        findMatchingAttributes(ServiceRecordInMemory rec,
                               Vector attrTags,
                               Hashtable ht,
                               Vector ret)
        throws ServiceLocationException {

        int len = attrTags.size();
        Vector attrList = rec.getAttrList();

        // For each attribute, go through the tag vector If an attribute
        //  matches, merge it into the return vector.

        int i, n = attrList.size();

        for (i = 0; i < n; i++) {
            ServerAttribute attr =
                (ServerAttribute)attrList.elementAt(i);

            // All attributes match if the pattern vector is
            //  empty.

            if (len <= 0) {
                saveValueIfMatch(attr, null, ht, ret);

            } else {

                // Check each pattern against the attribute id.

                int j;

                for (j = 0; j < len; j++) {
                    AttributePattern attrTag =
                        (AttributePattern)attrTags.elementAt(j);

                    saveValueIfMatch(attr, attrTag, ht, ret);

                }
            }
        }
    }

    // Check the attribute against the pattern. If the pattern is null,
    //  then match occurs. Merge the attribute into the vector
    //  if match.

    static private void saveValueIfMatch(ServerAttribute attr,
                                         AttributePattern attrTag,
                                         Hashtable ht,
                                         Vector ret)
        throws ServiceLocationException {

        AttributeString id = attr.idPattern;

        // We save the attribute value if either
        //  the pattern is null or it matches the attribute id.

        if (attrTag == null || attrTag.match(id)) {

            Vector values = attr.getValues();

            // Create new values vector so record copy isn't
            //  modified.

            if (values != null) {
                values = (Vector)values.clone();

            }

            // Create new attribute so record copy isn't
            //  modified.

            ServiceLocationAttribute nattr =
                new ServiceLocationAttribute(attr.getId(), values);

            // Merge duplicate attributes into vector.

            ServiceLocationAttribute.mergeDuplicateAttributes(nattr,
                                                              ht,
                                                              ret,
                                                              true);
        }
    }

    // Check whether the incoming scopes are the same as existing
    //  scopes.

    private void
        checkScopeStatus(ServiceURL surl,
                         Vector scopes,
                         short errCode)
        throws ServiceLocationException {

        // Drill down in the urlScopeLangTable table.

        Hashtable scopeLevel =
            (Hashtable)urlScopeLangTable.get(surl.toString());

        if (scopeLevel == null) {
            return;  // not yet registered...

        }

        // We need to have exactly the same scopes as in
        //  the registration.

        int i, n = scopes.size();
        boolean ok = true;

        if (n != scopeLevel.size()) {
            ok = false;

        } else {

            for (i = 0; i < n; i++) {
                if (scopeLevel.get(scopes.elementAt(i)) == null) {
                    ok = false;
                    break;

                }
            }
        }

        if (!ok) {
            throw
                new ServiceLocationException(errCode,
                                             "ssim_scope_mis",
                                             new Object[0]);

        }
    }

    // Check whether an existing nonservice URL is registered under
    //  a different service type.

    private void checkForExistingUnderOtherServiceType(ServiceURL url,
                                                       Vector scopes)
        throws ServiceLocationException {

        // Drill down in the urlScopeLangTable table.

        Hashtable scopeLevel =
            (Hashtable)urlScopeLangTable.get(url.toString());

        if (scopeLevel == null) {
            return; // not yet registered.

        }

        // Get hashtable of locale records under scopes. Any scope
        //  will do.

        Object scope = scopes.elementAt(0);

        Hashtable localeLevel = (Hashtable)scopeLevel.get(scope);

        Assert.slpassert(localeLevel != null,
                      "ssim_null_lang_table",
                      new Object[] {scope});

        // Get a record from any locale.

        Enumeration en = localeLevel.elements();

        Assert.slpassert(en.hasMoreElements(),
                      "ssim_empty_lang_table",
                      new Object[] {scope});

        // Get vector of registrations.

        Vector vec = (Vector)en.nextElement();

        Assert.slpassert(vec.size() > 0,
                      "ssim_empty_reg_vector",
                      new Object[] {scope});

        List elem = (List)vec.elementAt(0);

        // OK, now check the registration.

        ServiceURL recURL = elem.record.getServiceURL();
        ServiceType recType = recURL.getServiceType();

        if (!recType.equals(url.getServiceType())) {
            throw
                new ServiceLocationException(
                                ServiceLocationException.INVALID_UPDATE,
                                "ssim_st_already",
                                new Object[0]);
        }
    }

    // Merge old record into new record.

    final private void mergeOldRecordIntoNew(ServiceRecordInMemory oldRec,
                                             ServiceRecordInMemory newRec)
        throws ServiceLocationException {

        Vector newAttrs = newRec.getAttrList();
        Vector oldAttrs = oldRec.getAttrList();
        Hashtable ht = new Hashtable();

        // Charge up the hashtable with the new attributes.

        int i, n = newAttrs.size();

        for (i = 0; i < n; i++) {
            ServerAttribute attr =
                (ServerAttribute)newAttrs.elementAt(i);

            ht.put(attr.getId().toLowerCase(), attr);

        }

        // Merge in the old attributes.

        n = oldAttrs.size();

        for (i = 0; i < n; i++) {
            ServerAttribute attr =
                (ServerAttribute)oldAttrs.elementAt(i);

            if (ht.get(attr.getId().toLowerCase()) == null) {
                newAttrs.addElement(attr);

            }
        }

        // Change the attribute vector on the rec.

        newRec.setAttrList(newAttrs);

        // Merge old scopes into new.

        Vector oldScopes = oldRec.getScopes();
        Vector newScopes = newRec.getScopes();
        int j, m = oldScopes.size();

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

            if (!newScopes.contains(scope)) {
                newScopes.addElement(scope);

            }
        }

        // Note that we don't have to merge security because there
        //  will never be an incremental update to a record
        //  in a protected scope.

        // Change the scope vector on the rec.

        newRec.setScopes(newScopes);

    }

    // Delete attributes matching attrTags.

    private void deleteAttributes(ServiceRecordInMemory rec,
                                  Vector attrTags)
        throws ServiceLocationException {

        // For each attribute, go through the tag vector and put attributes
        // that do not match the tags into the new attribute vector.

        Vector attrList = rec.getAttrList();

        // If there are no attributes for this one, then simply return.

        if (attrList.size() <= 0) {
            return;

        }

        int i, n = attrList.size();
        Vector newAttrList = new Vector();
        int len = attrTags.size();

        for (i = 0; i < n; i++) {
            ServerAttribute attr =
                (ServerAttribute)attrList.elementAt(i);
            AttributeString id = attr.idPattern;
            boolean deleteIt = false;

            int j;

            // Now check the tags.

            for (j = 0; j < len; j++) {
                AttributePattern attrTag =
                    (AttributePattern)attrTags.elementAt(j);

                // If there's a match, mark for deletion.

                if (attrTag.match(id)) {
                    deleteIt = true;
                    break;

                }
            }

            if (!deleteIt) {
                newAttrList.addElement(attr);
            }
        }

        // Replace the attribute vector in the record.

        rec.setAttrList(newAttrList);
    }

    // Convert a vector of attribute tag strings to attribute pattern objects.

    private Vector stringVectorToAttributePattern(Vector tags, Locale locale)
        throws ServiceLocationException {

        // Takes care of findAttributes() case where no vector.

        if (tags == null) {
            return null;

        }

        Vector v = new Vector();
        int i, n = tags.size();

        for (i = 0; i < n; i++) {
            String value = (String)tags.elementAt(i);

            AttributePattern tag =
                new AttributePattern(value, locale);

            if (!v.contains(tag)) {
                v.addElement(tag);

            }
        }

        return v;
    }

    //
    // Output of service store to log.
    //

    // Write record to config log file.

    private void
        writeRecordToLog(SLPConfig conf, ServiceStore.ServiceRecord rec) {

        Locale locale = rec.getLocale();
        ServiceURL surl = rec.getServiceURL();
        Vector scopes = rec.getScopes();
        Vector attributes = rec.getAttrList();
        long exTime = rec.getExpirationTime();
        Hashtable urlSig = rec.getURLSignature();
        Hashtable attrSig = rec.getAttrSignature();

        conf.writeLogLine("ssim_dump_entry_start", new Object[0]);
        conf.writeLogLine("ssim_dump_entry",
                          new Object[] {
            locale,
                surl.toString(),
                Integer.toString(surl.getLifetime()),
                Long.toString(((exTime - System.currentTimeMillis())/1000)),
                surl.getServiceType(),
                scopes,
                attributes});

        if (urlSig != null) {
            conf.writeLogLine("ssim_dump_urlsig",
                              new Object[] {urlSig});

        }

        if (attrSig != null) {
            conf.writeLogLine("ssim_dump_attrsig",
                              new Object[] {
                attrSig});

        }
        conf.writeLogLine("ssim_entry_end", new Object[0]);
    }

    //
    // Utilities for dealing with service type/scope locale table.
    //

    // Bump up the number of registrations for this service type, scope and
    //  locale.

    private void
        addTypeLocale(String type, String scope, String lang) {

        String sstKey = makeScopeTypeKey(scope, type);

        // Get any existing record.

        Hashtable langTable = (Hashtable)sstLocales.get(sstKey);

        // Insert a new one if none there.

        if (langTable == null) {
            langTable = new Hashtable();

            sstLocales.put(sstKey, langTable);

        }

        // Look up locale.

        Integer numRegs = (Integer)langTable.get(lang);

        // Add a new one if none there, otherwise, bump up old.

        if (numRegs == null) {
            numRegs = Integer.valueOf(1);

        } else {
            numRegs = Integer.valueOf(numRegs.intValue() + 1);

        }

        // Put it back.

        langTable.put(lang, numRegs);

    }

    // Bump down the number of registrations for this service type, scope,
    //  in all locales.

    private void deleteTypeLocale(String type, String scope, String lang) {

        String sstKey = makeScopeTypeKey(scope, type);

        // Get any existing record.

        Hashtable langTable = (Hashtable)sstLocales.get(sstKey);

        // If none there, then error. But this should have been caught
        //  during deletion, so it's fatal.

        Assert.slpassert(langTable != null,
                      "ssim_ssttable_botch",
                      new Object[] {
            type,
                scope});

        // Get the Integer object recording the number of registrations.

        Integer numRegs = (Integer)langTable.get(lang);

        Assert.slpassert(numRegs != null,
                      "ssim_ssttable_lang_botch",
                      new Object[] {
            lang,
                type,
                scope});

        // Bump down by one, remove if zero.

        numRegs = Integer.valueOf(numRegs.intValue() - 1);

        if (numRegs.intValue() <= 0) {
            langTable.remove(lang);

            if (langTable.size() <= 0) {
                sstLocales.remove(sstKey);

            }

            // Garbage collection.

            // Remove records from the scopeTypeLangTable,
            //  since there are no registrations left for this
            //  type/scope/locale.

            String stlKey =
                makeScopeTypeLangKey(scope, type, lang);
            scopeTypeLangTable.remove(stlKey);

        } else {

            // Put it back.

            langTable.put(lang, numRegs);

        }
    }

    // Return REGS if the language is supported. Supported means that the
    //  there are some registrations of this service type in it or that
    //  there are none in any locale. Return NO_REGS if there are absolutely
    //  no registrations whatsoever, in any language. Return NO_REGS_IN_LOCALE
    //  if there are no registrations in that language but there are in
    //  others.

    private int
        languageSupported(String type, Vector scopes, String lang) {

        // Look through scope vector.

        boolean otherLangRegs = false;
        boolean sameLangRegs = false;
        int i, n = scopes.size();

        for (i = 0; i < n; i++) {
            String scope = (String)scopes.elementAt(i);
            String sstKey = makeScopeTypeKey(scope, type);

            // Get any existing record.

            Hashtable langTable = (Hashtable)sstLocales.get(sstKey);

            // If there are no regs, then check next scope.

            if (langTable == null) {
                continue;

            }

            Object numRegs = langTable.get(lang);

            // Check whether there are other language regs
            //  or same language regs.

            if (numRegs == null) {
                otherLangRegs = true;

            } else {
                sameLangRegs = true;

            }
        }

        // Return appropriate code.

        if (otherLangRegs == false &&
            sameLangRegs == false) {
            return NO_REGS;

        } else if (otherLangRegs == true &&
                   sameLangRegs == false) {
            return NO_REGS_IN_LOCALE;

        } else {
            return REGS_IN_LOCALE;

        }
    }

    //
    // Hash key calculations and hash table structuring.
    //

    // Return a key for type and scope.

    private String makeScopeTypeKey(String scope, String type) {
        return scope + "/" + type;

    }

    // Make a hash key consisting of the scope and service type.

    final private String
        makeScopeTypeLangKey(String scope,
                             String serviceType,
                             String lang) {

        return scope + "/" + serviceType + "/" + lang;
    }

    // Return the key's scope.

    final private String keyScope(String key) {
        int idx = key.indexOf('/');
        String ret = "";

        if (idx > 0) {
            ret = key.substring(0, idx);
        }

        return ret;
    }


    // Return the key's service type/NA.

    final private String keyServiceType(String key) {
        int idx = key.indexOf('/');
        String ret = "";
        int len = key.length();

        if (idx >= 0 && idx < len - 1) {
            ret = key.substring(idx+1, len);
        }

        // Parse off the final lang.

        idx = ret.indexOf('/');

        ret = ret.substring(0, idx);

        return ret;
    }

    // Return true if the record is for an abstract type.

    final private boolean isAbstractTypeRecord(String sstKey) {
        STLRecord rec = (STLRecord)scopeTypeLangTable.get(sstKey);

        return rec.isAbstract;

    }

}