xref: /onnv-gate/usr/src/lib/libslp/javalib/com/sun/slp/DATable.java (revision 7298:b69e27387f74)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*7298SMark.J.Nelson@Sun.COM  * Common Development and Distribution License (the "License").
6*7298SMark.J.Nelson@Sun.COM  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
220Sstevel@tonic-gate  * Copyright 2001,2003 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  *
250Sstevel@tonic-gate  */
260Sstevel@tonic-gate 
270Sstevel@tonic-gate //  DATable.java:     Interface for DATables.
280Sstevel@tonic-gate //  Author:           James Kempf
290Sstevel@tonic-gate //  Created On:       Mon May 11 13:46:02 1998
300Sstevel@tonic-gate //  Last Modified By: James Kempf
310Sstevel@tonic-gate //  Last Modified On: Mon Feb 22 15:47:37 1999
320Sstevel@tonic-gate //  Update Count:     53
330Sstevel@tonic-gate //
340Sstevel@tonic-gate 
350Sstevel@tonic-gate 
360Sstevel@tonic-gate package com.sun.slp;
370Sstevel@tonic-gate 
380Sstevel@tonic-gate /**
390Sstevel@tonic-gate  * DATable is an abstract class that provides the interface for DA
400Sstevel@tonic-gate  * and scope discovery. A variety of implementations are possible.
410Sstevel@tonic-gate  * The getDATable() method creates the right one from a subclass.
420Sstevel@tonic-gate  *
430Sstevel@tonic-gate  * @author James Kempf
440Sstevel@tonic-gate  */
450Sstevel@tonic-gate 
460Sstevel@tonic-gate import java.util.*;
470Sstevel@tonic-gate import java.net.*;
480Sstevel@tonic-gate 
490Sstevel@tonic-gate abstract class DATable extends Object {
500Sstevel@tonic-gate 
510Sstevel@tonic-gate     protected static DATable daTable;
520Sstevel@tonic-gate     protected static SLPConfig conf;
530Sstevel@tonic-gate 
540Sstevel@tonic-gate     // System property naming the DATable implementation class to use.
550Sstevel@tonic-gate 
560Sstevel@tonic-gate     final static String DA_TABLE_CLASS_PROP = "sun.net.slp.DATableClass";
570Sstevel@tonic-gate 
580Sstevel@tonic-gate     // SA only scopes property.
590Sstevel@tonic-gate 
600Sstevel@tonic-gate     final static String SA_ONLY_SCOPES_PROP = "sun.net.slp.SAOnlyScopes";
610Sstevel@tonic-gate 
620Sstevel@tonic-gate     // Hashtable key for multicast scopes.
630Sstevel@tonic-gate 
640Sstevel@tonic-gate     final static String MULTICAST_KEY = "&&**^^MULTICASTxxxKEY^^**&&";
650Sstevel@tonic-gate 
660Sstevel@tonic-gate     // Hashtable key for DA equivalence classes.
670Sstevel@tonic-gate 
680Sstevel@tonic-gate     final static String UNICAST_KEY = "&&**^^UNICASTxxxKEY^^**&&";
690Sstevel@tonic-gate 
700Sstevel@tonic-gate     /**
710Sstevel@tonic-gate      * A record for all DAs supporting exactly the same set of scopes.
720Sstevel@tonic-gate      *
730Sstevel@tonic-gate      * @author James Kempf
740Sstevel@tonic-gate      */
750Sstevel@tonic-gate 
760Sstevel@tonic-gate 
770Sstevel@tonic-gate     public static class DARecord extends Object {
780Sstevel@tonic-gate 
790Sstevel@tonic-gate 	// The scopes supported.
800Sstevel@tonic-gate 
810Sstevel@tonic-gate 	Vector scopes = null;		// String scope names
820Sstevel@tonic-gate 
830Sstevel@tonic-gate 	Vector daAddresses = new Vector();  // InetAddress DA addresses
840Sstevel@tonic-gate 
850Sstevel@tonic-gate     }
860Sstevel@tonic-gate 
870Sstevel@tonic-gate     /**
880Sstevel@tonic-gate      * Return a hashtable containing two entries:
890Sstevel@tonic-gate      *
900Sstevel@tonic-gate      * MULTICAST_KEY - Vector of scopes from the incoming vector that are not
910Sstevel@tonic-gate      * supported by any known DA.
920Sstevel@tonic-gate      *
930Sstevel@tonic-gate      * UNICAST_KEY - Vector of DATable.DARecord objects containing
940Sstevel@tonic-gate      * equivalence classes of DAs that all support the same set of scopes.
950Sstevel@tonic-gate      * Only DAs supporting one or more scopes in the incoming vector
960Sstevel@tonic-gate      * are returned.
970Sstevel@tonic-gate      *
980Sstevel@tonic-gate      * Note that the equivalence classes don't necessarily mean that the
990Sstevel@tonic-gate      * set of scopes are mutually exclusive. For example, if DA1 supports
1000Sstevel@tonic-gate      * scopes A, B, and C; and DA2 supports scopes C and D, then they
1010Sstevel@tonic-gate      * are in separate equivalence classes even though they both support
1020Sstevel@tonic-gate      * C. But if DA2 supports A, B, and C; then it is in the same equivalence
1030Sstevel@tonic-gate      * class.
1040Sstevel@tonic-gate      *
1050Sstevel@tonic-gate      * @param scopes The scopes for which DAs are required.
1060Sstevel@tonic-gate      * @return A Hashtable with the multicast scopes and DAAddresses.
1070Sstevel@tonic-gate      */
1080Sstevel@tonic-gate 
findDAScopes(Vector scopes)1090Sstevel@tonic-gate     abstract Hashtable findDAScopes(Vector scopes)
1100Sstevel@tonic-gate 	throws ServiceLocationException;
1110Sstevel@tonic-gate 
1120Sstevel@tonic-gate     /**
1130Sstevel@tonic-gate      * Remove a DA by address.
1140Sstevel@tonic-gate      *
1150Sstevel@tonic-gate      * @param address The host address of the DA.
1160Sstevel@tonic-gate      * @param scopes The scopes.
1170Sstevel@tonic-gate      * @return True if removed, false if not.
1180Sstevel@tonic-gate      */
1190Sstevel@tonic-gate 
removeDA(InetAddress address, Vector scopes)1200Sstevel@tonic-gate     abstract boolean removeDA(InetAddress address, Vector scopes);
1210Sstevel@tonic-gate 
1220Sstevel@tonic-gate     /**
1230Sstevel@tonic-gate      * Return a vector of scopes that the SA or UA client should use.
1240Sstevel@tonic-gate      * Note that if no DAs are around, SA adverts must be used to
1250Sstevel@tonic-gate      * find SAs. We must sort through the returned DAs and apply
1260Sstevel@tonic-gate      * the scope prioritization algorithm to them.
1270Sstevel@tonic-gate      *
1280Sstevel@tonic-gate      * @return Vector of scopes for the SA or UA client to use.
1290Sstevel@tonic-gate      */
1300Sstevel@tonic-gate 
findScopes()1310Sstevel@tonic-gate     synchronized Vector findScopes() throws ServiceLocationException {
1320Sstevel@tonic-gate 
1330Sstevel@tonic-gate 	// First, get the DA addresses v.s. scopes table from the DAtable.
1340Sstevel@tonic-gate 	//  This will also include DA addresses from the configuration file,
1350Sstevel@tonic-gate 	//  if any. We don't filter on any scopes, since we want all of
1360Sstevel@tonic-gate 	//  them. We are only interested in v2 scopes here.
1370Sstevel@tonic-gate 
1380Sstevel@tonic-gate 	Vector scopes = new Vector();
1390Sstevel@tonic-gate 	Hashtable daRec = daTable.findDAScopes(scopes);
1400Sstevel@tonic-gate 	Vector daEquivClasses = (Vector)daRec.get(UNICAST_KEY);
1410Sstevel@tonic-gate 
1420Sstevel@tonic-gate 	if (daEquivClasses != null) {
1430Sstevel@tonic-gate 
1440Sstevel@tonic-gate 	    // Go through the equivalence classes and pull out scopes.
1450Sstevel@tonic-gate 
1460Sstevel@tonic-gate 	    int i, n = daEquivClasses.size();
1470Sstevel@tonic-gate 
1480Sstevel@tonic-gate 	    for (i = 0; i < n; i++) {
1490Sstevel@tonic-gate 		DARecord rec = (DARecord)daEquivClasses.elementAt(i);
1500Sstevel@tonic-gate 		Vector v = rec.scopes;
1510Sstevel@tonic-gate 
1520Sstevel@tonic-gate 		int j, m = v.size();
1530Sstevel@tonic-gate 
1540Sstevel@tonic-gate 		for (j = 0; j < m; j++) {
1550Sstevel@tonic-gate 		    Object s = v.elementAt(j);
1560Sstevel@tonic-gate 
1570Sstevel@tonic-gate 		    // Unicast scopes take precedence over multicast scopes,
1580Sstevel@tonic-gate 		    //  so insert them at the beginning of the vector.
1590Sstevel@tonic-gate 
1600Sstevel@tonic-gate 		    if (!scopes.contains(s)) {
1610Sstevel@tonic-gate 			scopes.addElement(s);
1620Sstevel@tonic-gate 
1630Sstevel@tonic-gate 		    }
1640Sstevel@tonic-gate 		}
1650Sstevel@tonic-gate 	    }
1660Sstevel@tonic-gate 	}
1670Sstevel@tonic-gate 
1680Sstevel@tonic-gate 	return scopes;
1690Sstevel@tonic-gate     }
1700Sstevel@tonic-gate 
1710Sstevel@tonic-gate     /**
1720Sstevel@tonic-gate      * Get the right DA table implementation. The property
1730Sstevel@tonic-gate      * sun.net.slp.DATableClass determines the class.
1740Sstevel@tonic-gate      *
1750Sstevel@tonic-gate      * @return The DATable object for this process' SLP requests.
1760Sstevel@tonic-gate      */
1770Sstevel@tonic-gate 
1780Sstevel@tonic-gate 
getDATable()1790Sstevel@tonic-gate     static DATable getDATable() {
1800Sstevel@tonic-gate 
1810Sstevel@tonic-gate 	// Return it right up front if we have it.
1820Sstevel@tonic-gate 
1830Sstevel@tonic-gate 	if (daTable != null) {
1840Sstevel@tonic-gate 	    return daTable;
1850Sstevel@tonic-gate 
1860Sstevel@tonic-gate 	}
1870Sstevel@tonic-gate 
1880Sstevel@tonic-gate 	conf = SLPConfig.getSLPConfig();
1890Sstevel@tonic-gate 
1900Sstevel@tonic-gate 	// Link and instantiate it.
1910Sstevel@tonic-gate 
1920Sstevel@tonic-gate 	daTable = linkAndInstantiateFromProp();
1930Sstevel@tonic-gate 
1940Sstevel@tonic-gate 	return daTable;
1950Sstevel@tonic-gate 
1960Sstevel@tonic-gate     }
1970Sstevel@tonic-gate 
1980Sstevel@tonic-gate     // Link and instantiate the class in the property.
1990Sstevel@tonic-gate 
linkAndInstantiateFromProp()2000Sstevel@tonic-gate     static protected DATable linkAndInstantiateFromProp() {
2010Sstevel@tonic-gate 
2020Sstevel@tonic-gate 	// Get the property.
2030Sstevel@tonic-gate 
2040Sstevel@tonic-gate 	String className = System.getProperty(DA_TABLE_CLASS_PROP);
2050Sstevel@tonic-gate 
2060Sstevel@tonic-gate 	if (className == null) {
2070Sstevel@tonic-gate 	    Assert.slpassert(false,
2080Sstevel@tonic-gate 			  "no_da_table",
2090Sstevel@tonic-gate 			  new Object[] {DA_TABLE_CLASS_PROP});
2100Sstevel@tonic-gate 	}
2110Sstevel@tonic-gate 
2120Sstevel@tonic-gate 	Class tclass = null;
2130Sstevel@tonic-gate 
2140Sstevel@tonic-gate 	// Link the class and instantiate the object.
2150Sstevel@tonic-gate 
2160Sstevel@tonic-gate 	try {
2170Sstevel@tonic-gate 
2180Sstevel@tonic-gate 	    tclass = Class.forName(className);
2190Sstevel@tonic-gate 	    daTable = (DATable)tclass.newInstance();
2200Sstevel@tonic-gate 	    return daTable;
2210Sstevel@tonic-gate 
2220Sstevel@tonic-gate 	} catch (ClassNotFoundException ex) {
2230Sstevel@tonic-gate 
2240Sstevel@tonic-gate 	    Assert.slpassert(false,
2250Sstevel@tonic-gate 			  "no_da_table_class",
2260Sstevel@tonic-gate 			  new Object[] {className});
2270Sstevel@tonic-gate 
2280Sstevel@tonic-gate 	} catch (InstantiationException ex) {
2290Sstevel@tonic-gate 
2300Sstevel@tonic-gate 	    Assert.slpassert(false,
2310Sstevel@tonic-gate 			  "instantiation_exception",
2320Sstevel@tonic-gate 			  new Object[] {className});
2330Sstevel@tonic-gate 
2340Sstevel@tonic-gate 	} catch (IllegalAccessException ex) {
2350Sstevel@tonic-gate 
2360Sstevel@tonic-gate 	    Assert.slpassert(false,
2370Sstevel@tonic-gate 			  "access_exception",
2380Sstevel@tonic-gate 			  new Object[] {className});
2390Sstevel@tonic-gate 
2400Sstevel@tonic-gate 	}
2410Sstevel@tonic-gate 
2420Sstevel@tonic-gate 	// We won't reach this point, since the assertions will capture
2430Sstevel@tonic-gate 	//  any errors and kill the program.
2440Sstevel@tonic-gate 
2450Sstevel@tonic-gate 	return null;
2460Sstevel@tonic-gate     }
2470Sstevel@tonic-gate 
2480Sstevel@tonic-gate     //
2490Sstevel@tonic-gate     // Utility functions for DA filtering and handling scopes.
2500Sstevel@tonic-gate     //
2510Sstevel@tonic-gate 
2520Sstevel@tonic-gate     // Filter scopes, removing any not on the filter list if inVector is
2530Sstevel@tonic-gate     //  false and removing any in the filter list if inVector is true.
2540Sstevel@tonic-gate 
2550Sstevel@tonic-gate     public static void
filterScopes(Vector scopes, Vector filter, boolean inVector)2560Sstevel@tonic-gate 	filterScopes(Vector scopes, Vector filter, boolean inVector) {
2570Sstevel@tonic-gate 
2580Sstevel@tonic-gate 	int i = 0;
2590Sstevel@tonic-gate 
2600Sstevel@tonic-gate 	// Null or empty filter vector means that all should be accepted.
2610Sstevel@tonic-gate 
2620Sstevel@tonic-gate 	if (filter != null && !(filter.size() <= 0)) {
2630Sstevel@tonic-gate 
2640Sstevel@tonic-gate 	    while (i < scopes.size()) {
2650Sstevel@tonic-gate 		String scope = (String)scopes.elementAt(i);
2660Sstevel@tonic-gate 
2670Sstevel@tonic-gate 		if ((!inVector && !filter.contains(scope)) ||
2680Sstevel@tonic-gate 		    (inVector && filter.contains(scope))) {
2690Sstevel@tonic-gate 		    scopes.removeElementAt(i);
2700Sstevel@tonic-gate 
2710Sstevel@tonic-gate 		} else {
2720Sstevel@tonic-gate 		    i++;
2730Sstevel@tonic-gate 
2740Sstevel@tonic-gate 		}
2750Sstevel@tonic-gate 	    }
2760Sstevel@tonic-gate 	}
2770Sstevel@tonic-gate     }
2780Sstevel@tonic-gate 
2790Sstevel@tonic-gate     // Add a new address to the equivalence class.
2800Sstevel@tonic-gate 
addToEquivClass(String daaddr, Vector scopes, Vector ret)2810Sstevel@tonic-gate     static boolean addToEquivClass(String daaddr, Vector scopes, Vector ret) {
2820Sstevel@tonic-gate 
2830Sstevel@tonic-gate 	// Create the InetAddress object.
2840Sstevel@tonic-gate 
2850Sstevel@tonic-gate 	InetAddress addr = null;
2860Sstevel@tonic-gate 
2870Sstevel@tonic-gate 	try {
2880Sstevel@tonic-gate 
2890Sstevel@tonic-gate 	    addr = InetAddress.getByName(daaddr);
2900Sstevel@tonic-gate 
2910Sstevel@tonic-gate 	} catch (UnknownHostException ex) {
2920Sstevel@tonic-gate 
2930Sstevel@tonic-gate 	    if (conf.traceAll()) {
2940Sstevel@tonic-gate 		conf.writeLog("unknown_da_address",
2950Sstevel@tonic-gate 			      new Object[] {daaddr});
2960Sstevel@tonic-gate 
2970Sstevel@tonic-gate 	    }
2980Sstevel@tonic-gate 
2990Sstevel@tonic-gate 	    return false;
3000Sstevel@tonic-gate 	}
3010Sstevel@tonic-gate 
3020Sstevel@tonic-gate 	// Go through the existing vector.
3030Sstevel@tonic-gate 
3040Sstevel@tonic-gate 	int i, n = ret.size();
3050Sstevel@tonic-gate 	boolean equivalent = false;
3060Sstevel@tonic-gate 	DARecord rec = null;
3070Sstevel@tonic-gate 
3080Sstevel@tonic-gate     outer: for (i = 0; i < n && equivalent == false; i++) {
3090Sstevel@tonic-gate 	rec = (DARecord)ret.elementAt(i);
3100Sstevel@tonic-gate 	Vector dascopes = rec.scopes;
3110Sstevel@tonic-gate 
3120Sstevel@tonic-gate 	int j, m = dascopes.size();
3130Sstevel@tonic-gate 
3140Sstevel@tonic-gate 	for (j = 0; j < m; j++) {
3150Sstevel@tonic-gate 	    String scope = (String)dascopes.elementAt(j);
3160Sstevel@tonic-gate 
3170Sstevel@tonic-gate 	    if (!scopes.contains(scope)) {
3180Sstevel@tonic-gate 		continue outer;
3190Sstevel@tonic-gate 
3200Sstevel@tonic-gate 	    }
3210Sstevel@tonic-gate 	}
3220Sstevel@tonic-gate 
3230Sstevel@tonic-gate 	equivalent = true;
3240Sstevel@tonic-gate     }
3250Sstevel@tonic-gate 
3260Sstevel@tonic-gate 	// Make a new record if not equivalent.
3270Sstevel@tonic-gate 
3280Sstevel@tonic-gate 	if (!equivalent) {
3290Sstevel@tonic-gate 	    rec = new DATable.DARecord();
3300Sstevel@tonic-gate 	    rec.scopes = (Vector)scopes.clone();
3310Sstevel@tonic-gate 
3320Sstevel@tonic-gate 	    ret.addElement(rec);
3330Sstevel@tonic-gate 
3340Sstevel@tonic-gate 	}
3350Sstevel@tonic-gate 
3360Sstevel@tonic-gate 
3370Sstevel@tonic-gate 	// Add to record. Optimize, by putting the local address at the
3380Sstevel@tonic-gate 	//  beginning of the vector.
3390Sstevel@tonic-gate 
3400Sstevel@tonic-gate 	Vector interfaces = conf.getInterfaces();
3410Sstevel@tonic-gate 
3420Sstevel@tonic-gate 	if (interfaces.contains(addr)) {
3430Sstevel@tonic-gate 	    rec.daAddresses.insertElementAt(addr, 0);
3440Sstevel@tonic-gate 
3450Sstevel@tonic-gate 	} else {
3460Sstevel@tonic-gate 	    rec.daAddresses.addElement(addr);
3470Sstevel@tonic-gate 
3480Sstevel@tonic-gate 	}
3490Sstevel@tonic-gate 
3500Sstevel@tonic-gate 	return true;
3510Sstevel@tonic-gate     }
3520Sstevel@tonic-gate 
3530Sstevel@tonic-gate     /**
3540Sstevel@tonic-gate      * Validate the scope names. We check that they are all strings,
3550Sstevel@tonic-gate      * that none are the empty string. In addition, we collate to
3560Sstevel@tonic-gate      * remove duplicates, and lower case.
3570Sstevel@tonic-gate      */
3580Sstevel@tonic-gate 
validateScopes(Vector scopes, Locale locale)3590Sstevel@tonic-gate     static void validateScopes(Vector scopes, Locale locale)
3600Sstevel@tonic-gate 	throws ServiceLocationException {
3610Sstevel@tonic-gate 
3620Sstevel@tonic-gate 	// Check for empty vector.
3630Sstevel@tonic-gate 
3640Sstevel@tonic-gate 	if (scopes == null || scopes.size() <= 0) {
3650Sstevel@tonic-gate 	    throw
3660Sstevel@tonic-gate 		new ServiceLocationException(
3670Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
3680Sstevel@tonic-gate 				"no_scope_vector",
3690Sstevel@tonic-gate 				new Object[0]);
3700Sstevel@tonic-gate 	}
3710Sstevel@tonic-gate 
3720Sstevel@tonic-gate 	// Check for all strings and none empty.
3730Sstevel@tonic-gate 
3740Sstevel@tonic-gate 	int i;
3750Sstevel@tonic-gate 	Hashtable ht = new Hashtable();
3760Sstevel@tonic-gate 
3770Sstevel@tonic-gate 	for (i = 0; i < scopes.size(); i++) {
3780Sstevel@tonic-gate 	    Object o = scopes.elementAt(i);
3790Sstevel@tonic-gate 
3800Sstevel@tonic-gate 	    if (!(o instanceof String)) {
3810Sstevel@tonic-gate 		throw
3820Sstevel@tonic-gate 		    new ServiceLocationException(
3830Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
3840Sstevel@tonic-gate 				"non_string_element",
3850Sstevel@tonic-gate 				new Object[] {scopes});
3860Sstevel@tonic-gate 	    }
3870Sstevel@tonic-gate 
3880Sstevel@tonic-gate 	    String str = (String)o;
3890Sstevel@tonic-gate 
3900Sstevel@tonic-gate 	    if (str.length() <= 0) {
3910Sstevel@tonic-gate 		throw
3920Sstevel@tonic-gate 		    new ServiceLocationException(
3930Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
3940Sstevel@tonic-gate 				"null_element",
3950Sstevel@tonic-gate 				new Object[] {scopes});
3960Sstevel@tonic-gate 	    }
3970Sstevel@tonic-gate 
3980Sstevel@tonic-gate 	    // Lower case, trim.
3990Sstevel@tonic-gate 
4000Sstevel@tonic-gate 	    str = str.toLowerCase(locale).trim();
4010Sstevel@tonic-gate 
4020Sstevel@tonic-gate 	    // Squeeze out spaces.
4030Sstevel@tonic-gate 
4040Sstevel@tonic-gate 	    StringBuffer buf = new StringBuffer();
4050Sstevel@tonic-gate 	    StringTokenizer tk =
4060Sstevel@tonic-gate 		new StringTokenizer(str, ServiceLocationAttribute.WHITESPACE);
4070Sstevel@tonic-gate 	    String tok = null;
4080Sstevel@tonic-gate 
4090Sstevel@tonic-gate 	    while (tk.hasMoreTokens()) {
4100Sstevel@tonic-gate 
4110Sstevel@tonic-gate 		// Add a single embedded whitespace for each group found.
4120Sstevel@tonic-gate 
4130Sstevel@tonic-gate 		if (tok != null) {
4140Sstevel@tonic-gate 		    buf.append(" ");
4150Sstevel@tonic-gate 
4160Sstevel@tonic-gate 		}
4170Sstevel@tonic-gate 
4180Sstevel@tonic-gate 		tok = tk.nextToken();
4190Sstevel@tonic-gate 		buf.append(tok);
4200Sstevel@tonic-gate 	    }
4210Sstevel@tonic-gate 
4220Sstevel@tonic-gate 	    str = buf.toString();
4230Sstevel@tonic-gate 
4240Sstevel@tonic-gate 	    // If it wasn't already seen, put it into the hashtable.
4250Sstevel@tonic-gate 
4260Sstevel@tonic-gate 	    if (ht.get(str) == null) {
4270Sstevel@tonic-gate 		ht.put(str, str);
4280Sstevel@tonic-gate 		scopes.setElementAt(str, i);
4290Sstevel@tonic-gate 
4300Sstevel@tonic-gate 	    } else {
4310Sstevel@tonic-gate 		/*
4320Sstevel@tonic-gate 		 *  Must decrement the index 'i' otherwise the next iteration
4330Sstevel@tonic-gate 		 *  around the loop will miss the element immediately after
4340Sstevel@tonic-gate 		 *  the element removed.
4350Sstevel@tonic-gate 		 *
4360Sstevel@tonic-gate 		 *  WARNING: Do not use 'i' again until the loop has
4370Sstevel@tonic-gate 		 *           iterated as it may, after decrementing,
4380Sstevel@tonic-gate 		 *           be negative.
4390Sstevel@tonic-gate 		 */
4400Sstevel@tonic-gate 		scopes.removeElementAt(i);
4410Sstevel@tonic-gate 		i--;
4420Sstevel@tonic-gate 		continue;
4430Sstevel@tonic-gate 	    }
4440Sstevel@tonic-gate 	}
4450Sstevel@tonic-gate     }
4460Sstevel@tonic-gate 
4470Sstevel@tonic-gate }
448